From 66acaade2940bf82032bf7fc183e4d42240c135d Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Fri, 11 Feb 2022 20:00:30 +0200 Subject: [PATCH] :sparkles: Add HaloPSA node (#2620) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added node ui * wip problems with auth * updated authentication * fixed linter error * added haloPSA request function * removed any return type * fixed linter errors * added CRUD functionalities * updating branch from master * updated create case for clients resource, added limit to getAll operation * added required fields when creating clients and sites, added methods for fetching data to dynamicly populate options when creating site or client * added required fields for users and invoices when operation is create * :hammer: Removed some commented code * :bug: Fix bug in url formating * :hammer: fixed plural resources, fixed main for loop * :hammer: fix trailing coma * :hammer: fix for wrong resource endpoints * :hammer: fixed linter complain in Jenkings node * :hammer: replace custom fields with predefined * :hammer: updating resources optional fields * :zap: Small improvement * :hammer: replaced fixedCollection to collection in resources description * :hammer: updated site and ticket descriptions, code clean up * :hammer: fixed accordingly to PR review * :zap: Improvements * :zap: Improvements * :zap: Fix capitalization * :shirt: Fix trailing comma * :construction: node changes accordingly to review * :zap: lint errors fix * :zap: Activate simplify option by default * :zap: Fix some more issues Co-authored-by: ricardo Co-authored-by: Iván Ovejero Co-authored-by: Jan Oberhauser --- .../credentials/HaloPSAApi.credentials.ts | 81 +++ .../nodes/HaloPSA/GenericFunctions.ts | 252 +++++++ .../nodes/HaloPSA/HaloPSA.node.json | 22 + .../nodes-base/nodes/HaloPSA/HaloPSA.node.ts | 683 ++++++++++++++++++ .../HaloPSA/descriptions/ClientDescription.ts | 340 +++++++++ .../HaloPSA/descriptions/SiteDescription.ts | 314 ++++++++ .../HaloPSA/descriptions/TicketDescription.ts | 300 ++++++++ .../HaloPSA/descriptions/UserDescription.ts | 320 ++++++++ .../nodes/HaloPSA/descriptions/index.ts | 15 + packages/nodes-base/nodes/HaloPSA/halopsa.svg | 1 + packages/nodes-base/package.json | 2 + 11 files changed, 2330 insertions(+) create mode 100644 packages/nodes-base/credentials/HaloPSAApi.credentials.ts create mode 100644 packages/nodes-base/nodes/HaloPSA/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/HaloPSA/HaloPSA.node.json create mode 100644 packages/nodes-base/nodes/HaloPSA/HaloPSA.node.ts create mode 100644 packages/nodes-base/nodes/HaloPSA/descriptions/ClientDescription.ts create mode 100644 packages/nodes-base/nodes/HaloPSA/descriptions/SiteDescription.ts create mode 100644 packages/nodes-base/nodes/HaloPSA/descriptions/TicketDescription.ts create mode 100644 packages/nodes-base/nodes/HaloPSA/descriptions/UserDescription.ts create mode 100644 packages/nodes-base/nodes/HaloPSA/descriptions/index.ts create mode 100644 packages/nodes-base/nodes/HaloPSA/halopsa.svg diff --git a/packages/nodes-base/credentials/HaloPSAApi.credentials.ts b/packages/nodes-base/credentials/HaloPSAApi.credentials.ts new file mode 100644 index 00000000000..43552fe7169 --- /dev/null +++ b/packages/nodes-base/credentials/HaloPSAApi.credentials.ts @@ -0,0 +1,81 @@ +import { + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + +export class HaloPSAApi implements ICredentialType { + name = 'haloPSAApi'; + displayName = 'HaloPSA API'; + documentationUrl = 'halopsa'; + properties: INodeProperties[] = [ + { + displayName: 'Hosting Type', + name: 'hostingType', + type: 'options', + options: [ + { + name: 'On-Premise Solution', + value: 'onPremise', + }, + { + name: 'Hosted Solution Of Halo', + value: 'hostedHalo', + }, + ], + default: '', + description: 'Hosting Type', + }, + { + displayName: 'HaloPSA Authorisation Server URL', + name: 'authUrl', + type: 'string', + default: '', + required: true, + }, + { + displayName: 'Resource Server', + name: 'resourceApiUrl', + type: 'string', + default: '', + required: true, + description: `The Resource server is available at your "Halo Web Application url/api"`, + }, + { + displayName: 'Client ID', + name: 'client_id', + type: 'string', + default: '', + required: true, + description: 'Must be your application client id', + }, + { + displayName: 'Client Secret', + name: 'client_secret', + type: 'string', + default: '', + required: true, + description: 'Must be your application client secret', + }, + { + displayName: 'Tenant', + name: 'tenant', + type: 'string', + displayOptions: { + show: { + hostingType: [ + 'hostedHalo', + ], + }, + }, + default: '', + description: 'An additional tenant parameter for HaloPSA hosted solution', + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden', + default: 'admin edit:tickets edit:customers', + required: true, + }, + ]; +} diff --git a/packages/nodes-base/nodes/HaloPSA/GenericFunctions.ts b/packages/nodes-base/nodes/HaloPSA/GenericFunctions.ts new file mode 100644 index 00000000000..c98be4b3078 --- /dev/null +++ b/packages/nodes-base/nodes/HaloPSA/GenericFunctions.ts @@ -0,0 +1,252 @@ +import { IExecuteFunctions, IHookFunctions } from 'n8n-core'; + +import { + ICredentialDataDecryptedObject, + ICredentialTestFunctions, + IDataObject, + IExecuteSingleFunctions, + ILoadOptionsFunctions, + IPollFunctions, + JsonObject, + NodeApiError, +} from 'n8n-workflow'; + +import { OptionsWithUri } from 'request'; + +// Interfaces and Types ------------------------------------------------------------- +interface IHaloPSATokens { + scope: string; + token_type: string; + access_token: string; + expires_in: number; + refresh_token: string; + id_token: string; +} + +// API Requests --------------------------------------------------------------------- + +export async function getAccessTokens( + this: IExecuteFunctions | ILoadOptionsFunctions, +): Promise { + const credentials = (await this.getCredentials('haloPSAApi')) as IDataObject; + + const options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + method: 'POST', + form: { + client_id: credentials.client_id, + client_secret: credentials.client_secret, + grant_type: 'client_credentials', + scope: credentials.scope, + }, + uri: getAuthUrl(credentials), + json: true, + }; + + try { + const tokens = await this.helpers.request!(options); + return tokens; + } catch (error) { + throw new NodeApiError(this.getNode(), error as JsonObject); + } +} + +export async function haloPSAApiRequest( + this: + | IHookFunctions + | IExecuteFunctions + | IExecuteSingleFunctions + | ILoadOptionsFunctions + | IPollFunctions, + method: string, + resource: string, + accessToken: string, + body: IDataObject | IDataObject[] = {}, + qs: IDataObject = {}, + option: IDataObject = {}, +): Promise { // tslint:disable-line:no-any + const resourceApiUrl = ((await this.getCredentials('haloPSAApi')) as IDataObject) + .resourceApiUrl as string; + + try { + let options: OptionsWithUri = { + headers: { + Authorization: `Bearer ${accessToken}`, + 'User-Agent': 'https://n8n.io', + Connection: 'keep-alive', + Accept: '*/*', + 'Accept-Encoding': 'gzip, deflate, br', + 'Content-Type': 'application/json', + }, + method, + qs, + body, + uri: `${resourceApiUrl}${resource}`, + json: true, + }; + options = Object.assign({}, options, option); + if (Object.keys(body).length === 0) { + delete options.body; + } + const result = await this.helpers.request!(options); + if (method === 'DELETE' && result.id) { + return { success: true }; + } + return result; + } catch (error) { + const message = (error as JsonObject).message as string; + if (method === 'DELETE' || 'GET' || ('UPDATE' && message)) { + let newErrorMessage; + if (message.includes('400')) { + console.log(message); + newErrorMessage = JSON.parse(message.split(' - ')[1]); + (error as JsonObject).message = `For field ID, ${ + newErrorMessage.id || newErrorMessage['[0].id'] + }`; + } + if (message.includes('403')) { + ( + error as JsonObject + ).message = `You don\'t have permissions to ${method.toLowerCase()} ${resource + .split('/')[1] + .toLowerCase()}.`; + } + } + throw new NodeApiError(this.getNode(), error as JsonObject); + } +} + +// export async function reasignTickets( +// this: +// | IHookFunctions +// | IExecuteFunctions +// | IExecuteSingleFunctions +// | ILoadOptionsFunctions +// | IPollFunctions, +// clientId: string, +// reasigmentCliendId: string, +// accessToken: string, +// ): Promise { +// const response = (await haloPSAApiRequest.call( +// this, +// 'GET', +// `/tickets`, +// accessToken, +// {}, +// { client_id: reasigmentCliendId }, +// )) as IDataObject; + +// const { tickets } = response; +// console.log((tickets as IDataObject[]).map(t => t.id)); +// const body: IDataObject = { +// id: clientId, +// client_id: reasigmentCliendId, +// }; + +// for (const ticket of (tickets as IDataObject[])) { +// console.log(ticket.id); +// await haloPSAApiRequest.call(this, 'DELETE', `/tickets/${ticket.id}`, accessToken); +// } +// } + +export async function haloPSAApiRequestAllItems( + this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions, + propertyName: string, + method: string, + endpoint: string, + accessToken: string, + body = {}, + query: IDataObject = {}, +): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData: IDataObject; + query.page_size = 100; + query.page_no = 1; + query.pageinate = true; + + do { + responseData = (await haloPSAApiRequest.call( + this, + method, + endpoint, + accessToken, + body, + query, + )) as IDataObject; + returnData.push.apply(returnData, responseData[propertyName] as IDataObject[]); + query.page_no++; + //@ts-ignore + } while (returnData.length < responseData.record_count); + + return returnData; +} + +// Utilities ------------------------------------------------------------------------ +function getAuthUrl(credentials: IDataObject) { + return credentials.hostingType === 'on-premise' + ? `${credentials.appUrl}/auth/token` + : `${credentials.authUrl}/token?tenant=${credentials.tenant}`; +} + +export function simplifyHaloPSAGetOutput( + response: IDataObject[], + fieldsList: string[], +): IDataObject[] { + const output = []; + for (const item of response) { + const simplifiedItem: IDataObject = {}; + Object.keys(item).forEach((key: string) => { + if (fieldsList.includes(key)) { + simplifiedItem[key] = item[key]; + } + }); + output.push(simplifiedItem); + } + return output; +} + +export function qsSetStatus(status: string) { + if (!status) return {}; + const qs: IDataObject = {}; + if (status === 'all') { + qs['includeinactive'] = true; + qs['includeactive'] = true; + } else if (status === 'active') { + qs['includeinactive'] = false; + qs['includeactive'] = true; + } else { + qs['includeinactive'] = true; + qs['includeactive'] = false; + } + return qs; +} + +// Validation ----------------------------------------------------------------------- + +export async function validateCrendetials( + this: ICredentialTestFunctions, + decryptedCredentials: ICredentialDataDecryptedObject, +): Promise { + const credentials = decryptedCredentials; + + const options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + method: 'POST', + form: { + client_id: credentials.client_id, + client_secret: credentials.client_secret, + grant_type: 'client_credentials', + scope: credentials.scope, + }, + uri: getAuthUrl(credentials), + json: true, + }; + + return (await this.helpers.request!(options)) as IHaloPSATokens; +} diff --git a/packages/nodes-base/nodes/HaloPSA/HaloPSA.node.json b/packages/nodes-base/nodes/HaloPSA/HaloPSA.node.json new file mode 100644 index 00000000000..501170a176c --- /dev/null +++ b/packages/nodes-base/nodes/HaloPSA/HaloPSA.node.json @@ -0,0 +1,22 @@ +{ + "node": "n8n-nodes-base.haloPSA", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": [ + "Data & Storage", + "Sales", + "Productivity" + ], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/credentials/halopsa" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/nodes/n8n-nodes-base.halopsa/" + } + ] + } +} diff --git a/packages/nodes-base/nodes/HaloPSA/HaloPSA.node.ts b/packages/nodes-base/nodes/HaloPSA/HaloPSA.node.ts new file mode 100644 index 00000000000..cdcc8776775 --- /dev/null +++ b/packages/nodes-base/nodes/HaloPSA/HaloPSA.node.ts @@ -0,0 +1,683 @@ +import { IExecuteFunctions } from 'n8n-core'; + +import { + ICredentialDataDecryptedObject, + ICredentialsDecrypted, + ICredentialTestFunctions, + IDataObject, + ILoadOptionsFunctions, + INodeCredentialTestResult, + INodeExecutionData, + INodePropertyOptions, + INodeType, + INodeTypeDescription, + JsonObject, +} from 'n8n-workflow'; + +import { + clientFields, + clientOperations, + siteFields, + siteOperations, + ticketFields, + ticketOperations, + userFields, + userOperations, +} from './descriptions'; + +import { + getAccessTokens, + haloPSAApiRequest, + haloPSAApiRequestAllItems, + qsSetStatus, + simplifyHaloPSAGetOutput, + validateCrendetials, +} from './GenericFunctions'; + +export class HaloPSA implements INodeType { + description: INodeTypeDescription = { + displayName: 'HaloPSA', + name: 'haloPSA', + icon: 'file:halopsa.svg', + group: ['input'], + version: 1, + description: 'Consume HaloPSA API', + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + defaults: { + name: 'HaloPSA', + color: '#fd314e', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'haloPSAApi', + required: true, + testedBy: 'haloPSAApiCredentialTest', + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'Client', + value: 'client', + }, + { + name: 'Site', + value: 'site', + }, + { + name: 'Ticket', + value: 'ticket', + }, + { + name: 'User', + value: 'user', + }, + ], + default: 'client', + required: true, + }, + ...clientOperations, + ...clientFields, + ...ticketOperations, + ...ticketFields, + ...siteOperations, + ...siteFields, + ...userOperations, + ...userFields, + ], + }; + + methods = { + loadOptions: { + async getHaloPSASites(this: ILoadOptionsFunctions): Promise { + const tokens = await getAccessTokens.call(this); + + const response = (await haloPSAApiRequestAllItems.call( + this, + 'sites', + 'GET', + '/site', + tokens.access_token, + )) as IDataObject[]; + + const options = response.map((site) => { + return { + name: site.clientsite_name as string, + value: site.id as number, + }; + }); + + return options.sort((a, b) => a.name.localeCompare(b.name)); + }, + + async getHaloPSAClients(this: ILoadOptionsFunctions): Promise { + const tokens = await getAccessTokens.call(this); + + const response = (await haloPSAApiRequestAllItems.call( + this, + 'clients', + 'GET', + '/Client', + tokens.access_token, + )) as IDataObject[]; + + const options = response.map((client) => { + return { + name: client.name as string, + value: client.id as number, + }; + }); + + return options.sort((a, b) => a.name.localeCompare(b.name)); + }, + + async getHaloPSATicketsTypes(this: ILoadOptionsFunctions): Promise { + const tokens = await getAccessTokens.call(this); + + const response = (await haloPSAApiRequest.call( + this, + 'GET', + `/TicketType`, + tokens.access_token, + {}, + )) as IDataObject[]; + + const options = response.map((ticket) => { + return { + name: ticket.name as string, + value: ticket.id as number, + }; + }); + + return options + .filter((ticket) => { + if ( + // folowing types throws error 400 - "CODE:APP03/2 Please select the CAB members to approve" + ticket.name.includes('Request') || + ticket.name.includes('Offboarding') || + ticket.name.includes('Onboarding') || + ticket.name.includes('Other Hardware') || + ticket.name.includes('Software Change') + ) { + return false; + } + return true; + }) + .sort((a, b) => a.name.localeCompare(b.name)); + }, + + async getHaloPSAAgents(this: ILoadOptionsFunctions): Promise { + const tokens = await getAccessTokens.call(this); + + const response = (await haloPSAApiRequest.call( + this, + 'GET', + `/agent`, + tokens.access_token, + {}, + )) as IDataObject[]; + + const options = response.map((agent) => { + return { + name: agent.name as string, + value: agent.id as number, + }; + }); + + return options.sort((a, b) => a.name.localeCompare(b.name)); + }, + }, + + credentialTest: { + async haloPSAApiCredentialTest( + this: ICredentialTestFunctions, + credential: ICredentialsDecrypted, + ): Promise { + try { + await validateCrendetials.call(this, credential.data as ICredentialDataDecryptedObject); + } catch (error) { + return { + status: 'Error', + message: (error as JsonObject).message as string, + }; + } + return { + status: 'OK', + message: 'Connection successful!', + }; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + let responseData; + + const tokens = await getAccessTokens.call(this); + + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + //==================================================================== + // Main Loop + //==================================================================== + + for (let i = 0; i < items.length; i++) { + try { + if (resource === 'client') { + const simplifiedOutput = ['id', 'name', 'notes', 'is_vip', 'website']; + + if (operation === 'create') { + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const name = this.getNodeParameter('clientName', i) as string; + const body: IDataObject = { + name, + ...additionalFields, + }; + + responseData = await haloPSAApiRequest.call( + this, + 'POST', + '/client', + tokens.access_token, + [body], + ); + } + + if (operation === 'delete') { + const clientId = this.getNodeParameter('clientId', i) as string; + // const reasign = this.getNodeParameter('reasign', i) as boolean; + // if (reasign) { + // const reasigmentCliendId = this.getNodeParameter('reasigmentCliendId', i) as string; + // await reasignTickets.call(this, clientId, reasigmentCliendId, tokens.access_token); + // } + + responseData = await haloPSAApiRequest.call( + this, + 'DELETE', + `/client/${clientId}`, + tokens.access_token, + ); + } + + if (operation === 'get') { + const clientId = this.getNodeParameter('clientId', i) as string; + const simplify = this.getNodeParameter('simplify', i) as boolean; + let response; + response = await haloPSAApiRequest.call( + this, + 'GET', + `/client/${clientId}`, + tokens.access_token, + ); + responseData = simplify + ? simplifyHaloPSAGetOutput([response], simplifiedOutput) + : response; + } + + if (operation === 'getAll') { + const filters = this.getNodeParameter('filters', i) as IDataObject; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const simplify = this.getNodeParameter('simplify', i) as boolean; + const qs: IDataObject = {}; + let response; + + Object.assign(qs, filters, qsSetStatus(filters.activeStatus as string)); + if (returnAll) { + response = await haloPSAApiRequestAllItems.call( + this, + 'clients', + 'GET', + `/client`, + tokens.access_token, + {}, + qs, + ); + } else { + const limit = this.getNodeParameter('limit', i) as number; + qs.count = limit; + const { clients } = await haloPSAApiRequest.call( + this, + 'GET', + `/client`, + tokens.access_token, + {}, + qs, + ); + response = clients; + } + responseData = simplify + ? simplifyHaloPSAGetOutput(response, simplifiedOutput) + : response; + } + + if (operation === 'update') { + const clientId = this.getNodeParameter('clientId', i) as IDataObject; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const body: IDataObject = { + id: clientId, + ...updateFields, + }; + + responseData = await haloPSAApiRequest.call( + this, + 'POST', + '/client', + tokens.access_token, + [body], + ); + } + } + + if (resource === 'site') { + const simplifiedOutput = [ + 'id', + 'name', + 'client_id', + 'maincontact_name', + 'notes', + 'phonenumber', + ]; + + if (operation === 'create') { + const name = this.getNodeParameter('siteName', i) as string; + const clientId = this.getNodeParameter('clientId', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: IDataObject = { + name, + client_id: clientId, + ...additionalFields, + }; + + responseData = await haloPSAApiRequest.call( + this, + 'POST', + '/site', + tokens.access_token, + [body], + ); + } + + if (operation === 'delete') { + const siteId = this.getNodeParameter('siteId', i) as string; + responseData = await haloPSAApiRequest.call( + this, + 'DELETE', + `/site/${siteId}`, + tokens.access_token, + ); + } + + if (operation === 'get') { + const siteId = this.getNodeParameter('siteId', i) as string; + const simplify = this.getNodeParameter('simplify', i) as boolean; + let response; + response = await haloPSAApiRequest.call( + this, + 'GET', + `/site/${siteId}`, + tokens.access_token, + ); + responseData = simplify + ? simplifyHaloPSAGetOutput([response], simplifiedOutput) + : response; + } + + if (operation === 'getAll') { + const filters = this.getNodeParameter('filters', i) as IDataObject; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const simplify = this.getNodeParameter('simplify', i) as boolean; + const qs: IDataObject = {}; + let response; + + Object.assign(qs, filters, qsSetStatus(filters.activeStatus as string)); + if (returnAll) { + response = await haloPSAApiRequestAllItems.call( + this, + 'sites', + 'GET', + `/site`, + tokens.access_token, + {}, + qs, + ); + } else { + const limit = this.getNodeParameter('limit', i) as number; + qs.count = limit; + const { sites } = await haloPSAApiRequest.call( + this, + 'GET', + `/site`, + tokens.access_token, + {}, + qs, + ); + response = sites; + } + responseData = simplify + ? simplifyHaloPSAGetOutput(response, simplifiedOutput) + : response; + } + + if (operation === 'update') { + const siteId = this.getNodeParameter('siteId', i) as IDataObject; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const body: IDataObject = { + id: siteId, + ...updateFields, + }; + + responseData = await haloPSAApiRequest.call( + this, + 'POST', + '/site', + tokens.access_token, + [body], + ); + } + } + + if (resource === 'ticket') { + const simplifiedOutput = [ + 'id', + 'summary', + 'details', + 'agent_id', + 'startdate', + 'targetdate', + ]; + + if (operation === 'create') { + const summary = this.getNodeParameter('summary', i) as string; + const details = this.getNodeParameter('details', i) as string; + const ticketType = this.getNodeParameter('ticketType', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: IDataObject = { + tickettype_id: ticketType, + summary, + details, + ...additionalFields, + }; + + responseData = await haloPSAApiRequest.call( + this, + 'POST', + '/tickets', + tokens.access_token, + [body], + ); + } + + if (operation === 'delete') { + const ticketId = this.getNodeParameter('ticketId', i) as string; + responseData = await haloPSAApiRequest.call( + this, + 'DELETE', + `/tickets/${ticketId}`, + tokens.access_token, + ); + } + + if (operation === 'get') { + const ticketId = this.getNodeParameter('ticketId', i) as string; + const simplify = this.getNodeParameter('simplify', i) as boolean; + let response; + response = await haloPSAApiRequest.call( + this, + 'GET', + `/tickets/${ticketId}`, + tokens.access_token, + ); + responseData = simplify + ? simplifyHaloPSAGetOutput([response], simplifiedOutput) + : response; + } + + if (operation === 'getAll') { + const filters = this.getNodeParameter('filters', i) as IDataObject; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const simplify = this.getNodeParameter('simplify', i) as boolean; + const qs: IDataObject = {}; + let response; + + Object.assign(qs, filters, qsSetStatus(filters.activeStatus as string)); + if (returnAll) { + response = await haloPSAApiRequestAllItems.call( + this, + 'tickets', + 'GET', + `/tickets`, + tokens.access_token, + {}, + qs, + ); + } else { + const limit = this.getNodeParameter('limit', i) as number; + qs.count = limit; + const { tickets } = await haloPSAApiRequest.call( + this, + 'GET', + `/tickets`, + tokens.access_token, + {}, + qs, + ); + response = tickets; + } + responseData = simplify + ? simplifyHaloPSAGetOutput(response, simplifiedOutput) + : response; + } + + if (operation === 'update') { + const ticketId = this.getNodeParameter('ticketId', i) as IDataObject; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const body: IDataObject = { + id: ticketId, + ...updateFields, + }; + + responseData = await haloPSAApiRequest.call( + this, + 'POST', + '/tickets', + tokens.access_token, + [body], + ); + } + } + + if (resource === 'user') { + const simplifiedOutput = [ + 'id', + 'name', + 'site_id', + 'emailaddress', + 'notes', + 'surname', + 'inactive', + ]; + + if (operation === 'create') { + const name = this.getNodeParameter('userName', i) as string; + const siteId = this.getNodeParameter('siteId', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body: IDataObject = { + name, + site_id: siteId, + ...additionalFields, + }; + + responseData = await haloPSAApiRequest.call( + this, + 'POST', + '/users', + tokens.access_token, + [body], + ); + } + + if (operation === 'delete') { + const userId = this.getNodeParameter('userId', i) as string; + responseData = await haloPSAApiRequest.call( + this, + 'DELETE', + `/users/${userId}`, + tokens.access_token, + ); + } + + if (operation === 'get') { + const userId = this.getNodeParameter('userId', i) as string; + const simplify = this.getNodeParameter('simplify', i) as boolean; + let response; + response = await haloPSAApiRequest.call( + this, + 'GET', + `/users/${userId}`, + tokens.access_token, + ); + responseData = simplify + ? simplifyHaloPSAGetOutput([response], simplifiedOutput) + : response; + } + + if (operation === 'getAll') { + const filters = this.getNodeParameter('filters', i) as IDataObject; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const simplify = this.getNodeParameter('simplify', i) as boolean; + const qs: IDataObject = {}; + let response; + + Object.assign(qs, filters, qsSetStatus(filters.activeStatus as string)); + if (returnAll) { + response = await haloPSAApiRequestAllItems.call( + this, + 'users', + 'GET', + `/users`, + tokens.access_token, + {}, + qs, + ); + } else { + const limit = this.getNodeParameter('limit', i) as number; + qs.count = limit; + const { users } = await haloPSAApiRequest.call( + this, + 'GET', + `/users`, + tokens.access_token, + {}, + qs, + ); + response = users; + } + responseData = simplify + ? simplifyHaloPSAGetOutput(response, simplifiedOutput) + : response; + } + + if (operation === 'update') { + const userId = this.getNodeParameter('userId', i) as IDataObject; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const body: IDataObject = { + id: userId, + ...updateFields, + }; + + responseData = await haloPSAApiRequest.call( + this, + 'POST', + '/users', + tokens.access_token, + [body], + ); + } + } + + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData); + } else if (responseData !== undefined) { + returnData.push(responseData); + } + } catch (error) { + if (this.continueOnFail()) { + returnData.push({ error: (error as JsonObject).message }); + continue; + } + throw error; + } + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/HaloPSA/descriptions/ClientDescription.ts b/packages/nodes-base/nodes/HaloPSA/descriptions/ClientDescription.ts new file mode 100644 index 00000000000..729196c1ff2 --- /dev/null +++ b/packages/nodes-base/nodes/HaloPSA/descriptions/ClientDescription.ts @@ -0,0 +1,340 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const clientOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: ['client'], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a client', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a client', + }, + { + name: 'Get', + value: 'get', + description: 'Get a client', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all clients', + }, + { + name: 'Update', + value: 'update', + description: 'Update a client', + }, + ], + default: 'create', + }, +]; + +export const clientFields: INodeProperties[] = [ + /* -------------------------------------------------------------------------- */ + /* client:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Name', + name: 'clientName', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: ['create'], + resource: ['client'], + }, + }, + description: 'Enter client name', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + operation: ['create'], + resource: ['client'], + }, + }, + options: [ + { + displayName: 'Account Status', + name: 'inactive', + type: 'options', + default: false, + options: [ + { + name: 'Active', + value: false, + }, + { + name: 'Inactive', + value: true, + }, + ], + }, + { + displayName: 'Notes', + name: 'notes', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + }, + { + displayName: 'VIP', + name: 'is_vip', + type: 'boolean', + default: false, + description: 'Whether the client is VIP or not', + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* client:delete */ + /* -------------------------------------------------------------------------- */ + // { + // displayName: 'Reasign tickets before deleting', + // name: 'reasign', + // type: 'boolean', + // default: false, + // description: 'Whether tickets assigned to client sould be reasigned', + // displayOptions: { + // show: { + // resource: [ + // 'client', + // ], + // operation: [ + // 'delete', + // ], + // }, + // }, + // }, + + // { + // displayName: 'Client ID For Reasigment', + // name: 'reasigmentCliendId', + // type: 'string', + // default: '', + // displayOptions: { + // show: { + // resource: [ + // 'client', + // ], + // operation: [ + // 'delete', + // ], + // reasign: [ + // true + // ] + // }, + // }, + // }, + + /* -------------------------------------------------------------------------- */ + /* client:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Client ID', + name: 'clientId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: ['client'], + operation: ['get', 'delete'], + }, + }, + }, + + { + displayName: 'Simplify Output', + name: 'simplify', + type: 'boolean', + default: true, + description: 'Whether output should be simplified', + displayOptions: { + show: { + resource: ['client'], + operation: ['get', 'getAll'], + }, + }, + }, + /* -------------------------------------------------------------------------- */ + /* client:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: ['client'], + operation: ['getAll'], + }, + }, + default: false, + description: 'Whether to return all results or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + displayOptions: { + show: { + resource: ['client'], + operation: ['getAll'], + returnAll: [false], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + description: 'Max number of results to return', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + resource: ['client'], + operation: ['getAll'], + }, + }, + options: [ + { + displayName: 'Active Status', + name: 'activeStatus', + type: 'options', + default: 'active', + options: [ + { + name: 'Active only', + value: 'active', + description: 'Whether to include active customers in the response', + }, + { + name: 'All', + value: 'all', + description: 'Whether to include active and inactive customers in the response', + }, + { + name: 'Inactive only', + value: 'inactive', + description: 'Whether to include inactive Customers in the response', + }, + ], + }, + { + displayName: 'Text To Filter By', + name: 'search', + type: 'string', + default: '', + description: 'Filter clients by your search string', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* client:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Client ID', + name: 'clientId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: ['client'], + operation: ['update'], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + resource: ['client'], + operation: ['update'], + }, + }, + options: [ + { + displayName: 'Account Status', + name: 'inactive', + type: 'options', + default: false, + options: [ + { + name: 'Active', + value: false, + }, + { + name: 'Inactive', + value: true, + }, + ], + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Notes', + name: 'notes', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + }, + { + displayName: 'VIP', + name: 'is_vip', + type: 'boolean', + default: false, + description: 'Whether the client is VIP or not', + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/HaloPSA/descriptions/SiteDescription.ts b/packages/nodes-base/nodes/HaloPSA/descriptions/SiteDescription.ts new file mode 100644 index 00000000000..03f31c7f815 --- /dev/null +++ b/packages/nodes-base/nodes/HaloPSA/descriptions/SiteDescription.ts @@ -0,0 +1,314 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const siteOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: ['site'], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a site', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a site', + }, + { + name: 'Get', + value: 'get', + description: 'Get a site', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all sites', + }, + { + name: 'Update', + value: 'update', + description: 'Update a site', + }, + ], + default: 'create', + }, +]; + +export const siteFields: INodeProperties[] = [ + /* -------------------------------------------------------------------------- */ + /* site:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Name', + name: 'siteName', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: ['site'], + operation: ['create'], + }, + }, + description: 'Enter site name', + }, + { + displayName: 'Select Client by ID', + name: 'selectOption', + type: 'boolean', + default: false, + description: 'Whether client can be selected by id', + displayOptions: { + show: { + resource: ['site'], + operation: ['create'], + }, + }, + }, + { + displayName: 'Client ID', + name: 'clientId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: ['site'], + operation: ['create'], + selectOption: [true], + }, + }, + }, + { + displayName: 'Client Name', + name: 'clientId', + type: 'options', + default: '', + required: true, + typeOptions: { + loadOptionsMethod: 'getHaloPSAClients', + }, + displayOptions: { + show: { + resource: ['site'], + operation: ['create'], + selectOption: [false], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + resource: ['site'], + operation: ['create'], + }, + }, + options: [ + { + displayName: 'Main Contact', + name: 'maincontact_name', + type: 'string', + default: '', + }, + { + displayName: 'Notes', + name: 'notes', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + }, + { + displayName: 'Phone Number', + name: 'phonenumber', + type: 'string', + default: '', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* site:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Site ID', + name: 'siteId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: ['site'], + operation: ['delete', 'get'], + }, + }, + }, + { + displayName: 'Simplify Output', + name: 'simplify', + type: 'boolean', + default: true, + description: 'Whether output should be simplified', + displayOptions: { + show: { + resource: ['site'], + operation: ['get', 'getAll'], + }, + }, + }, + /* -------------------------------------------------------------------------- */ + /* site:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: ['site'], + operation: ['getAll'], + }, + }, + default: false, + description: 'Whether to return all results or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + displayOptions: { + show: { + resource: ['site'], + operation: ['getAll'], + returnAll: [false], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + description: 'Max number of results to return', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + resource: ['site'], + operation: ['getAll'], + }, + }, + options: [ + { + displayName: 'Active Status', + name: 'activeStatus', + type: 'options', + default: 'all', + options: [ + { + name: 'Active only', + value: 'active', + description: 'Whether to include active sites in the response', + }, + { + name: 'All', + value: 'all', + description: 'Whether to include active and inactive sites in the response', + }, + { + name: 'Inactive only', + value: 'inactive', + description: 'Whether to include inactive sites in the response', + }, + ], + }, + { + displayName: 'Text To Filter By', + name: 'search', + type: 'string', + default: '', + description: 'Filter sites by your search string', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* site:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Site ID', + name: 'siteId', + type: 'string', + default: '', + displayOptions: { + show: { + resource: ['site'], + operation: ['update'], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + resource: ['site'], + operation: ['update'], + }, + }, + options: [ + { + displayName: 'Client ID', + name: 'client_id', + type: 'string', + default: '', + }, + { + displayName: 'Main Contact', + name: 'maincontact_name', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Enter site name', + }, + { + displayName: 'Notes', + name: 'notes', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + }, + { + displayName: 'Phone Number', + name: 'phonenumber', + type: 'string', + default: '', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/HaloPSA/descriptions/TicketDescription.ts b/packages/nodes-base/nodes/HaloPSA/descriptions/TicketDescription.ts new file mode 100644 index 00000000000..0591d150f28 --- /dev/null +++ b/packages/nodes-base/nodes/HaloPSA/descriptions/TicketDescription.ts @@ -0,0 +1,300 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const ticketOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: ['ticket'], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a ticket', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a ticket', + }, + { + name: 'Get', + value: 'get', + description: 'Get a ticket', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all tickets', + }, + { + name: 'Update', + value: 'update', + description: 'Update a ticket', + }, + ], + default: 'delete', + }, +]; + +export const ticketFields: INodeProperties[] = [ + /* -------------------------------------------------------------------------- */ + /* ticket:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Ticket Type', + name: 'ticketType', + type: 'options', + default: '', + required: true, + typeOptions: { + loadOptionsMethod: 'getHaloPSATicketsTypes', + }, + displayOptions: { + show: { + resource: ['ticket'], + operation: ['create'], + }, + }, + }, + { + displayName: 'Summary', + name: 'summary', + type: 'string', + default: '', + placeholder: '', + required: true, + displayOptions: { + show: { + resource: ['ticket'], + operation: ['create'], + }, + }, + }, + { + displayName: 'Details', + name: 'details', + type: 'string', + default: '', + placeholder: '', + required: true, + displayOptions: { + show: { + resource: ['ticket'], + operation: ['create'], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + resource: ['ticket'], + operation: ['create'], + }, + }, + options: [ + { + displayName: 'Assigned Agent Name/ID', + name: 'agent_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getHaloPSAAgents', + }, + }, + { + displayName: 'Start Date', + name: 'startdate', + type: 'dateTime', + default: '', + }, + { + displayName: 'Target Date', + name: 'targetdate', + type: 'dateTime', + default: '', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* site:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Ticket ID', + name: 'ticketId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: ['ticket'], + operation: ['delete', 'get'], + }, + }, + }, + + { + displayName: 'Simplify Output', + name: 'simplify', + type: 'boolean', + default: true, + description: 'Whether output should be simplified', + displayOptions: { + show: { + resource: ['ticket'], + operation: ['get', 'getAll'], + }, + }, + }, + /* -------------------------------------------------------------------------- */ + /* ticket:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: ['ticket'], + operation: ['getAll'], + }, + }, + default: false, + description: 'Whether to return all results or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + displayOptions: { + show: { + resource: ['ticket'], + operation: ['getAll'], + returnAll: [false], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + description: 'Max number of results to return', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + resource: ['ticket'], + operation: ['getAll'], + }, + }, + options: [ + { + displayName: 'Active Status', + name: 'activeStatus', + type: 'options', + default: 'all', + options: [ + { + name: 'Active only', + value: 'active', + description: 'Whether to include active customers in the response', + }, + { + name: 'All', + value: 'all', + description: 'Whether to include active and inactive customers in the response', + }, + { + name: 'Inactive only', + value: 'inactive', + description: 'Whether to include inactive Customers in the responsee', + }, + ], + }, + { + displayName: 'Text To Filter By', + name: 'search', + type: 'string', + default: '', + description: 'Filter tickets by your search string', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* ticket:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Ticket ID', + name: 'ticketId', + type: 'string', + default: '', + displayOptions: { + show: { + resource: ['ticket'], + operation: ['update'], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + resource: ['ticket'], + operation: ['update'], + }, + }, + options: [ + { + displayName: 'Assigned Agent Name/ID', + name: 'agent_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getHaloPSAAgents', + }, + }, + { + displayName: 'Details', + name: 'details', + type: 'string', + default: '', + }, + { + displayName: 'Start Date', + name: 'startdate', + type: 'dateTime', + default: '', + }, + { + displayName: 'Summary', + name: 'summary', + type: 'string', + default: '', + }, + { + displayName: 'Target Date', + name: 'targetdate', + type: 'dateTime', + default: '', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/HaloPSA/descriptions/UserDescription.ts b/packages/nodes-base/nodes/HaloPSA/descriptions/UserDescription.ts new file mode 100644 index 00000000000..c13912c0405 --- /dev/null +++ b/packages/nodes-base/nodes/HaloPSA/descriptions/UserDescription.ts @@ -0,0 +1,320 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const userOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: ['user'], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a user', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a user', + }, + { + name: 'Get', + value: 'get', + description: 'Get a user', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all users', + }, + { + name: 'Update', + value: 'update', + description: 'Update a user', + }, + ], + default: 'create', + }, +]; + +export const userFields: INodeProperties[] = [ + /* -------------------------------------------------------------------------- */ + /* user:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Name', + name: 'userName', + type: 'string', + default: '', + description: 'Enter user name', + required: true, + displayOptions: { + show: { + resource: ['user'], + operation: ['create'], + }, + }, + }, + { + displayName: 'Site Name/ID', + name: 'siteId', + type: 'options', + default: '', + required: true, + typeOptions: { + loadOptionsMethod: 'getHaloPSASites', + }, + displayOptions: { + show: { + resource: ['user'], + operation: ['create'], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + resource: ['user'], + operation: ['create'], + }, + }, + options: [ + { + displayName: 'Email', + name: 'emailaddress', + type: 'string', + default: '', + }, + { + displayName: 'Notes', + name: 'notes', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + }, + { + displayName: 'Password', + name: 'password', + type: 'string', + default: '', + description: + 'Your new password must be at least 8 characters long and contain at least one letter, one number or symbol, one upper case character and one lower case character', + }, + { + displayName: 'Surname', + name: 'surname', + type: 'string', + default: '', + }, + { + displayName: 'User is Inactive', + name: 'inactive', + type: 'boolean', + default: false, + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* user:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'User ID', + name: 'userId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: ['user'], + operation: ['delete', 'get'], + }, + }, + }, + { + displayName: 'Simplify Output', + name: 'simplify', + type: 'boolean', + default: true, + description: 'Whether output should be simplified', + displayOptions: { + show: { + resource: ['user'], + operation: ['get', 'getAll'], + }, + }, + }, + /* -------------------------------------------------------------------------- */ + /* user:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: ['user'], + operation: ['getAll'], + }, + }, + default: false, + description: 'Whether to return all results or only up to a given limit', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + displayOptions: { + show: { + resource: ['user'], + operation: ['getAll'], + returnAll: [false], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + description: 'Max number of results to return', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + resource: ['user'], + operation: ['getAll'], + }, + }, + options: [ + { + displayName: 'Active Status', + name: 'activeStatus', + type: 'options', + default: 'all', + options: [ + { + name: 'Active only', + value: 'active', + description: 'Whether to include active customers in the response', + }, + { + name: 'All', + value: 'all', + description: 'Whether to include active and inactive customers in the response', + }, + { + name: 'Inactive only', + value: 'inactive', + description: 'Whether to include inactive Customers in the response', + }, + ], + }, + { + displayName: 'Text To Filter By', + name: 'search', + type: 'string', + default: '', + description: 'Filter users by your search string', + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* user:update */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'User ID', + name: 'userId', + type: 'string', + default: '', + displayOptions: { + show: { + resource: ['user'], + operation: ['update'], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + resource: ['user'], + operation: ['update'], + }, + }, + options: [ + { + displayName: 'Email', + name: 'emailaddress', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Enter user name', + }, + { + displayName: 'Notes', + name: 'notes', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + }, + { + displayName: 'Password', + name: 'password', + type: 'string', + typeOptions: { + password: true, + }, + default: '', + description: + 'Your new password must be at least 8 characters long and contain at least one letter, one number or symbol, one upper case character and one lower case character', + }, + { + displayName: 'Site ID', + name: 'site_id', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getHaloPSASites', + }, + }, + { + displayName: 'Surname', + name: 'surname', + type: 'string', + default: '', + }, + { + displayName: 'User is Inactive', + name: 'inactive', + type: 'boolean', + default: false, + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/HaloPSA/descriptions/index.ts b/packages/nodes-base/nodes/HaloPSA/descriptions/index.ts new file mode 100644 index 00000000000..aa9fd75548b --- /dev/null +++ b/packages/nodes-base/nodes/HaloPSA/descriptions/index.ts @@ -0,0 +1,15 @@ +import { clientFields, clientOperations } from './ClientDescription'; +import { siteFields, siteOperations } from './SiteDescription'; +import { ticketFields, ticketOperations } from './TicketDescription'; +import { userFields, userOperations } from './UserDescription'; + +export { + clientFields, + clientOperations, + siteFields, + siteOperations, + ticketFields, + ticketOperations, + userFields, + userOperations +}; diff --git a/packages/nodes-base/nodes/HaloPSA/halopsa.svg b/packages/nodes-base/nodes/HaloPSA/halopsa.svg new file mode 100644 index 00000000000..7419d179fef --- /dev/null +++ b/packages/nodes-base/nodes/HaloPSA/halopsa.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 76b38eb4904..5cede55b07b 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -133,6 +133,7 @@ "dist/credentials/GrafanaApi.credentials.js", "dist/credentials/GSuiteAdminOAuth2Api.credentials.js", "dist/credentials/GumroadApi.credentials.js", + "dist/credentials/HaloPSAApi.credentials.js", "dist/credentials/HarvestApi.credentials.js", "dist/credentials/HarvestOAuth2Api.credentials.js", "dist/credentials/HelpScoutOAuth2Api.credentials.js", @@ -458,6 +459,7 @@ "dist/nodes/Grist/Grist.node.js", "dist/nodes/Gumroad/GumroadTrigger.node.js", "dist/nodes/HackerNews/HackerNews.node.js", + "dist/nodes/HaloPSA/HaloPSA.node.js", "dist/nodes/Harvest/Harvest.node.js", "dist/nodes/HelpScout/HelpScout.node.js", "dist/nodes/HelpScout/HelpScoutTrigger.node.js",