From e19db703ce54f273526c6205e82f3662d9dc455d Mon Sep 17 00:00:00 2001 From: ricardo Date: Fri, 24 Apr 2020 00:59:19 -0500 Subject: [PATCH] :zap: Added update status to issues --- .../nodes-base/nodes/Jira/GenericFunctions.ts | 12 +- .../nodes-base/nodes/Jira/IssueDescription.ts | 227 +++++++++--------- .../nodes-base/nodes/Jira/IssueInterface.ts | 15 +- .../nodes/Jira/JiraSoftwareCloud.node.ts | 142 +++++------ 4 files changed, 194 insertions(+), 202 deletions(-) diff --git a/packages/nodes-base/nodes/Jira/GenericFunctions.ts b/packages/nodes-base/nodes/Jira/GenericFunctions.ts index 66ca437ded..133578fd50 100644 --- a/packages/nodes-base/nodes/Jira/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Jira/GenericFunctions.ts @@ -1,4 +1,6 @@ -import { OptionsWithUri } from 'request'; +import { + OptionsWithUri, + } from 'request'; import { IExecuteFunctions, @@ -41,11 +43,11 @@ export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecut try { return await this.helpers.request!(options); } catch (error) { - let errorMessage = error; - if (error.error && error.error.errorMessages) { - errorMessage = error.error.errorMessages; + const errorMessage = error.response.body.message || error.response.body.error || error.response.body.errors; + if (errorMessage !== undefined) { + throw new Error(errorMessage); } - throw new Error(errorMessage); + throw error; } } diff --git a/packages/nodes-base/nodes/Jira/IssueDescription.ts b/packages/nodes-base/nodes/Jira/IssueDescription.ts index ae46db23e3..74708b088f 100644 --- a/packages/nodes-base/nodes/Jira/IssueDescription.ts +++ b/packages/nodes-base/nodes/Jira/IssueDescription.ts @@ -44,7 +44,7 @@ export const issueOperations = [ description: 'Creates an email notification for an issue and adds it to the mail queue.', }, { - name: 'Transitions', + name: 'Status', 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.`, }, @@ -101,6 +101,9 @@ export const issueFields = [ }, typeOptions: { loadOptionsMethod: 'getIssueTypes', + loadOptionsDependsOn: [ + 'project', + ], }, description: 'Issue Types', }, @@ -139,36 +142,6 @@ export const issueFields = [ }, }, options: [ - { - displayName: 'Parent Issue Key', - name: 'parentIssueKey', - type: 'string', - required: false, - default: '', - description: 'Parent Issue Key', - }, - { - 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', @@ -188,6 +161,36 @@ export const issueFields = [ required : false, description: 'Description', }, + { + displayName: 'Labels', + name: 'labels', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getLabels', + }, + default: [], + required : false, + description: 'Labels', + }, + { + displayName: 'Parent Issue Key', + name: 'parentIssueKey', + type: 'string', + required: false, + default: '', + description: 'Parent Issue Key', + }, + { + displayName: 'Priority', + name: 'priority', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getPriorities', + }, + default: '', + required : false, + description: 'Priority', + }, { displayName: 'Update History', name: 'updateHistory', @@ -238,55 +241,6 @@ 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', - 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', @@ -306,6 +260,63 @@ export const issueFields = [ required : false, description: 'Description', }, + { + displayName: 'Issue Type', + name: 'issueType', + type: 'options', + required: false, + typeOptions: { + loadOptionsMethod: 'getIssueTypes', + }, + default: '', + description: 'Issue Types', + }, + { + displayName: 'Labels', + name: 'labels', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getLabels', + }, + default: [], + required : false, + description: 'Labels', + }, + { + displayName: 'Parent Issue Key', + name: 'parentIssueKey', + type: 'string', + required: false, + default: '', + description: 'Parent Issue Key', + }, + { + displayName: 'Priority', + name: 'priority', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getPriorities', + }, + default: '', + required : false, + description: 'Priority', + }, + { + displayName: 'Summary', + name: 'summary', + type: 'string', + required: false, + default: '', + description: 'Summary', + }, + { + displayName: 'Status ID', + name: 'statusId', + type: 'string', + required: false, + default: '', + description: 'The ID of the issue status.', + }, ], }, @@ -387,6 +398,23 @@ export const issueFields = [ }, }, options: [ + { + 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.` + }, { displayName: 'Fields', name: 'fields', @@ -410,23 +438,6 @@ export const issueFields = [ 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, - 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', @@ -715,6 +726,17 @@ export const issueFields = [ }, }, options: [ + { + 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: 'Subject', name: 'subject', @@ -736,17 +758,6 @@ export const issueFields = [ 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.', - }, ], }, { diff --git a/packages/nodes-base/nodes/Jira/IssueInterface.ts b/packages/nodes-base/nodes/Jira/IssueInterface.ts index 59ba0ca1e7..fd7a948e29 100644 --- a/packages/nodes-base/nodes/Jira/IssueInterface.ts +++ b/packages/nodes-base/nodes/Jira/IssueInterface.ts @@ -1,18 +1,21 @@ -import { IDataObject } from "n8n-workflow"; +import { + IDataObject, + } from 'n8n-workflow'; export interface IFields { - summary?: string; - project?: IDataObject; - issuetype?: IDataObject; - labels?: string[]; - priority?: IDataObject; assignee?: IDataObject; description?: string; + issuetype?: IDataObject; + labels?: string[]; parent?: IDataObject; + priority?: IDataObject; + project?: IDataObject; + summary?: string; } export interface IIssue { fields?: IFields; + transition?: IDataObject; } export interface INotify { diff --git a/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts b/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts index b4e3cf0c2a..3388acaeeb 100644 --- a/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts +++ b/packages/nodes-base/nodes/Jira/JiraSoftwareCloud.node.ts @@ -1,28 +1,32 @@ import { IExecuteFunctions, } from 'n8n-core'; + import { IDataObject, - INodeTypeDescription, - INodeExecutionData, - INodeType, ILoadOptionsFunctions, + INodeExecutionData, INodePropertyOptions, + INodeType, + INodeTypeDescription, } from 'n8n-workflow'; + import { jiraSoftwareCloudApiRequest, jiraSoftwareCloudApiRequestAllItems, validateJSON, } from './GenericFunctions'; + import { issueOperations, issueFields, } from './IssueDescription'; + import { - IIssue, IFields, - INotify, + IIssue, INotificationRecipients, + INotify, NotificationRecipientsRestrictions, } from './IssueInterface'; @@ -37,7 +41,7 @@ export class JiraSoftwareCloud implements INodeType { description: 'Consume Jira Software API', defaults: { name: 'Jira Software', - color: '#c02428', + color: '#4185f7', }, inputs: ['main'], outputs: ['main'], @@ -113,11 +117,8 @@ export class JiraSoftwareCloud implements INodeType { if (jiraCloudCredentials === undefined) { endpoint = '/project'; } - try { - projects = await jiraSoftwareCloudApiRequest.call(this, endpoint, 'GET'); - } catch (err) { - throw new Error(`Jira Error: ${err}`); - } + projects = await jiraSoftwareCloudApiRequest.call(this, endpoint, 'GET'); + if (projects.values && Array.isArray(projects.values)) { projects = projects.values; } @@ -135,21 +136,22 @@ export class JiraSoftwareCloud implements INodeType { // Get all the issue types to display them to user so that he can // select them easily async getIssueTypes(this: ILoadOptionsFunctions): Promise { + const projectId = this.getCurrentNodeParameter('project'); const returnData: INodePropertyOptions[] = []; let issueTypes; - try { - issueTypes = await jiraSoftwareCloudApiRequest.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, - }); + issueTypes = await jiraSoftwareCloudApiRequest.call(this, '/issuetype', 'GET'); + + for (const issueType of issueTypes) { + if (issueType.scope.project.id === projectId) { + const issueTypeName = issueType.name; + const issueTypeId = issueType.id; + + returnData.push({ + name: issueTypeName, + value: issueTypeId, + }); + } } return returnData; }, @@ -159,11 +161,9 @@ export class JiraSoftwareCloud implements INodeType { async getLabels(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; let labels; - try { - labels = await jiraSoftwareCloudApiRequest.call(this, '/label', 'GET'); - } catch (err) { - throw new Error(`Jira Error: ${err}`); - } + + labels = await jiraSoftwareCloudApiRequest.call(this, '/label', 'GET'); + for (const label of labels.values) { const labelName = label; const labelId = label; @@ -181,11 +181,9 @@ export class JiraSoftwareCloud implements INodeType { async getPriorities(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; let priorities; - try { - priorities = await jiraSoftwareCloudApiRequest.call(this, '/priority', 'GET'); - } catch (err) { - throw new Error(`Jira Error: ${err}`); - } + + priorities = await jiraSoftwareCloudApiRequest.call(this, '/priority', 'GET'); + for (const priority of priorities) { const priorityName = priority.name; const priorityId = priority.id; @@ -203,11 +201,9 @@ export class JiraSoftwareCloud implements INodeType { async getUsers(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; let users; - try { - users = await jiraSoftwareCloudApiRequest.call(this, '/users/search', 'GET'); - } catch (err) { - throw new Error(`Jira Error: ${err}`); - } + + users = await jiraSoftwareCloudApiRequest.call(this, '/users/search', 'GET'); + for (const user of users) { const userName = user.displayName; const userId = user.accountId; @@ -225,11 +221,9 @@ export class JiraSoftwareCloud implements INodeType { 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}`); - } + + groups = await jiraSoftwareCloudApiRequest.call(this, '/groups/picker', 'GET'); + for (const group of groups.groups) { const groupName = group.name; const groupId = group.name; @@ -309,11 +303,7 @@ export class JiraSoftwareCloud implements INodeType { }; } body.fields = fields; - try { - responseData = await jiraSoftwareCloudApiRequest.call(this, '/issue', 'POST', body); - } catch (err) { - throw new Error(`Jira Error: ${JSON.stringify(err)}`); - } + responseData = await jiraSoftwareCloudApiRequest.call(this, '/issue', 'POST', body); } //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-put if (operation === 'update') { @@ -363,11 +353,13 @@ export class JiraSoftwareCloud implements INodeType { }; } body.fields = fields; - try { - responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}`, 'PUT', body); - } catch (err) { - throw new Error(`Jira Error: ${JSON.stringify(err)}`); + + if (updateFields.statusId) { + responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}/transitions`, 'POST', { transition: { id: updateFields.statusId } }); } + + responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}`, 'PUT', body); + responseData = { success: true }; } //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-get if (operation === 'get') { @@ -388,11 +380,9 @@ export class JiraSoftwareCloud implements INodeType { if (additionalFields.updateHistory) { qs.updateHistory = additionalFields.updateHistory as string; } - try { - responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}`, 'GET', {}, qs); - } catch (err) { - throw new Error(`Jira Error: ${JSON.stringify(err)}`); - } + + responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}`, 'GET', {}, qs); + } //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-search-post if (operation === 'getAll') { @@ -421,16 +411,12 @@ export class JiraSoftwareCloud implements INodeType { 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 (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; } } //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-notify-post @@ -513,11 +499,8 @@ export class JiraSoftwareCloud implements INodeType { 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)}`); - } + responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}/notify`, 'POST', body, qs); + } //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-transitions-get if (operation === 'transitions') { @@ -532,23 +515,16 @@ export class JiraSoftwareCloud implements INodeType { if (additionalFields.skipRemoteOnlyCondition) { qs.skipRemoteOnlyCondition = additionalFields.skipRemoteOnlyCondition 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)}`); - } + responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}/transitions`, 'GET', {}, qs); + responseData = responseData.transitions; + } //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; qs.deleteSubtasks = deleteSubtasks; - try { - responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}`, 'DELETE', {}, qs); - } catch (err) { - throw new Error(`Jira Error: ${JSON.stringify(err)}`); - } + responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}`, 'DELETE', {}, qs); } } if (Array.isArray(responseData)) {