From bb2ba588f55f6346ca6e65ca9f1b7d0f7d07b13f Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 27 Nov 2019 17:42:28 -0500 Subject: [PATCH] :sparkles: issue:create done --- .../credentials/JiraApi.credentials.ts | 17 +- .../nodes-base/nodes/Jira/GenericFunctions.ts | 10 +- .../nodes-base/nodes/Jira/IssueDescription.ts | 191 ++++++++++++++++++ packages/nodes-base/nodes/Jira/Jira.node.ts | 190 ++++++++++++++++- packages/nodes-base/nodes/Jira/jira.png | Bin 2343 -> 3886 bytes 5 files changed, 400 insertions(+), 8 deletions(-) create mode 100644 packages/nodes-base/nodes/Jira/IssueDescription.ts diff --git a/packages/nodes-base/credentials/JiraApi.credentials.ts b/packages/nodes-base/credentials/JiraApi.credentials.ts index ddb1996454..e6230c6fc0 100644 --- a/packages/nodes-base/credentials/JiraApi.credentials.ts +++ b/packages/nodes-base/credentials/JiraApi.credentials.ts @@ -3,14 +3,25 @@ import { NodePropertyTypes, } from 'n8n-workflow'; - export class JiraApi implements ICredentialType { name = 'jiraApi'; displayName = 'Jira API'; properties = [ { - displayName: 'API Key', - name: 'apiKey', + displayName: 'Email', + name: 'email', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'API Token', + name: 'apiToken', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Domain', + name: 'domain', type: 'string' as NodePropertyTypes, default: '', }, diff --git a/packages/nodes-base/nodes/Jira/GenericFunctions.ts b/packages/nodes-base/nodes/Jira/GenericFunctions.ts index f52062f8ae..8e71e1aa1b 100644 --- a/packages/nodes-base/nodes/Jira/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Jira/GenericFunctions.ts @@ -4,7 +4,8 @@ import { IExecuteFunctions, IHookFunctions, ILoadOptionsFunctions, - IExecuteSingleFunctions + IExecuteSingleFunctions, + BINARY_ENCODING } from 'n8n-core'; import { @@ -16,15 +17,16 @@ export async function jiraApiRequest(this: IHookFunctions | IExecuteFunctions | if (credentials === undefined) { throw new Error('No credentials got returned!'); } - + const data = Buffer.from(`${credentials!.email}:${credentials!.apiToken}`).toString(BINARY_ENCODING); + console.log(data) const headerWithAuthentication = Object.assign({}, - { Authorization: `Bearer ${credentials.apiKey}`, Accept: 'application/json' }); + { Authorization: `Basic ${data}`, Accept: 'application/json', 'Content-Type': 'application/json' }); const options: OptionsWithUri = { headers: headerWithAuthentication, method, qs: query, - uri: uri || `https://api.intercom.io${endpoint}`, + uri: uri || `${credentials.domain}/rest/api/2${endpoint}`, body, json: true }; diff --git a/packages/nodes-base/nodes/Jira/IssueDescription.ts b/packages/nodes-base/nodes/Jira/IssueDescription.ts new file mode 100644 index 0000000000..fd58d07b2a --- /dev/null +++ b/packages/nodes-base/nodes/Jira/IssueDescription.ts @@ -0,0 +1,191 @@ +import { INodeProperties } from "n8n-workflow"; + +export const issueOpeations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'issue', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new issue', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const issueFields = [ + +/* -------------------------------------------------------------------------- */ +/* issue:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Project', + name: 'project', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'create' + ] + }, + }, + typeOptions: { + loadOptionsMethod: 'getProjects', + }, + description: 'Project', + }, + { + displayName: 'Issue Type', + name: 'issueType', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'create' + ] + }, + }, + typeOptions: { + loadOptionsMethod: 'getIssueTypes', + }, + description: 'Issue Types', + }, + { + displayName: 'Summary', + name: 'summary', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'create', + ], + }, + }, + default: '', + description: 'Summary', + }, + { + displayName: 'Parent Issue Identifier', + name: 'parentIssueId', + type: 'options', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'create', + ], + }, + }, + default: 'id', + options: [ + { + name: 'ID', + value: 'id', + description: 'Issue ID', + }, + { + name: 'Key', + value: 'key', + description: 'Issue Key', + + } + ], + description: 'Parent Issue Identifier', + }, + { + displayName: 'Parent Issue Identifier Value', + name: 'parentIssueIdValue', + type: 'string', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'create', + ], + }, + }, + default: '', + description: 'Parent Issue ID/Key valie', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Labels', + name: 'labels', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getLabels', + }, + default: [], + required : false, + description: 'Labels', + }, + { + displayName: 'Priority', + name: 'priority', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getPriorities', + }, + default: [], + required : false, + description: 'Priority', + }, + { + displayName: 'Assignee', + name: 'assignee', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + default: [], + required : false, + description: 'Assignee', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Jira/Jira.node.ts b/packages/nodes-base/nodes/Jira/Jira.node.ts index df38733e83..aac2b21d31 100644 --- a/packages/nodes-base/nodes/Jira/Jira.node.ts +++ b/packages/nodes-base/nodes/Jira/Jira.node.ts @@ -14,6 +14,10 @@ import { jiraApiRequestAllItems, validateJSON, } from './GenericFunctions'; +import { + issueOpeations, + issueFields, +} from './IssueDescription'; export class Jira implements INodeType { description: INodeTypeDescription = { @@ -51,10 +55,194 @@ export class Jira implements INodeType { default: 'issue', description: 'Resource to consume.', }, + ...issueOpeations, + ...issueFields, ], }; + methods = { + loadOptions: { + // Get all the projects to display them to user so that he can + // select them easily + async getProjects(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let projects; + try { + projects = await jiraApiRequest.call(this, '/project/search', 'GET'); + } catch (err) { + throw new Error(`Jira Error: ${err}`); + } + for (const project of projects.values) { + const projectName = project.name; + const projectId = project.id; + + returnData.push({ + name: projectName, + value: projectId, + }); + } + return returnData; + }, + + // Get all the issue types to display them to user so that he can + // select them easily + async getIssueTypes(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let issueTypes; + try { + issueTypes = await jiraApiRequest.call(this, '/issuetype', 'GET'); + } catch (err) { + throw new Error(`Jira Error: ${err}`); + } + for (const issueType of issueTypes) { + const issueTypeName = issueType.name; + const issueTypeId = issueType.id; + + returnData.push({ + name: issueTypeName, + value: issueTypeId, + }); + } + return returnData; + }, + + // Get all the labels to display them to user so that he can + // select them easily + async getLabels(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let labels; + try { + labels = await jiraApiRequest.call(this, '/label', 'GET'); + } catch (err) { + throw new Error(`Jira Error: ${err}`); + } + for (const label of labels.values) { + const labelName = label; + const labelId = label; + + returnData.push({ + name: labelName, + value: labelId, + }); + } + return returnData; + }, + + // Get all the priorities to display them to user so that he can + // select them easily + async getPriorities(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let priorities; + try { + priorities = await jiraApiRequest.call(this, '/priority', 'GET'); + } catch (err) { + throw new Error(`Jira Error: ${err}`); + } + for (const priority of priorities) { + const priorityName = priority.name; + const priorityId = priority.id; + + returnData.push({ + name: priorityName, + value: priorityId, + }); + } + return returnData; + }, + + // Get all the users to display them to user so that he can + // select them easily + async getUsers(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let users; + try { + users = await jiraApiRequest.call(this, '/users/search', 'GET'); + } catch (err) { + throw new Error(`Jira Error: ${err}`); + } + for (const user of users) { + const userName = user.displayName; + const userId = user.accountId; + + returnData.push({ + name: userName, + value: userId, + }); + } + return returnData; + }, + } + }; + async execute(this: IExecuteFunctions): Promise { - return [this.helpers.returnJsonArray({})]; + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + let responseData; + let qs: IDataObject = {}; + for (let i = 0; i < length; i++) { + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + if (resource === 'issue') { + if (operation === 'create') { + const summary = this.getNodeParameter('summary', i) as string; + const projectId = this.getNodeParameter('project', i) as string; + const issueTypeId = this.getNodeParameter('issueType', i) as string; + const parentIssueId = this.getNodeParameter('parentIssueId', i) as string; + const parentIssueIdValue = this.getNodeParameter('parentIssueIdValue', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body = { + fields: { + summary, + project: { + id: projectId, + }, + issuetype: { + id: issueTypeId + }, + } + }; + if (additionalFields.labels) { + body.fields.labels = additionalFields.labels as string[]; + } + if (additionalFields.priority) { + body.fields.priority = { + id: additionalFields.priority as string, + }; + } + if (additionalFields.assignee) { + body.fields.assignee = { + id: additionalFields.assignee as string, + }; + } + if (!parentIssueIdValue && issueTypeId === 'sub-task') { + throw new Error('You must define a Parent ID/Key when Issue type is sub-task'); + + } else if (parentIssueIdValue && issueTypeId === 'sub-task') { + if (parentIssueId === 'id') { + body.fields.parent = { + id: parentIssueIdValue, + }; + } + if (parentIssueId === 'key') { + body.fields.parent = { + key: parentIssueIdValue, + }; + } + } + try { + responseData = await jiraApiRequest.call(this, '/issue', 'POST', body); + } catch (err) { + throw new Error(`Jira Error: ${JSON.stringify(err)}`); + } + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + } + return [this.helpers.returnJsonArray(returnData)]; } } diff --git a/packages/nodes-base/nodes/Jira/jira.png b/packages/nodes-base/nodes/Jira/jira.png index 665ee5a217d7ee3dffcbec3bf4af49bdc3797f7c..db28f59c875a3865568b59fb2097232bf453d1f7 100644 GIT binary patch delta 3482 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 IM6N<$f^SWm5dZ)H delta 1927 zcmV;22YC3d9;Xr^iBL{Q4GJ0x0000DNk~Le0000y0000j2nGNE0P=PSOtB%j0e=TL zNklUrMADGtn>+d}~Tq z%Th!%QVTIDa}D~jL?MYF3zsM{*O!nG@h$OX+nt&Fa!%jp+_}^4u!}f5a}D*t%$@ss z&gXoew{u5mt!*Mw_}mT6A^?lKfq#@4nbo>mU}MgE!0}AnsoUG5@MLX|PiuKwCN=S( z)fu$eWQfGL4QrCX4e4Vp(>=1vpIG0dXYudGIojcE5-nNa$q}ra-uleQcXHwA4O-VV z=@|^#xGfoI22RF@8FpWTM-vV`J7{3vAKhx#3NjQ5mv*z#EpvsR6m;c*^M9~aGmk$< z{c3-x9e5wtbpfgXp>mMdrJYcEc2dxl1&(2793P);Z$Bhc)g&DmC{0obV_k}&q4Z~? zoAhf*Kvxbp4=bnsHX)bId{3xgZJ`4aLXs|oDDGb=4|=<_qkgS^z0%N?0nWqrpZ(|a z;5(=Z<+@fusFc(S>y)n5JAVp#oCP#Fv>niM4RnQ~ciDj(($ES4>iz{6Cu!*%kgiKo z+JUew8Dw)5dO3&;UG;x5V=scv0otWc>elLcE!z>u40L%HH)RE8*kftojad563zp;T zmyScywFAdZNeAFCUTVK~mEgU+M(aXspxF+xFn*UlhELCrpsB1CS%2ApW7xXY`bAk) z^&JSwab)N?3=43Euc@KS7cVKVJtr(%2oQ$=?c8`g46SsEw$!iI3m7^oL(8LdS%72M zQB8V+w&+{w*nqVIn6qtQ*{tz#t5Ql>(z@{18Koh%C5c}?1f7ZT+l+~!shT!p0v8@i zi=c51E8TnOUIWLlkAF4ki3mmq*+20K0cgxvx+#K5GqAE9$Exl>ggV+X(-Es1b)d7@ zvdQ{AL0dJnscwy46e-OU)H`Uwf zB8pw1>s>wU^{TXFzhYSSSp?ku+z0r0AcY_!EXyeCR@GQ;-@)WqYNmmmbHriGXk8_a zN=4%^MeUYjV7oW(3@K4f8&HNWHn3P;_P})0!=)*lpXE_&^jMhj&nB;SQnpjCoPq_E z_Jdq7?aZ3g*M9_9+CIjZFxStVb1`XkFw_IWrV&t@RV{Wk7!YY$O}6Tw*(X+tojl>} z+nxHl>CciC9{-0y1PMK0*PtJ5e6YJ&H#LVPtTwx^dzeSmFs!jqcC?(2Qqzgw>9h{j zmHbksJNL2>7d!!j$HY9$&uM!w8Uz2nl~$U<+yk4{K7SEHAOv?{QnH0B%rliTwS%@$6c1!*<$xa)p>0 z9p~V6Yq7_~tKI}HN+SuSg~DoQxm87L!Yq7P8*?m%4gf6~8g4zVb(uKHgC6q6r9Mgo zhn+ry8-JwA_%fApIDJ{Ts8jPWp$yx6a;1359Ots7gg{%eNf!o_1|IzR#B$LD*kyK= z$*b&x#b(K{KA;CErH?eM(~shsR3>xnh_9Un@5W`%_kG7l*@P=I00boN|p>-CPnc_Jl4 z550v{u)@eEuEApObl=y+Di@SkJln$^XXMGGUHyt@=$wk(gk3l52Y#OQ~~q;5U+2R zWh0tLsR6FaUiqSZ z*QjZxQ%-qhhUMp00N4zdTDFrhZ$NLxQf~pE z+flfPVJqAWYf`|>68~|Aw~~uSrd+*G3w#N{a$ku98^B_?bh_fo#uIycfVp?$t8Bu_ zxYO+ijz^I2mK9FSoDyd1vAcV~Jx