diff --git a/packages/nodes-base/nodes/Jira/GenericFunctions.ts b/packages/nodes-base/nodes/Jira/GenericFunctions.ts
index 66ca437ded..3f2e6dd82a 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,21 @@ 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;
+ let errorMessage = error.message;
+
+ if (error.response.body) {
+ if (error.response.body.errorMessages && error.response.body.errorMessages.length) {
+ errorMessage = JSON.stringify(error.response.body.errorMessages);
+ } else {
+ errorMessage = error.response.body.message || error.response.body.error || error.response.body.errors || error.message;
+ }
}
- throw new Error(errorMessage);
+
+ if (typeof errorMessage !== 'string') {
+ errorMessage = JSON.stringify(errorMessage);
+ }
+
+ throw new Error(`Jira error response [${error.statusCode}]: ${errorMessage}`);
}
}
diff --git a/packages/nodes-base/nodes/Jira/IssueDescription.ts b/packages/nodes-base/nodes/Jira/IssueDescription.ts
index ae46db23e3..5f3c18c9d6 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,66 @@ 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: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getTransitions',
+ },
+ required: false,
+ default: '',
+ description: 'The ID of the issue status.',
+ },
],
},
@@ -387,6 +401,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 +441,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 +729,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 +761,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..7335d24b6b 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'],
@@ -108,16 +112,12 @@ export class JiraSoftwareCloud implements INodeType {
async getProjects(this: ILoadOptionsFunctions): Promise {
const returnData: INodePropertyOptions[] = [];
const jiraCloudCredentials = this.getCredentials('jiraSoftwareCloudApi');
- let projects;
let endpoint = '/project/search';
if (jiraCloudCredentials === undefined) {
endpoint = '/project';
}
- try {
- projects = await jiraSoftwareCloudApiRequest.call(this, endpoint, 'GET');
- } catch (err) {
- throw new Error(`Jira Error: ${err}`);
- }
+ let projects = await jiraSoftwareCloudApiRequest.call(this, endpoint, 'GET');
+
if (projects.values && Array.isArray(projects.values)) {
projects = projects.values;
}
@@ -135,21 +135,21 @@ 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,
- });
+ const 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;
},
@@ -158,12 +158,9 @@ export class JiraSoftwareCloud implements INodeType {
// select them easily
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}`);
- }
+
+ const labels = await jiraSoftwareCloudApiRequest.call(this, '/label', 'GET');
+
for (const label of labels.values) {
const labelName = label;
const labelId = label;
@@ -180,12 +177,9 @@ export class JiraSoftwareCloud implements INodeType {
// select them easily
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}`);
- }
+
+ const priorities = await jiraSoftwareCloudApiRequest.call(this, '/priority', 'GET');
+
for (const priority of priorities) {
const priorityName = priority.name;
const priorityId = priority.id;
@@ -202,12 +196,9 @@ export class JiraSoftwareCloud implements INodeType {
// select them easily
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}`);
- }
+
+ const users = await jiraSoftwareCloudApiRequest.call(this, '/users/search', 'GET');
+
for (const user of users) {
const userName = user.displayName;
const userId = user.accountId;
@@ -224,12 +215,9 @@ export class JiraSoftwareCloud implements INodeType {
// 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}`);
- }
+
+ const groups = await jiraSoftwareCloudApiRequest.call(this, '/groups/picker', 'GET');
+
for (const group of groups.groups) {
const groupName = group.name;
const groupId = group.name;
@@ -240,7 +228,24 @@ export class JiraSoftwareCloud implements INodeType {
});
}
return returnData;
- }
+ },
+
+ // Get all the groups to display them to user so that he can
+ // select them easily
+ async getTransitions(this: ILoadOptionsFunctions): Promise {
+ const returnData: INodePropertyOptions[] = [];
+
+ const issueKey = this.getCurrentNodeParameter('issueKey');
+ const transitions = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}/transitions`, 'GET');
+
+ for (const transition of transitions.transitions) {
+ returnData.push({
+ name: transition.name,
+ value: transition.id,
+ });
+ }
+ return returnData;
+ },
}
};
@@ -309,11 +314,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 +364,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 +391,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 +422,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 +510,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 +526,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)) {