From 1c9a78e6249539a4e5abe6429be08f59d32ed230 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Tue, 26 Nov 2019 15:38:38 -0500 Subject: [PATCH 1/8] :tada: node-setup --- .../credentials/JiraApi.credentials.ts | 18 ++++ .../nodes-base/nodes/Jira/GenericFunctions.ts | 82 ++++++++++++++++++ packages/nodes-base/nodes/Jira/Jira.node.ts | 60 +++++++++++++ packages/nodes-base/nodes/Jira/jira.png | Bin 0 -> 2343 bytes packages/nodes-base/package.json | 6 +- 5 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 packages/nodes-base/credentials/JiraApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Jira/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Jira/Jira.node.ts create mode 100644 packages/nodes-base/nodes/Jira/jira.png diff --git a/packages/nodes-base/credentials/JiraApi.credentials.ts b/packages/nodes-base/credentials/JiraApi.credentials.ts new file mode 100644 index 00000000000..ddb1996454c --- /dev/null +++ b/packages/nodes-base/credentials/JiraApi.credentials.ts @@ -0,0 +1,18 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class JiraApi implements ICredentialType { + name = 'jiraApi'; + displayName = 'Jira API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Jira/GenericFunctions.ts b/packages/nodes-base/nodes/Jira/GenericFunctions.ts new file mode 100644 index 00000000000..f52062f8ae5 --- /dev/null +++ b/packages/nodes-base/nodes/Jira/GenericFunctions.ts @@ -0,0 +1,82 @@ +import { OptionsWithUri } from 'request'; + +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IExecuteSingleFunctions +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +export async function jiraApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, query?: IDataObject, uri?: string): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('jiraApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const headerWithAuthentication = Object.assign({}, + { Authorization: `Bearer ${credentials.apiKey}`, Accept: 'application/json' }); + + const options: OptionsWithUri = { + headers: headerWithAuthentication, + method, + qs: query, + uri: uri || `https://api.intercom.io${endpoint}`, + body, + json: true + }; + + try { + return await this.helpers.request!(options); + } catch (error) { + const errorMessage = error.response.body.message || error.response.body.Message; + + if (errorMessage !== undefined) { + throw errorMessage; + } + throw error.response.body; + } +} + + + +/** + * Make an API request to paginated intercom endpoint + * and return all results + */ +export async function jiraApiRequestAllItems(this: IHookFunctions | IExecuteFunctions, propertyName: string, endpoint: string, method: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + + query.per_page = 60; + + let uri: string | undefined; + + do { + responseData = await jiraApiRequest.call(this, endpoint, method, body, query, uri); + uri = responseData.pages.next; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData.pages !== undefined && + responseData.pages.next !== undefined && + responseData.pages.next !== null + ); + + return returnData; +} + + +export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any + let result; + try { + result = JSON.parse(json!); + } catch (exception) { + result = ''; + } + return result; +} diff --git a/packages/nodes-base/nodes/Jira/Jira.node.ts b/packages/nodes-base/nodes/Jira/Jira.node.ts new file mode 100644 index 00000000000..df38733e83a --- /dev/null +++ b/packages/nodes-base/nodes/Jira/Jira.node.ts @@ -0,0 +1,60 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeTypeDescription, + INodeExecutionData, + INodeType, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; +import { + jiraApiRequest, + jiraApiRequestAllItems, + validateJSON, +} from './GenericFunctions'; + +export class Jira implements INodeType { + description: INodeTypeDescription = { + displayName: 'Jira', + name: 'Jira', + icon: 'file:jira.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Jira API', + defaults: { + name: 'Jira', + color: '#c02428', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'jiraApi', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Issue', + value: 'issue', + description: 'Creates an issue or, where the option to create subtasks is enabled in Jira, a subtask', + }, + ], + default: 'issue', + description: 'Resource to consume.', + }, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + return [this.helpers.returnJsonArray({})]; + } +} diff --git a/packages/nodes-base/nodes/Jira/jira.png b/packages/nodes-base/nodes/Jira/jira.png new file mode 100644 index 0000000000000000000000000000000000000000..665ee5a217d7ee3dffcbec3bf4af49bdc3797f7c GIT binary patch literal 2343 zcmY*bdpy%^AO0;QbBNNT5tbgsQ#&AGwmB!tX{9zIGsDb`ZOo~12rY#UBJr{`5jiD^ zJV|9aM8*tLl!TBZDi8He_4dB+{rTL7?{#0_>-v80ziy7Jv%Ml%9Si_~qQgF0oMcB! zCT!hW$@(#3-2ura9bn~T1prm)^2d5;ix^p}> z|1Q%$@D@!6_hm@&nGTTJ5Pu1BMOg3YL+XranmaI5A{G~Z@S~->P@_Dz8_yL8AvFVj zrytiTQZF!!`IF@L?Q3+!n=rgSSB1i-_R0U^6+!nx?N%^X*vH1B)ccXL*`<0-R#hQ`Y zqHxenfXm1b@$y`wBXG|%WzvWNTeS0Obc;U^3jfrE=sDrmk-W}uxt{_GuYRIBSRPR_ zUBC0jw>?|4p2fNp`_Q;j8>}zDPwBlsroTSAp{DS}trLxc=b50+M~nF@zot zImnJ6y@MV^WR5i6-#cX;Tri+ijXaTsO?K%@FDNP7L>P!8_16oR>lAoj$NMXlv9A2p-8mCE!LUZ-iki?&8WX@+6!3(Nv4}qbVBWf=VX7} z9jWZwG@pU8xv)~4BVJJdK?LD#t<~im!%F9I=EP??+>)F0 z9{A!H3DQF=L$Y5Yp-IqfyV~`p4h!)i!w=u!gNowwn2(9=fnK_4j&`F+yP3-*p9Ygz z1${Rs4cBD&o=Uv;ViT+95l=rif6R^g37<_TpNt<*$z!-O~N!h|Jc~>BJ}gt(DjUSv_9*?y{cr z*Exr_GiOd$Wi&nz?H!S-KLL^>M}zF-7A(ozRL}M zOdwvcpZn>bC>~)9jw24=b&VrFV=W$~PA#N0Md|3;tbf93Z)1Fg(9btu2KWqj^Wz+- z@e5WKyRdG`^Tp!J>++a2D6cs0H4_UMD-uz45Z90Gdl-=RO&wng%e$n1Kp|9XaKGY2 zx=4pOKk060IO^T}(KUuHkn!QJ=jp#W1e*V*TtUD5=GoN>z5X4uO<$*a2Xlg`=bt8KSPy?*<2IU1%UWD+4FA$z^tn~RoOj)@`bIZDadI+Nj=zm} zuM=Bk9V{StWT(7^_Q|Om%SDfqx8>NEfXdsvzg{~oDy9@2bdDk?kBuJ&`W3P~YA+hl zE$J;dI;dcDC^uzKLow=3{GI077dO97LS*o7K2JD|t(0#@2A~%yf9yPG zT$L|$hs)3!nj2jjaTNTC6EgAJ$R2O^Iu?fZgXjOncVxQz4qTdF!)PoWXA4Y7{#Q!p zGd)=WNq?J}xKDI@X=!}F9VgnuoDxwn(S?0BZ}Y`&b*G6thZLx|rj8BY!;*AF-;if sXPS)HrV(EB)fmkLj}ef2xyVCQUGZtausZ?IM^SpWb4 literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index ae70262dc8d..6673d95d771 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -42,7 +42,8 @@ "dist/credentials/HttpDigestAuth.credentials.js", "dist/credentials/HttpHeaderAuth.credentials.js", "dist/credentials/IntercomApi.credentials.js", - "dist/credentials/Imap.credentials.js", + "dist/credentials/Imap.credentials.js", + "dist/credentials/JiraApi.credentials.js", "dist/credentials/LinkFishApi.credentials.js", "dist/credentials/MailchimpApi.credentials.js", "dist/credentials/MailgunApi.credentials.js", @@ -100,7 +101,8 @@ "dist/nodes/HttpRequest.node.js", "dist/nodes/If.node.js", "dist/nodes/Interval.node.js", - "dist/nodes/Intercom/Intercom.node.js", + "dist/nodes/Intercom/Intercom.node.js", + "dist/nodes/Jira/Jira.node.js", "dist/nodes/LinkFish/LinkFish.node.js", "dist/nodes/Mailchimp/Mailchimp.node.js", "dist/nodes/Mailgun/Mailgun.node.js", From bb2ba588f55f6346ca6e65ca9f1b7d0f7d07b13f Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 27 Nov 2019 17:42:28 -0500 Subject: [PATCH 2/8] :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 ddb1996454c..e6230c6fc02 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 f52062f8ae5..8e71e1aa1b4 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 00000000000..fd58d07b2a7 --- /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 df38733e83a..aac2b21d31c 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 Date: Thu, 28 Nov 2019 01:10:41 -0500 Subject: [PATCH 3/8] :sparkles: Renamed and added interfaces --- ...ts => JiraSoftwareCloudApi.credentials.ts} | 6 +- .../nodes-base/nodes/Jira/GenericFunctions.ts | 9 +- .../nodes-base/nodes/Jira/IssueDescription.ts | 41 +++++----- .../nodes-base/nodes/Jira/IssueInterface.ts | 16 ++++ ...Jira.node.ts => JiraSoftwareCloud.node.ts} | 82 ++++++++++--------- packages/nodes-base/package.json | 4 +- 6 files changed, 86 insertions(+), 72 deletions(-) rename packages/nodes-base/credentials/{JiraApi.credentials.ts => JiraSoftwareCloudApi.credentials.ts} (75%) create mode 100644 packages/nodes-base/nodes/Jira/IssueInterface.ts rename packages/nodes-base/nodes/Jira/{Jira.node.ts => JiraSoftwareCloud.node.ts} (74%) diff --git a/packages/nodes-base/credentials/JiraApi.credentials.ts b/packages/nodes-base/credentials/JiraSoftwareCloudApi.credentials.ts similarity index 75% rename from packages/nodes-base/credentials/JiraApi.credentials.ts rename to packages/nodes-base/credentials/JiraSoftwareCloudApi.credentials.ts index e6230c6fc02..b1c54bfd540 100644 --- a/packages/nodes-base/credentials/JiraApi.credentials.ts +++ b/packages/nodes-base/credentials/JiraSoftwareCloudApi.credentials.ts @@ -3,9 +3,9 @@ import { NodePropertyTypes, } from 'n8n-workflow'; -export class JiraApi implements ICredentialType { - name = 'jiraApi'; - displayName = 'Jira API'; +export class JiraSoftwareCloudApi implements ICredentialType { + name = 'jiraSoftwareCloudApi'; + displayName = 'Jira Software Cloud API'; properties = [ { displayName: 'Email', diff --git a/packages/nodes-base/nodes/Jira/GenericFunctions.ts b/packages/nodes-base/nodes/Jira/GenericFunctions.ts index 8e71e1aa1b4..61a6f4a0086 100644 --- a/packages/nodes-base/nodes/Jira/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Jira/GenericFunctions.ts @@ -12,13 +12,12 @@ import { IDataObject, } from 'n8n-workflow'; -export async function jiraApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, query?: IDataObject, uri?: string): Promise { // tslint:disable-line:no-any - const credentials = this.getCredentials('jiraApi'); +export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, query?: IDataObject, uri?: string): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('jiraSoftwareCloudApi'); 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: `Basic ${data}`, Accept: 'application/json', 'Content-Type': 'application/json' }); @@ -49,7 +48,7 @@ export async function jiraApiRequest(this: IHookFunctions | IExecuteFunctions | * Make an API request to paginated intercom endpoint * and return all results */ -export async function jiraApiRequestAllItems(this: IHookFunctions | IExecuteFunctions, propertyName: string, endpoint: string, method: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any +export async function jiraSoftwareCloudApiRequestAllItems(this: IHookFunctions | IExecuteFunctions, propertyName: string, endpoint: string, method: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any const returnData: IDataObject[] = []; @@ -60,7 +59,7 @@ export async function jiraApiRequestAllItems(this: IHookFunctions | IExecuteFunc let uri: string | undefined; do { - responseData = await jiraApiRequest.call(this, endpoint, method, body, query, uri); + responseData = await jiraSoftwareCloudApiRequest.call(this, endpoint, method, body, query, uri); uri = responseData.pages.next; returnData.push.apply(returnData, responseData[propertyName]); } while ( diff --git a/packages/nodes-base/nodes/Jira/IssueDescription.ts b/packages/nodes-base/nodes/Jira/IssueDescription.ts index fd58d07b2a7..13677f04b88 100644 --- a/packages/nodes-base/nodes/Jira/IssueDescription.ts +++ b/packages/nodes-base/nodes/Jira/IssueDescription.ts @@ -88,10 +88,9 @@ export const issueFields = [ description: 'Summary', }, { - displayName: 'Parent Issue Identifier', - name: 'parentIssueId', - type: 'options', - required: false, + displayName: 'Has Parent Issue?', + name: 'hasParentIssue', + type: 'boolean', displayOptions: { show: { resource: [ @@ -102,25 +101,12 @@ export const issueFields = [ ], }, }, - default: 'id', - options: [ - { - name: 'ID', - value: 'id', - description: 'Issue ID', - }, - { - name: 'Key', - value: 'key', - description: 'Issue Key', - - } - ], - description: 'Parent Issue Identifier', + default: false, + description: 'Weather The Issue Has A Parent Issue ID/Key or Not', }, { - displayName: 'Parent Issue Identifier Value', - name: 'parentIssueIdValue', + displayName: 'Parent Issue Key', + name: 'parentIssueKey', type: 'string', required: false, displayOptions: { @@ -131,10 +117,13 @@ export const issueFields = [ operation: [ 'create', ], + hasParentIssue: [ + true, + ], }, }, default: '', - description: 'Parent Issue ID/Key valie', + description: 'Parent Issue Key', }, { displayName: 'Additional Fields', @@ -186,6 +175,14 @@ export const issueFields = [ required : false, description: 'Assignee', }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + required : false, + description: 'Description', + }, ], }, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Jira/IssueInterface.ts b/packages/nodes-base/nodes/Jira/IssueInterface.ts new file mode 100644 index 00000000000..b0c16d1eb0a --- /dev/null +++ b/packages/nodes-base/nodes/Jira/IssueInterface.ts @@ -0,0 +1,16 @@ +import { IDataObject } from "n8n-workflow"; + +export interface IFields { + summary: string; + project?: IDataObject; + issuetype?: IDataObject; + labels?: string[]; + priority?: IDataObject; + assignee?: IDataObject; + description?: string; + parent?: IDataObject; +} + +export interface IIssue { + fields?: IFields; +} diff --git a/packages/nodes-base/nodes/Jira/Jira.node.ts b/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts similarity index 74% rename from packages/nodes-base/nodes/Jira/Jira.node.ts rename to packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts index aac2b21d31c..fe094c01bbb 100644 --- a/packages/nodes-base/nodes/Jira/Jira.node.ts +++ b/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts @@ -10,33 +10,37 @@ import { INodePropertyOptions, } from 'n8n-workflow'; import { - jiraApiRequest, - jiraApiRequestAllItems, + jiraSoftwareCloudApiRequest, + jiraSoftwareCloudApiRequestAllItems, validateJSON, } from './GenericFunctions'; import { issueOpeations, issueFields, } from './IssueDescription'; +import { + IIssue, + IFields, + } from './IssueInterface'; -export class Jira implements INodeType { +export class JiraSoftwareCloud implements INodeType { description: INodeTypeDescription = { - displayName: 'Jira', - name: 'Jira', + displayName: 'Jira Software Cloud', + name: 'Jira Software Cloud', icon: 'file:jira.png', group: ['output'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', - description: 'Consume Jira API', + description: 'Consume Jira Software Cloud API', defaults: { - name: 'Jira', + name: 'Jira Software Cloud', color: '#c02428', }, inputs: ['main'], outputs: ['main'], credentials: [ { - name: 'jiraApi', + name: 'jiraSoftwareCloudApi', required: true, } ], @@ -68,7 +72,7 @@ export class Jira implements INodeType { const returnData: INodePropertyOptions[] = []; let projects; try { - projects = await jiraApiRequest.call(this, '/project/search', 'GET'); + projects = await jiraSoftwareCloudApiRequest.call(this, '/project/search', 'GET'); } catch (err) { throw new Error(`Jira Error: ${err}`); } @@ -90,7 +94,7 @@ export class Jira implements INodeType { const returnData: INodePropertyOptions[] = []; let issueTypes; try { - issueTypes = await jiraApiRequest.call(this, '/issuetype', 'GET'); + issueTypes = await jiraSoftwareCloudApiRequest.call(this, '/issuetype', 'GET'); } catch (err) { throw new Error(`Jira Error: ${err}`); } @@ -112,7 +116,7 @@ export class Jira implements INodeType { const returnData: INodePropertyOptions[] = []; let labels; try { - labels = await jiraApiRequest.call(this, '/label', 'GET'); + labels = await jiraSoftwareCloudApiRequest.call(this, '/label', 'GET'); } catch (err) { throw new Error(`Jira Error: ${err}`); } @@ -134,7 +138,7 @@ export class Jira implements INodeType { const returnData: INodePropertyOptions[] = []; let priorities; try { - priorities = await jiraApiRequest.call(this, '/priority', 'GET'); + priorities = await jiraSoftwareCloudApiRequest.call(this, '/priority', 'GET'); } catch (err) { throw new Error(`Jira Error: ${err}`); } @@ -156,7 +160,7 @@ export class Jira implements INodeType { const returnData: INodePropertyOptions[] = []; let users; try { - users = await jiraApiRequest.call(this, '/users/search', 'GET'); + users = await jiraSoftwareCloudApiRequest.call(this, '/users/search', 'GET'); } catch (err) { throw new Error(`Jira Error: ${err}`); } @@ -188,50 +192,48 @@ export class Jira implements INodeType { 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 hasParentIssue = this.getNodeParameter('hasParentIssue', i) as boolean; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - const body = { - fields: { - summary, - project: { - id: projectId, - }, - issuetype: { - id: issueTypeId - }, - } + const body: IIssue = {}; + const fields: IFields = { + summary, + project: { + id: projectId, + }, + issuetype: { + id: issueTypeId, + }, }; if (additionalFields.labels) { - body.fields.labels = additionalFields.labels as string[]; + fields.labels = additionalFields.labels as string[]; } if (additionalFields.priority) { - body.fields.priority = { + fields.priority = { id: additionalFields.priority as string, }; } if (additionalFields.assignee) { - body.fields.assignee = { + 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'); + if (additionalFields.description) { + fields.description = additionalFields.description as string; + } + if (hasParentIssue) { + const parentIssueKey = this.getNodeParameter('parentIssueKey', i) as string; + if (!parentIssueKey && issueTypeId === 'sub-task') { + throw new Error('You must define a Parent Issue 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, + } else if (parentIssueKey && issueTypeId === 'sub-task') { + fields.parent = { + key: parentIssueKey, }; } } + body.fields = fields; try { - responseData = await jiraApiRequest.call(this, '/issue', 'POST', body); + responseData = await jiraSoftwareCloudApiRequest.call(this, '/issue', 'POST', body); } catch (err) { throw new Error(`Jira Error: ${JSON.stringify(err)}`); } diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 6673d95d771..a82727dbb0c 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -43,7 +43,7 @@ "dist/credentials/HttpHeaderAuth.credentials.js", "dist/credentials/IntercomApi.credentials.js", "dist/credentials/Imap.credentials.js", - "dist/credentials/JiraApi.credentials.js", + "dist/credentials/JiraSoftwareCloudApi.credentials.js", "dist/credentials/LinkFishApi.credentials.js", "dist/credentials/MailchimpApi.credentials.js", "dist/credentials/MailgunApi.credentials.js", @@ -102,7 +102,7 @@ "dist/nodes/If.node.js", "dist/nodes/Interval.node.js", "dist/nodes/Intercom/Intercom.node.js", - "dist/nodes/Jira/Jira.node.js", + "dist/nodes/Jira/JiraSoftwareCloud.node.js", "dist/nodes/LinkFish/LinkFish.node.js", "dist/nodes/Mailchimp/Mailchimp.node.js", "dist/nodes/Mailgun/Mailgun.node.js", From 9def6cd6ae4331bd098c9f7890989bedb352db19 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 29 Nov 2019 17:30:00 -0500 Subject: [PATCH 4/8] :sparkles: Issue resource done --- .../nodes-base/nodes/Jira/GenericFunctions.ts | 14 +- .../nodes-base/nodes/Jira/IssueDescription.ts | 786 +++++++++++++++++- .../nodes-base/nodes/Jira/IssueInterface.ts | 21 + .../nodes/Jira/JiraSoftwareCloud.node.ts | 249 +++++- 4 files changed, 1032 insertions(+), 38 deletions(-) diff --git a/packages/nodes-base/nodes/Jira/GenericFunctions.ts b/packages/nodes-base/nodes/Jira/GenericFunctions.ts index 61a6f4a0086..b6c6b116740 100644 --- a/packages/nodes-base/nodes/Jira/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Jira/GenericFunctions.ts @@ -20,7 +20,6 @@ export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecut const data = Buffer.from(`${credentials!.email}:${credentials!.apiToken}`).toString(BINARY_ENCODING); const headerWithAuthentication = Object.assign({}, { Authorization: `Basic ${data}`, Accept: 'application/json', 'Content-Type': 'application/json' }); - const options: OptionsWithUri = { headers: headerWithAuthentication, method, @@ -33,7 +32,8 @@ export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecut try { return await this.helpers.request!(options); } catch (error) { - const errorMessage = error.response.body.message || error.response.body.Message; + const errorMessage = + error.response.body.message || error.response.body.Message; if (errorMessage !== undefined) { throw errorMessage; @@ -54,18 +54,18 @@ export async function jiraSoftwareCloudApiRequestAllItems(this: IHookFunctions | let responseData; - query.per_page = 60; + query.maxResults = 100; let uri: string | undefined; do { responseData = await jiraSoftwareCloudApiRequest.call(this, endpoint, method, body, query, uri); - uri = responseData.pages.next; + uri = responseData.nextPage; returnData.push.apply(returnData, responseData[propertyName]); } while ( - responseData.pages !== undefined && - responseData.pages.next !== undefined && - responseData.pages.next !== null + responseData.isLast !== false && + responseData.nextPage !== undefined && + responseData.nextPage !== null ); return returnData; diff --git a/packages/nodes-base/nodes/Jira/IssueDescription.ts b/packages/nodes-base/nodes/Jira/IssueDescription.ts index 13677f04b88..47d0a9f6108 100644 --- a/packages/nodes-base/nodes/Jira/IssueDescription.ts +++ b/packages/nodes-base/nodes/Jira/IssueDescription.ts @@ -18,6 +18,36 @@ export const issueOpeations = [ value: 'create', description: 'Create a new issue', }, + { + name: 'Update', + value: 'update', + description: 'Update an issue', + }, + { + name: 'Get', + value: 'get', + description: 'Get an issue', + }, + { + name: 'Changelog', + value: 'changelog', + description: 'Get issue changelog', + }, + { + name: 'Notify', + value: 'notify', + description: 'Creates an email notification for an issue and adds it to the mail queue.', + }, + { + name: 'Transitions', + value: 'transitions', + description: `Returns either all transitions or a transition that can be performed by the user on an issue, based on the issue's status.`, + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete an issue', + }, ], default: 'create', description: 'The operation to perform.', @@ -87,23 +117,6 @@ export const issueFields = [ default: '', description: 'Summary', }, - { - displayName: 'Has Parent Issue?', - name: 'hasParentIssue', - type: 'boolean', - displayOptions: { - show: { - resource: [ - 'issue', - ], - operation: [ - 'create', - ], - }, - }, - default: false, - description: 'Weather The Issue Has A Parent Issue ID/Key or Not', - }, { displayName: 'Parent Issue Key', name: 'parentIssueKey', @@ -117,9 +130,6 @@ export const issueFields = [ operation: [ 'create', ], - hasParentIssue: [ - true, - ], }, }, default: '', @@ -183,6 +193,742 @@ export const issueFields = [ required : false, description: 'Description', }, + { + displayName: 'Update History', + name: 'updateHistory', + type: 'boolean', + default: false, + required : false, + description: `Whether the project in which the issue is created is added to the user's Recently viewed project list, as shown under Projects in Jira.`, + }, ], }, + +/* -------------------------------------------------------------------------- */ +/* issue:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Issue Key', + name: 'issueKey', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'update', + ], + }, + }, + default: '', + description: 'Issue Key', + }, + { + displayName: 'Issue Type', + name: 'issueType', + type: 'options', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'update' + ] + }, + }, + typeOptions: { + loadOptionsMethod: 'getIssueTypes', + }, + default: '', + description: 'Issue Types', + }, + { + displayName: 'Summary', + name: 'summary', + type: 'string', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'update', + ], + }, + }, + default: '', + description: 'Summary', + }, + { + displayName: 'Parent Issue Key', + name: 'parentIssueKey', + type: 'string', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'update', + ], + }, + }, + default: '', + description: 'Parent Issue Key', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'update', + ], + }, + }, + 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', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + required : false, + description: 'Description', + }, + ], + }, + +/* -------------------------------------------------------------------------- */ +/* issue:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Issue Key', + name: 'issueKey', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'delete', + ], + }, + }, + default: '', + description: 'Issue Key', + }, + { + displayName: 'Delete Subtasks', + name: 'deleteSubtasks', + type: 'boolean', + required: true, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'delete', + ], + }, + }, + default: false, + description: 'Delete Subtasks', + }, + +/* -------------------------------------------------------------------------- */ +/* issue:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Issue Key', + name: 'issueKey', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'get', + ], + }, + }, + default: '', + description: 'Issue Key', + }, + { + displayName: 'Fields', + name: 'fields', + type: 'string', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'get', + ], + }, + }, + default: '', + description: `A list of fields to return for the issue. This parameter accepts a comma-separated list. Use it to retrieve a subset of fields. Allowed values: + *all Returns all fields. + *navigable Returns navigable fields. + Any issue field, prefixed with a minus to exclude.` + }, + { + displayName: 'Fields By Key', + name: 'fieldsByKey', + type: 'boolean', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'get', + ], + }, + }, + default: false, + description: `Indicates whether fields in fields are referenced by keys rather than IDs. This parameter is useful where fields have been added by a connect app and a field's key may differ from its ID.`, + }, + { + displayName: 'Expand', + name: 'expand', + type: 'string', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'get', + ], + }, + }, + default: '', + description: `Use expand to include additional information about the issues in the response. This parameter accepts a comma-separated list. Expand options include: + renderedFields Returns field values rendered in HTML format. + names Returns the display name of each field. + schema Returns the schema describing a field type. + transitions Returns all possible transitions for the issue. + editmeta Returns information about how each field can be edited. + changelog Returns a list of recent updates to an issue, sorted by date, starting from the most recent. + versionedRepresentations Returns a JSON array for each version of a field's value, with the highest number representing the most recent version. Note: When included in the request, the fields parameter is ignored.` + }, + { + displayName: 'Properties', + name: 'properties', + type: 'string', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'get', + ], + }, + }, + default: '', + description: `A list of issue properties to return for the issue. This parameter accepts a comma-separated list. Allowed values: + *all Returns all issue properties. + Any issue property key, prefixed with a minus to exclude. + Examples: + *all Returns all properties. + *all,-prop1 Returns all properties except prop1. + prop1,prop2 Returns prop1 and prop2 properties. + This parameter may be specified multiple times. For example, properties=prop1,prop2& properties=prop3.` + }, + { + displayName: 'Update History', + name: 'updateHistory', + type: 'boolean', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'get', + ], + }, + }, + default: false, + description: `Whether the project in which the issue is created is added to the user's Recently viewed project list, as shown under Projects in Jira. This also populates the JQL issues search lastViewed field.`, + }, + +/* -------------------------------------------------------------------------- */ +/* issue:changelog */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Issue Key', + name: 'issueKey', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'changelog', + ], + }, + }, + default: '', + description: 'Issue Key', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'changelog', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'changelog', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 50, + description: 'How many results to return.', + }, + +/* -------------------------------------------------------------------------- */ +/* issue:notify */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Issue Key', + name: 'issueKey', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'notify', + ], + }, + }, + default: '', + description: 'Issue Key', + }, + { + displayName: 'Subject', + name: 'subject', + type: 'string', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'notify', + ], + }, + }, + default: '', + description: 'The subject of the email notification for the issue. If this is not specified, then the subject is set to the issue key and summary.', + }, + { + displayName: 'Text Body', + name: 'textBody', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'notify', + ], + }, + }, + default: '', + description: 'The subject of the email notification for the issue. If this is not specified, then the subject is set to the issue key and summary.', + }, + { + displayName: 'HTML Body', + name: 'htmlBody', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'notify', + ], + }, + }, + default: '', + description: 'The HTML body of the email notification for the issue.', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'notify', + ], + }, + }, + }, + { + displayName: 'Notification Recipients', + name: 'notificationRecipientsUi', + type: 'fixedCollection', + placeholder: 'Add Recipients', + typeOptions: { + multipleValues: false, + }, + description: 'The recipients of the email notification for the issue.', + default: {}, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'notify', + ], + jsonParameters: [ + false, + ], + }, + }, + options: [ + { + name: 'notificationRecipientsValues', + displayName: 'Recipients', + values: [ + { + displayName: 'Reporter', + name: 'reporter', + type: 'boolean', + description: `Indicates whether the notification should be sent to the issue's reporter.`, + default: false, + }, + { + displayName: 'Assignee', + name: 'assignee', + type: 'boolean', + default: false, + description: `Indicates whether the notification should be sent to the issue's assignees.`, + }, + { + displayName: 'Watchers', + name: 'watchers', + type: 'boolean', + default: false, + description: `Indicates whether the notification should be sent to the issue's assignees.`, + }, + { + displayName: 'Voters', + name: 'voters', + type: 'boolean', + default: false, + description: `Indicates whether the notification should be sent to the issue's voters.`, + }, + { + displayName: 'Users', + name: 'users', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + default: [], + description: `List of users to receive the notification.`, + }, + { + displayName: 'Groups', + name: 'groups', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getGroups', + }, + default: [], + description: `List of groups to receive the notification.`, + }, + ] + + } + ] + }, + { + displayName: 'Notification Recipients', + name: 'notificationRecipientsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'notify', + ], + jsonParameters: [ + true, + ] + }, + }, + default: '', + description: 'The recipients of the email notification for the issue.', + }, + { + displayName: 'Notification Recipients Restrictions', + name: 'notificationRecipientsRestrictionsUi', + type: 'fixedCollection', + placeholder: 'Add Recipients Restriction', + typeOptions: { + multipleValues: false, + }, + description: 'Restricts the notifications to users with the specified permissions.', + default: {}, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'notify', + ], + jsonParameters: [ + false, + ], + }, + }, + options: [ + { + name: 'notificationRecipientsRestrictionsValues', + displayName: 'Recipients Restrictions', + values: [ + { + displayName: 'Users', + name: 'users', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + default: [], + description: `List of users to receive the notification.`, + }, + { + displayName: 'Groups', + name: 'groups', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getGroups', + }, + default: [], + description: `List of groups to receive the notification.`, + }, + ] + + } + ] + }, + { + displayName: 'Notification Recipients Restrictions', + name: 'notificationRecipientsRestrictionsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'notify', + ], + jsonParameters: [ + true, + ] + }, + }, + default: '', + description: 'Restricts the notifications to users with the specified permissions.', + }, + +/* -------------------------------------------------------------------------- */ +/* issue:transitions */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Issue Key', + name: 'issueKey', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'transitions', + ], + }, + }, + default: '', + description: 'Issue Key', + }, + { + displayName: 'Expand', + name: 'expand', + type: 'string', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'transitions', + ], + }, + }, + default: '', + description: 'Use expand to include additional information about transitions in the response. This parameter accepts transitions.fields, which returns information about the fields in the transition screen for each transition. Fields hidden from the screen are not returned. Use this information to populate the fields and update fields in Transition issue.', + }, + { + displayName: 'Transition ID', + name: 'transitionId', + type: 'string', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'transitions', + ], + }, + }, + default: '', + description: 'The ID of the transition.', + }, + { + displayName: 'Skip Remote Only Condition', + name: 'skipRemoteOnlyCondition', + type: 'boolean', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'transitions', + ], + }, + }, + default: false, + description: 'Indicates whether transitions with the condition Hide From User Condition are included in the response.', + }, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Jira/IssueInterface.ts b/packages/nodes-base/nodes/Jira/IssueInterface.ts index b0c16d1eb0a..88b24962ce8 100644 --- a/packages/nodes-base/nodes/Jira/IssueInterface.ts +++ b/packages/nodes-base/nodes/Jira/IssueInterface.ts @@ -14,3 +14,24 @@ export interface IFields { export interface IIssue { fields?: IFields; } + +export interface INotify { + subject?: string; + textBody?: string; + htmlBody?: string; + to?: INotificationRecipients; + restrict?: NotificationRecipientsRestrictions; +} + +export interface INotificationRecipients { + reporter?: boolean; + assignee?: boolean; + watchers?: boolean; + voters?: boolean; + users?: IDataObject[]; + groups?: IDataObject[]; +} + +export interface NotificationRecipientsRestrictions { + groups?: IDataObject[]; +} diff --git a/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts b/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts index fe094c01bbb..6591be60e32 100644 --- a/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts +++ b/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts @@ -21,6 +21,9 @@ import { import { IIssue, IFields, + INotify, + INotificationRecipients, + NotificationRecipientsRestrictions, } from './IssueInterface'; export class JiraSoftwareCloud implements INodeType { @@ -175,6 +178,28 @@ export class JiraSoftwareCloud implements INodeType { } return returnData; }, + + // Get all the groups to display them to user so that he can + // select them easily + async getGroups(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let groups; + try { + groups = await jiraSoftwareCloudApiRequest.call(this, '/groups/picker', 'GET'); + } catch (err) { + throw new Error(`Jira Error: ${err}`); + } + for (const group of groups.groups) { + const groupName = group.name; + const groupId = group.name; + + returnData.push({ + name: groupName, + value: groupId, + }); + } + return returnData; + } } }; @@ -183,7 +208,7 @@ export class JiraSoftwareCloud implements INodeType { const returnData: IDataObject[] = []; const length = items.length as unknown as number; let responseData; - let qs: IDataObject = {}; + const 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; @@ -192,8 +217,8 @@ export class JiraSoftwareCloud implements INodeType { 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 hasParentIssue = this.getNodeParameter('hasParentIssue', i) as boolean; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const parentIssueKey = this.getNodeParameter('parentIssueKey', i) as string; const body: IIssue = {}; const fields: IFields = { summary, @@ -220,17 +245,24 @@ export class JiraSoftwareCloud implements INodeType { if (additionalFields.description) { fields.description = additionalFields.description as string; } - if (hasParentIssue) { - const parentIssueKey = this.getNodeParameter('parentIssueKey', i) as string; - if (!parentIssueKey && issueTypeId === 'sub-task') { - throw new Error('You must define a Parent Issue Key when Issue type is sub-task'); - - } else if (parentIssueKey && issueTypeId === 'sub-task') { - fields.parent = { - key: parentIssueKey, - }; + if (additionalFields.updateHistory) { + qs.updateHistory = additionalFields.updateHistory as boolean; + } + const issueTypes = await jiraSoftwareCloudApiRequest.call(this, '/issuetype', 'GET', body, qs); + const subtaskIssues = []; + for (const issueType of issueTypes) { + if (issueType.subtask) { + subtaskIssues.push(issueType.id); } } + if (!parentIssueKey && subtaskIssues.includes(issueTypeId)) { + throw new Error('You must define a Parent Issue Key when Issue type is sub-task'); + + } else if (parentIssueKey && subtaskIssues.includes(issueTypeId)) { + fields.parent = { + key: parentIssueKey.toUpperCase(), + }; + } body.fields = fields; try { responseData = await jiraSoftwareCloudApiRequest.call(this, '/issue', 'POST', body); @@ -238,6 +270,201 @@ export class JiraSoftwareCloud implements INodeType { throw new Error(`Jira Error: ${JSON.stringify(err)}`); } } + if (operation === 'update') { + const summary = this.getNodeParameter('summary', i) as string; + const issueTypeId = this.getNodeParameter('issueType', i) as string; + const issueKey = this.getNodeParameter('issueKey', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const parentIssueKey = this.getNodeParameter('parentIssueKey', i) as string; + const body: IIssue = {}; + const fields: IFields = { + summary, + issuetype: { + id: issueTypeId + } + }; + if (additionalFields.labels) { + fields.labels = additionalFields.labels as string[]; + } + if (additionalFields.priority) { + fields.priority = { + id: additionalFields.priority as string, + }; + } + if (additionalFields.assignee) { + fields.assignee = { + id: additionalFields.assignee as string, + }; + } + if (additionalFields.description) { + fields.description = additionalFields.description as string; + } + const issueTypes = await jiraSoftwareCloudApiRequest.call(this, '/issuetype', 'GET', body); + const subtaskIssues = []; + for (const issueType of issueTypes) { + if (issueType.subtask) { + subtaskIssues.push(issueType.id); + } + } + if (!parentIssueKey && subtaskIssues.includes(issueTypeId)) { + throw new Error('You must define a Parent Issue Key when Issue type is sub-task'); + + } else if (parentIssueKey && subtaskIssues.includes(issueTypeId)) { + fields.parent = { + key: parentIssueKey.toUpperCase(), + }; + } + body.fields = fields; + try { + responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}`, 'PUT', body); + } catch (err) { + throw new Error(`Jira Error: ${JSON.stringify(err)}`); + } + } + if (operation === 'get') { + const issueKey = this.getNodeParameter('issueKey', i) as string; + const fields = this.getNodeParameter('fields', i) as string; + const fieldsByKey = this.getNodeParameter('fieldsByKey', i) as boolean; + const expand = this.getNodeParameter('expand', i) as string; + const properties = this.getNodeParameter('properties', i) as string; + const updateHistory = this.getNodeParameter('updateHistory', i) as boolean; + qs.fields = fields; + qs.fieldsByKey = fieldsByKey; + qs.expand = expand; + qs.properties = properties; + qs.updateHistory = updateHistory; + try { + responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}`, 'GET', {}, qs); + } catch (err) { + throw new Error(`Jira Error: ${JSON.stringify(err)}`); + } + } + if (operation === 'changelog') { + const issueKey = this.getNodeParameter('issueKey', i) as string; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + try { + if (returnAll) { + responseData = await jiraSoftwareCloudApiRequestAllItems.call(this, 'values',`/issue/${issueKey}/changelog`, 'GET'); + } else { + qs.maxResults = this.getNodeParameter('limit', i) as number; + responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}/changelog`, 'GET', {}, qs); + responseData = responseData.values; + } + } catch (err) { + throw new Error(`Jira Error: ${JSON.stringify(err)}`); + } + } + if (operation === 'notify') { + const issueKey = this.getNodeParameter('issueKey', i) as string; + const textBody = this.getNodeParameter('textBody', i) as string; + const htmlBody = this.getNodeParameter('htmlBody', i) as string; + const jsonActive = this.getNodeParameter('jsonParameters', 0) as boolean; + const body: INotify = {}; + body.htmlBody = htmlBody; + body.textBody = textBody; + if (!jsonActive) { + const notificationRecipientsValues = (this.getNodeParameter('notificationRecipientsUi', i) as IDataObject).notificationRecipientsValues as IDataObject[]; + const notificationRecipients: INotificationRecipients = {}; + if (notificationRecipientsValues) { + // @ts-ignore + if (notificationRecipientsValues.reporter) { + // @ts-ignore + notificationRecipients.reporter = notificationRecipientsValues.reporter as boolean; + } + // @ts-ignore + if (notificationRecipientsValues.assignee) { + // @ts-ignore + notificationRecipients.assignee = notificationRecipientsValues.assignee as boolean; + } + // @ts-ignore + if (notificationRecipientsValues.assignee) { + // @ts-ignore + notificationRecipients.watchers = notificationRecipientsValues.watchers as boolean; + } + // @ts-ignore + if (notificationRecipientsValues.voters) { + // @ts-ignore + notificationRecipients.watchers = notificationRecipientsValues.voters as boolean; + } + // @ts-ignore + if (notificationRecipientsValues.users.length > 0) { + // @ts-ignore + notificationRecipients.users = notificationRecipientsValues.users.map(user => { + return { + accountId: user + }; + }); + } + // @ts-ignore + if (notificationRecipientsValues.groups.length > 0) { + // @ts-ignore + notificationRecipients.groups = notificationRecipientsValues.groups.map(group => { + return { + name: group + }; + }); + } + } + body.to = notificationRecipients; + const notificationRecipientsRestrictionsValues = (this.getNodeParameter('notificationRecipientsRestrictionsUi', i) as IDataObject).notificationRecipientsRestrictionsValues as IDataObject[]; + const notificationRecipientsRestrictions: NotificationRecipientsRestrictions = {}; + if (notificationRecipientsRestrictionsValues) { + // @ts-ignore + if (notificationRecipientsRestrictionsValues.groups. length > 0) { + // @ts-ignore + notificationRecipientsRestrictions.groups = notificationRecipientsRestrictionsValues.groups.map(group => { + return { + name: group + }; + }); + } + } + body.restrict = notificationRecipientsRestrictions; + } else { + const notificationRecipientsJson = validateJSON(this.getNodeParameter('notificationRecipientsJson', i) as string); + if (notificationRecipientsJson) { + body.to = notificationRecipientsJson; + } + const notificationRecipientsRestrictionsJson = validateJSON(this.getNodeParameter('notificationRecipientsRestrictionsJson', i) as string); + if (notificationRecipientsRestrictionsJson) { + body.restrict = notificationRecipientsRestrictionsJson; + } + } + try { + responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}/notify`, 'POST', body, qs); + } catch (err) { + throw new Error(`Jira Error: ${JSON.stringify(err)}`); + } + } + if (operation === 'transitions') { + const issueKey = this.getNodeParameter('issueKey', i) as string; + const transitionId = this.getNodeParameter('transitionId', i) as string; + const expand = this.getNodeParameter('expand', i) as string; + if (transitionId) { + qs.transitionId = transitionId; + } + if (expand) { + qs.expand = expand; + } + qs.skipRemoteOnlyCondition = this.getNodeParameter('skipRemoteOnlyCondition', i) as boolean; + + try { + responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}/transitions`, 'GET', {}, qs); + responseData = responseData.transitions; + } catch (err) { + throw new Error(`Jira Error: ${JSON.stringify(err)}`); + } + } + if (operation === 'delete') { + const issueKey = this.getNodeParameter('issueKey', i) as string; + const deleteSubtasks = this.getNodeParameter('deleteSubtasks', i) as boolean; + qs.deleteSubtasks = deleteSubtasks; + try { + responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}`, 'DELETE', {}, qs); + } catch (err) { + throw new Error(`Jira Error: ${JSON.stringify(err)}`); + } + } } if (Array.isArray(responseData)) { returnData.push.apply(returnData, responseData as IDataObject[]); From 1dc80e3bddb9d00c89addfc80eb3f3cc1162c785 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 29 Nov 2019 17:36:07 -0500 Subject: [PATCH 5/8] :bulb: added api links --- packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts b/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts index 6591be60e32..45561b02af7 100644 --- a/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts +++ b/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts @@ -213,6 +213,7 @@ export class JiraSoftwareCloud implements INodeType { const resource = this.getNodeParameter('resource', 0) as string; const operation = this.getNodeParameter('operation', 0) as string; if (resource === 'issue') { + //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-post if (operation === 'create') { const summary = this.getNodeParameter('summary', i) as string; const projectId = this.getNodeParameter('project', i) as string; @@ -270,6 +271,7 @@ export class JiraSoftwareCloud implements INodeType { throw new Error(`Jira Error: ${JSON.stringify(err)}`); } } + //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-put if (operation === 'update') { const summary = this.getNodeParameter('summary', i) as string; const issueTypeId = this.getNodeParameter('issueType', i) as string; @@ -321,6 +323,7 @@ export class JiraSoftwareCloud implements INodeType { throw new Error(`Jira Error: ${JSON.stringify(err)}`); } } + //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-get if (operation === 'get') { const issueKey = this.getNodeParameter('issueKey', i) as string; const fields = this.getNodeParameter('fields', i) as string; @@ -339,6 +342,7 @@ export class JiraSoftwareCloud implements INodeType { throw new Error(`Jira Error: ${JSON.stringify(err)}`); } } + //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-changelog-get if (operation === 'changelog') { const issueKey = this.getNodeParameter('issueKey', i) as string; const returnAll = this.getNodeParameter('returnAll', i) as boolean; @@ -354,6 +358,7 @@ export class JiraSoftwareCloud implements INodeType { throw new Error(`Jira Error: ${JSON.stringify(err)}`); } } + //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-notify-post if (operation === 'notify') { const issueKey = this.getNodeParameter('issueKey', i) as string; const textBody = this.getNodeParameter('textBody', i) as string; @@ -436,6 +441,7 @@ export class JiraSoftwareCloud implements INodeType { throw new Error(`Jira Error: ${JSON.stringify(err)}`); } } + https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-transitions-get if (operation === 'transitions') { const issueKey = this.getNodeParameter('issueKey', i) as string; const transitionId = this.getNodeParameter('transitionId', i) as string; @@ -455,6 +461,7 @@ export class JiraSoftwareCloud implements INodeType { throw new Error(`Jira Error: ${JSON.stringify(err)}`); } } + //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-delete if (operation === 'delete') { const issueKey = this.getNodeParameter('issueKey', i) as string; const deleteSubtasks = this.getNodeParameter('deleteSubtasks', i) as boolean; From 8ca31db0aa00b63d76115206bbcb3562aaedd448 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Sun, 1 Dec 2019 16:47:53 -0500 Subject: [PATCH 6/8] fixes done --- .../nodes-base/nodes/Jira/IssueDescription.ts | 456 +++++++----------- .../nodes-base/nodes/Jira/IssueInterface.ts | 2 +- .../nodes/Jira/JiraSoftwareCloud.node.ts | 108 +++-- packages/nodes-base/package.json | 4 +- 4 files changed, 250 insertions(+), 320 deletions(-) diff --git a/packages/nodes-base/nodes/Jira/IssueDescription.ts b/packages/nodes-base/nodes/Jira/IssueDescription.ts index 47d0a9f6108..1a8de914352 100644 --- a/packages/nodes-base/nodes/Jira/IssueDescription.ts +++ b/packages/nodes-base/nodes/Jira/IssueDescription.ts @@ -117,24 +117,6 @@ export const issueFields = [ default: '', description: 'Summary', }, - { - displayName: 'Parent Issue Key', - name: 'parentIssueKey', - type: 'string', - required: false, - displayOptions: { - show: { - resource: [ - 'issue', - ], - operation: [ - 'create', - ], - }, - }, - default: '', - description: 'Parent Issue Key', - }, { displayName: 'Additional Fields', name: 'additionalFields', @@ -152,6 +134,14 @@ export const issueFields = [ }, }, options: [ + { + displayName: 'Parent Issue Key', + name: 'parentIssueKey', + type: 'string', + required: false, + default: '', + description: 'Parent Issue Key', + }, { displayName: 'Labels', name: 'labels', @@ -226,65 +216,8 @@ export const issueFields = [ description: 'Issue Key', }, { - displayName: 'Issue Type', - name: 'issueType', - type: 'options', - required: false, - displayOptions: { - show: { - resource: [ - 'issue', - ], - operation: [ - 'update' - ] - }, - }, - typeOptions: { - loadOptionsMethod: 'getIssueTypes', - }, - default: '', - description: 'Issue Types', - }, - { - displayName: 'Summary', - name: 'summary', - type: 'string', - required: false, - displayOptions: { - show: { - resource: [ - 'issue', - ], - operation: [ - 'update', - ], - }, - }, - default: '', - description: 'Summary', - }, - { - displayName: 'Parent Issue Key', - name: 'parentIssueKey', - type: 'string', - required: false, - displayOptions: { - show: { - resource: [ - 'issue', - ], - operation: [ - 'update', - ], - }, - }, - default: '', - description: 'Parent Issue Key', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', + displayName: 'Update Fields', + name: 'updateFields', type: 'collection', placeholder: 'Add Field', default: {}, @@ -299,6 +232,33 @@ export const issueFields = [ }, }, options: [ + { + displayName: 'Issue Type', + name: 'issueType', + type: 'options', + required: false, + typeOptions: { + loadOptionsMethod: 'getIssueTypes', + }, + default: '', + description: 'Issue Types', + }, + { + displayName: 'Summary', + name: 'summary', + type: 'string', + required: false, + default: '', + description: 'Summary', + }, + { + displayName: 'Parent Issue Key', + name: 'parentIssueKey', + type: 'string', + required: false, + default: '', + description: 'Parent Issue Key', + }, { displayName: 'Labels', name: 'labels', @@ -405,10 +365,11 @@ export const issueFields = [ description: 'Issue Key', }, { - displayName: 'Fields', - name: 'fields', - type: 'string', - required: false, + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, displayOptions: { show: { resource: [ @@ -419,97 +380,72 @@ export const issueFields = [ ], }, }, - default: '', - description: `A list of fields to return for the issue. This parameter accepts a comma-separated list. Use it to retrieve a subset of fields. Allowed values: - *all Returns all fields. - *navigable Returns navigable fields. - Any issue field, prefixed with a minus to exclude.` - }, - { - displayName: 'Fields By Key', - name: 'fieldsByKey', - type: 'boolean', - required: false, - displayOptions: { - show: { - resource: [ - 'issue', - ], - operation: [ - 'get', - ], + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + required: false, + default: '', + description: `A list of fields to return for the issue. This parameter accepts a comma-separated list. + Use it to retrieve a subset of fields. Allowed values: + *all Returns all fields. + *navigable Returns navigable fields. + Any issue field, prefixed with a minus to exclude.` }, - }, - default: false, - description: `Indicates whether fields in fields are referenced by keys rather than IDs. This parameter is useful where fields have been added by a connect app and a field's key may differ from its ID.`, - }, - { - displayName: 'Expand', - name: 'expand', - type: 'string', - required: false, - displayOptions: { - show: { - resource: [ - 'issue', - ], - operation: [ - 'get', - ], + { + displayName: 'Fields By Key', + name: 'fieldsByKey', + type: 'boolean', + required: false, + default: false, + description: `Indicates whether fields in fields are referenced by keys rather than IDs. + This parameter is useful where fields have been added by a connect app and a field's key + may differ from its ID.`, }, - }, - default: '', - description: `Use expand to include additional information about the issues in the response. This parameter accepts a comma-separated list. Expand options include: - renderedFields Returns field values rendered in HTML format. - names Returns the display name of each field. - schema Returns the schema describing a field type. - transitions Returns all possible transitions for the issue. - editmeta Returns information about how each field can be edited. - changelog Returns a list of recent updates to an issue, sorted by date, starting from the most recent. - versionedRepresentations Returns a JSON array for each version of a field's value, with the highest number representing the most recent version. Note: When included in the request, the fields parameter is ignored.` - }, - { - displayName: 'Properties', - name: 'properties', - type: 'string', - required: false, - displayOptions: { - show: { - resource: [ - 'issue', - ], - operation: [ - 'get', - ], + { + displayName: 'Expand', + name: 'expand', + type: 'string', + required: false, + default: '', + description: `Use expand to include additional information about the issues in the response. + This parameter accepts a comma-separated list. Expand options include: + renderedFields Returns field values rendered in HTML format. + names Returns the display name of each field. + schema Returns the schema describing a field type. + transitions Returns all possible transitions for the issue. + editmeta Returns information about how each field can be edited. + changelog Returns a list of recent updates to an issue, sorted by date, starting from the most recent. + versionedRepresentations Returns a JSON array for each version of a field's value, with the highest number representing the most recent version. Note: When included in the request, the fields parameter is ignored.` }, - }, - default: '', - description: `A list of issue properties to return for the issue. This parameter accepts a comma-separated list. Allowed values: - *all Returns all issue properties. - Any issue property key, prefixed with a minus to exclude. - Examples: - *all Returns all properties. - *all,-prop1 Returns all properties except prop1. - prop1,prop2 Returns prop1 and prop2 properties. - This parameter may be specified multiple times. For example, properties=prop1,prop2& properties=prop3.` - }, - { - displayName: 'Update History', - name: 'updateHistory', - type: 'boolean', - required: false, - displayOptions: { - show: { - resource: [ - 'issue', - ], - operation: [ - 'get', - ], + { + displayName: 'Properties', + name: 'properties', + type: 'string', + required: false, + default: '', + description: `A list of issue properties to return for the issue. + This parameter accepts a comma-separated list. Allowed values: + *all Returns all issue properties. + Any issue property key, prefixed with a minus to exclude. + Examples: + *all Returns all properties. + *all,-prop1 Returns all properties except prop1. + prop1,prop2 Returns prop1 and prop2 properties. + This parameter may be specified multiple times. For example, properties=prop1,prop2& properties=prop3.` }, - }, - default: false, - description: `Whether the project in which the issue is created is added to the user's Recently viewed project list, as shown under Projects in Jira. This also populates the JQL issues search lastViewed field.`, + { + displayName: 'Update History', + name: 'updateHistory', + type: 'boolean', + required: false, + default: false, + description: `Whether the project in which the issue is created is added to the user's + Recently viewed project list, as shown under Projects in Jira. This also populates the + JQL issues search lastViewed field.`, + }, + ] }, /* -------------------------------------------------------------------------- */ @@ -574,7 +510,6 @@ export const issueFields = [ default: 50, description: 'How many results to return.', }, - /* -------------------------------------------------------------------------- */ /* issue:notify */ /* -------------------------------------------------------------------------- */ @@ -596,66 +531,6 @@ export const issueFields = [ default: '', description: 'Issue Key', }, - { - displayName: 'Subject', - name: 'subject', - type: 'string', - required: false, - displayOptions: { - show: { - resource: [ - 'issue', - ], - operation: [ - 'notify', - ], - }, - }, - default: '', - description: 'The subject of the email notification for the issue. If this is not specified, then the subject is set to the issue key and summary.', - }, - { - displayName: 'Text Body', - name: 'textBody', - type: 'string', - typeOptions: { - alwaysOpenEditWindow: true, - }, - required: false, - displayOptions: { - show: { - resource: [ - 'issue', - ], - operation: [ - 'notify', - ], - }, - }, - default: '', - description: 'The subject of the email notification for the issue. If this is not specified, then the subject is set to the issue key and summary.', - }, - { - displayName: 'HTML Body', - name: 'htmlBody', - type: 'string', - typeOptions: { - alwaysOpenEditWindow: true, - }, - required: false, - displayOptions: { - show: { - resource: [ - 'issue', - ], - operation: [ - 'notify', - ], - }, - }, - default: '', - description: 'The HTML body of the email notification for the issue.', - }, { displayName: 'JSON Parameters', name: 'jsonParameters', @@ -673,6 +548,57 @@ export const issueFields = [ }, }, }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'notify', + ], + }, + }, + options: [ + { + displayName: 'Subject', + name: 'subject', + type: 'string', + required: false, + default: '', + description: `The subject of the email notification for the issue. If this is not specified, + then the subject is set to the issue key and summary.` + }, + { + displayName: 'Text Body', + name: 'textBody', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + required: false, + default: '', + description: `The subject of the email notification for the issue. + If this is not specified, then the subject is set to the issue key and summary.` + }, + { + displayName: 'HTML Body', + name: 'htmlBody', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + required: false, + default: '', + description: 'The HTML body of the email notification for the issue.', + }, + ], + }, { displayName: 'Notification Recipients', name: 'notificationRecipientsUi', @@ -878,10 +804,11 @@ export const issueFields = [ description: 'Issue Key', }, { - displayName: 'Expand', - name: 'expand', - type: 'string', - required: false, + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, displayOptions: { show: { resource: [ @@ -892,43 +819,34 @@ export const issueFields = [ ], }, }, - default: '', - description: 'Use expand to include additional information about transitions in the response. This parameter accepts transitions.fields, which returns information about the fields in the transition screen for each transition. Fields hidden from the screen are not returned. Use this information to populate the fields and update fields in Transition issue.', - }, - { - displayName: 'Transition ID', - name: 'transitionId', - type: 'string', - required: false, - displayOptions: { - show: { - resource: [ - 'issue', - ], - operation: [ - 'transitions', - ], + options: [ + { + displayName: 'Expand', + name: 'expand', + type: 'string', + required: false, + default: '', + description: `Use expand to include additional information about transitions in the response. + This parameter accepts transitions.fields, which returns information about the fields in the + transition screen for each transition. Fields hidden from the screen are not returned. Use this + information to populate the fields and update fields in Transition issue.` }, - }, - default: '', - description: 'The ID of the transition.', - }, - { - displayName: 'Skip Remote Only Condition', - name: 'skipRemoteOnlyCondition', - type: 'boolean', - required: false, - displayOptions: { - show: { - resource: [ - 'issue', - ], - operation: [ - 'transitions', - ], + { + displayName: 'Transition ID', + name: 'transitionId', + type: 'string', + required: false, + default: '', + description: 'The ID of the transition.', }, - }, - default: false, - description: 'Indicates whether transitions with the condition Hide From User Condition are included in the response.', + { + displayName: 'Skip Remote Only Condition', + name: 'skipRemoteOnlyCondition', + type: 'boolean', + required: false, + default: false, + description: 'Indicates whether transitions with the condition Hide From User Condition are included in the response.', + }, + ], }, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Jira/IssueInterface.ts b/packages/nodes-base/nodes/Jira/IssueInterface.ts index 88b24962ce8..59ba0ca1e7d 100644 --- a/packages/nodes-base/nodes/Jira/IssueInterface.ts +++ b/packages/nodes-base/nodes/Jira/IssueInterface.ts @@ -1,7 +1,7 @@ import { IDataObject } from "n8n-workflow"; export interface IFields { - summary: string; + summary?: string; project?: IDataObject; issuetype?: IDataObject; labels?: string[]; diff --git a/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts b/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts index 45561b02af7..dcf32760460 100644 --- a/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts +++ b/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts @@ -219,7 +219,6 @@ export class JiraSoftwareCloud implements INodeType { const projectId = this.getNodeParameter('project', i) as string; const issueTypeId = this.getNodeParameter('issueType', i) as string; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - const parentIssueKey = this.getNodeParameter('parentIssueKey', i) as string; const body: IIssue = {}; const fields: IFields = { summary, @@ -256,12 +255,14 @@ export class JiraSoftwareCloud implements INodeType { subtaskIssues.push(issueType.id); } } - if (!parentIssueKey && subtaskIssues.includes(issueTypeId)) { + if (!additionalFields.parentIssueKey + && subtaskIssues.includes(issueTypeId)) { throw new Error('You must define a Parent Issue Key when Issue type is sub-task'); - } else if (parentIssueKey && subtaskIssues.includes(issueTypeId)) { + } else if (additionalFields.parentIssueKey + && subtaskIssues.includes(issueTypeId)) { fields.parent = { - key: parentIssueKey.toUpperCase(), + key: (additionalFields.parentIssueKey as string).toUpperCase(), }; } body.fields = fields; @@ -273,33 +274,33 @@ export class JiraSoftwareCloud implements INodeType { } //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-put if (operation === 'update') { - const summary = this.getNodeParameter('summary', i) as string; - const issueTypeId = this.getNodeParameter('issueType', i) as string; const issueKey = this.getNodeParameter('issueKey', i) as string; - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; - const parentIssueKey = this.getNodeParameter('parentIssueKey', i) as string; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; const body: IIssue = {}; - const fields: IFields = { - summary, - issuetype: { - id: issueTypeId - } - }; - if (additionalFields.labels) { - fields.labels = additionalFields.labels as string[]; + const fields: IFields = {}; + if (updateFields.summary) { + fields.summary = updateFields.summary as string; } - if (additionalFields.priority) { + if (updateFields.issueType) { + fields.issuetype = { + id: updateFields.issueType as string, + }; + } + if (updateFields.labels) { + fields.labels = updateFields.labels as string[]; + } + if (updateFields.priority) { fields.priority = { - id: additionalFields.priority as string, + id: updateFields.priority as string, }; } - if (additionalFields.assignee) { + if (updateFields.assignee) { fields.assignee = { - id: additionalFields.assignee as string, + id: updateFields.assignee as string, }; } - if (additionalFields.description) { - fields.description = additionalFields.description as string; + if (updateFields.description) { + fields.description = updateFields.description as string; } const issueTypes = await jiraSoftwareCloudApiRequest.call(this, '/issuetype', 'GET', body); const subtaskIssues = []; @@ -308,12 +309,14 @@ export class JiraSoftwareCloud implements INodeType { subtaskIssues.push(issueType.id); } } - if (!parentIssueKey && subtaskIssues.includes(issueTypeId)) { + if (!updateFields.parentIssueKey + && subtaskIssues.includes(updateFields.issueType)) { throw new Error('You must define a Parent Issue Key when Issue type is sub-task'); - } else if (parentIssueKey && subtaskIssues.includes(issueTypeId)) { + } else if (updateFields.parentIssueKey + && subtaskIssues.includes(updateFields.issueType)) { fields.parent = { - key: parentIssueKey.toUpperCase(), + key: (updateFields.parentIssueKey as string).toUpperCase(), }; } body.fields = fields; @@ -326,16 +329,22 @@ export class JiraSoftwareCloud implements INodeType { //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-get if (operation === 'get') { const issueKey = this.getNodeParameter('issueKey', i) as string; - const fields = this.getNodeParameter('fields', i) as string; - const fieldsByKey = this.getNodeParameter('fieldsByKey', i) as boolean; - const expand = this.getNodeParameter('expand', i) as string; - const properties = this.getNodeParameter('properties', i) as string; - const updateHistory = this.getNodeParameter('updateHistory', i) as boolean; - qs.fields = fields; - qs.fieldsByKey = fieldsByKey; - qs.expand = expand; - qs.properties = properties; - qs.updateHistory = updateHistory; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + if (additionalFields.fields) { + qs.fields = additionalFields.fields as string; + } + if (additionalFields.fieldsByKey) { + qs.fieldsByKey = additionalFields.fieldsByKey as boolean; + } + if (additionalFields.expand) { + qs.expand = additionalFields.expand as string; + } + if (additionalFields.properties) { + qs.properties = additionalFields.properties as string; + } + if (additionalFields.updateHistory) { + qs.updateHistory = additionalFields.updateHistory as string; + } try { responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}`, 'GET', {}, qs); } catch (err) { @@ -361,12 +370,15 @@ export class JiraSoftwareCloud implements INodeType { //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-notify-post if (operation === 'notify') { const issueKey = this.getNodeParameter('issueKey', i) as string; - const textBody = this.getNodeParameter('textBody', i) as string; - const htmlBody = this.getNodeParameter('htmlBody', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; const jsonActive = this.getNodeParameter('jsonParameters', 0) as boolean; const body: INotify = {}; - body.htmlBody = htmlBody; - body.textBody = textBody; + if (additionalFields.textBody) { + body.textBody = additionalFields.textBody as string; + } + if (additionalFields.htmlBody) { + body.htmlBody = additionalFields.htmlBody as string; + } if (!jsonActive) { const notificationRecipientsValues = (this.getNodeParameter('notificationRecipientsUi', i) as IDataObject).notificationRecipientsValues as IDataObject[]; const notificationRecipients: INotificationRecipients = {}; @@ -441,19 +453,19 @@ export class JiraSoftwareCloud implements INodeType { throw new Error(`Jira Error: ${JSON.stringify(err)}`); } } - https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-transitions-get + //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-transitions-get if (operation === 'transitions') { const issueKey = this.getNodeParameter('issueKey', i) as string; - const transitionId = this.getNodeParameter('transitionId', i) as string; - const expand = this.getNodeParameter('expand', i) as string; - if (transitionId) { - qs.transitionId = transitionId; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + if (additionalFields.transitionId) { + qs.transitionId = additionalFields.transitionId as string; } - if (expand) { - qs.expand = expand; + if (additionalFields.expand) { + qs.expand = additionalFields.expand as string; + } + if (additionalFields.skipRemoteOnlyCondition) { + qs.skipRemoteOnlyCondition = additionalFields.skipRemoteOnlyCondition as boolean; } - qs.skipRemoteOnlyCondition = this.getNodeParameter('skipRemoteOnlyCondition', i) as boolean; - try { responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}/transitions`, 'GET', {}, qs); responseData = responseData.transitions; diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index a82727dbb0c..0f48460c107 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -101,8 +101,8 @@ "dist/nodes/HttpRequest.node.js", "dist/nodes/If.node.js", "dist/nodes/Interval.node.js", - "dist/nodes/Intercom/Intercom.node.js", - "dist/nodes/Jira/JiraSoftwareCloud.node.js", + "dist/nodes/Intercom/Intercom.node.js", + "dist/nodes/Jira/JiraSoftwareCloud.node.js", "dist/nodes/LinkFish/LinkFish.node.js", "dist/nodes/Mailchimp/Mailchimp.node.js", "dist/nodes/Mailgun/Mailgun.node.js", From 28cb10df3aefd9cebe6e59edbe2b5d865d1416a7 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Mon, 2 Dec 2019 09:15:03 -0500 Subject: [PATCH 7/8] :art: added line breaks --- .../nodes-base/nodes/Jira/IssueDescription.ts | 65 ++++++++++--------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/packages/nodes-base/nodes/Jira/IssueDescription.ts b/packages/nodes-base/nodes/Jira/IssueDescription.ts index 1a8de914352..eb9079603e3 100644 --- a/packages/nodes-base/nodes/Jira/IssueDescription.ts +++ b/packages/nodes-base/nodes/Jira/IssueDescription.ts @@ -41,7 +41,8 @@ export const issueOpeations = [ { name: 'Transitions', value: 'transitions', - description: `Returns either all transitions or a transition that can be performed by the user on an issue, based on the issue's status.`, + description: `Returns either all transitions or a transition that can be performed by
+ the user on an issue, based on the issue's status.`, }, { name: 'Delete', @@ -189,7 +190,8 @@ export const issueFields = [ type: 'boolean', default: false, required : false, - description: `Whether the project in which the issue is created is added to the user's Recently viewed project list, as shown under Projects in Jira.`, + description: `Whether the project in which the issue is created is added to the user's
+ Recently viewed project list, as shown under Projects in Jira.`, }, ], }, @@ -387,11 +389,12 @@ export const issueFields = [ type: 'string', required: false, default: '', - description: `A list of fields to return for the issue. This parameter accepts a comma-separated list. - Use it to retrieve a subset of fields. Allowed values: - *all Returns all fields. - *navigable Returns navigable fields. - Any issue field, prefixed with a minus to exclude.` + description: `A list of fields to return for the issue.
+ This parameter accepts a comma-separated list.
+ Use it to retrieve a subset of fields. Allowed values:
+ *all Returns all fields.
+ *navigable Returns navigable fields.
+ Any issue field, prefixed with a minus to exclude.
` }, { displayName: 'Fields By Key', @@ -399,8 +402,8 @@ export const issueFields = [ type: 'boolean', required: false, default: false, - description: `Indicates whether fields in fields are referenced by keys rather than IDs. - This parameter is useful where fields have been added by a connect app and a field's key + description: `Indicates whether fields in fields are referenced by keys rather than IDs.
+ This parameter is useful where fields have been added by a connect app and a field's key
may differ from its ID.`, }, { @@ -409,15 +412,16 @@ export const issueFields = [ type: 'string', required: false, default: '', - description: `Use expand to include additional information about the issues in the response. - This parameter accepts a comma-separated list. Expand options include: - renderedFields Returns field values rendered in HTML format. - names Returns the display name of each field. - schema Returns the schema describing a field type. - transitions Returns all possible transitions for the issue. - editmeta Returns information about how each field can be edited. - changelog Returns a list of recent updates to an issue, sorted by date, starting from the most recent. - versionedRepresentations Returns a JSON array for each version of a field's value, with the highest number representing the most recent version. Note: When included in the request, the fields parameter is ignored.` + description: `Use expand to include additional information about the issues in the response.
+ This parameter accepts a comma-separated list. Expand options include:
+ renderedFields Returns field values rendered in HTML format.
+ names Returns the display name of each field.
+ schema Returns the schema describing a field type.
+ transitions Returns all possible transitions for the issue.
+ editmeta Returns information about how each field can be edited.
+ changelog Returns a list of recent updates to an issue, sorted by date, starting from the most recent.
+ versionedRepresentations Returns a JSON array for each version of a field's value, with the highest number
+ representing the most recent version. Note: When included in the request, the fields parameter is ignored.` }, { displayName: 'Properties', @@ -425,14 +429,14 @@ export const issueFields = [ type: 'string', required: false, default: '', - description: `A list of issue properties to return for the issue. - This parameter accepts a comma-separated list. Allowed values: - *all Returns all issue properties. - Any issue property key, prefixed with a minus to exclude. - Examples: - *all Returns all properties. - *all,-prop1 Returns all properties except prop1. - prop1,prop2 Returns prop1 and prop2 properties. + description: `A list of issue properties to return for the issue.
+ This parameter accepts a comma-separated list. Allowed values:
+ *all Returns all issue properties.
+ Any issue property key, prefixed with a minus to exclude.
+ Examples:
+ *all Returns all properties.
+ *all,-prop1 Returns all properties except prop1.
+ prop1,prop2 Returns prop1 and prop2 properties.
This parameter may be specified multiple times. For example, properties=prop1,prop2& properties=prop3.` }, { @@ -826,9 +830,9 @@ export const issueFields = [ type: 'string', required: false, default: '', - description: `Use expand to include additional information about transitions in the response. - This parameter accepts transitions.fields, which returns information about the fields in the - transition screen for each transition. Fields hidden from the screen are not returned. Use this + description: `Use expand to include additional information about transitions in the response.
+ This parameter accepts transitions.fields, which returns information about the fields in the
+ transition screen for each transition. Fields hidden from the screen are not returned. Use this
information to populate the fields and update fields in Transition issue.` }, { @@ -845,7 +849,8 @@ export const issueFields = [ type: 'boolean', required: false, default: false, - description: 'Indicates whether transitions with the condition Hide From User Condition are included in the response.', + description: `Indicates whether transitions with the condition Hide
+ From User Condition are included in the response.`, }, ], }, From d7ef2f41f8bd15719e790a1b7c892dd160bb25d2 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 2 Dec 2019 22:40:24 +0100 Subject: [PATCH 8/8] :zap: Some small adjustments --- .../JiraSoftwareCloudApi.credentials.ts | 2 +- .../nodes-base/nodes/Jira/IssueDescription.ts | 3 +-- .../nodes/Jira/JiraSoftwareCloud.node.ts | 6 ++++-- packages/nodes-base/nodes/Jira/jira.png | Bin 3886 -> 1824 bytes 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/nodes-base/credentials/JiraSoftwareCloudApi.credentials.ts b/packages/nodes-base/credentials/JiraSoftwareCloudApi.credentials.ts index b1c54bfd540..a11d526f7d6 100644 --- a/packages/nodes-base/credentials/JiraSoftwareCloudApi.credentials.ts +++ b/packages/nodes-base/credentials/JiraSoftwareCloudApi.credentials.ts @@ -5,7 +5,7 @@ import { export class JiraSoftwareCloudApi implements ICredentialType { name = 'jiraSoftwareCloudApi'; - displayName = 'Jira Software Cloud API'; + displayName = 'Jira SW Cloud API'; properties = [ { displayName: 'Email', diff --git a/packages/nodes-base/nodes/Jira/IssueDescription.ts b/packages/nodes-base/nodes/Jira/IssueDescription.ts index eb9079603e3..ee061e0a9c6 100644 --- a/packages/nodes-base/nodes/Jira/IssueDescription.ts +++ b/packages/nodes-base/nodes/Jira/IssueDescription.ts @@ -41,8 +41,7 @@ export const issueOpeations = [ { name: 'Transitions', value: 'transitions', - description: `Returns either all transitions or a transition that can be performed by
- the user on an issue, based on the issue's status.`, + description: `Returns either all transitions or a transition that can be performed by the user on an issue, based on the issue's status.`, }, { name: 'Delete', diff --git a/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts b/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts index dcf32760460..f5dd4afe854 100644 --- a/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts +++ b/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts @@ -209,9 +209,11 @@ export class JiraSoftwareCloud implements INodeType { const length = items.length as unknown as number; let responseData; const qs: IDataObject = {}; + + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + 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') { //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-post if (operation === 'create') { diff --git a/packages/nodes-base/nodes/Jira/jira.png b/packages/nodes-base/nodes/Jira/jira.png index db28f59c875a3865568b59fb2097232bf453d1f7..066c3b0aee3c29a3bb8f694c9454a7848448b4c4 100644 GIT binary patch literal 1824 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