Extended Jira Node (#1306)

*  Add Issue Attachment resource

*  Add custom fields to issue:create and issue:update

*  Filter custom fields by the project selected

*  Change the logo to SVG

*  Small improvement

*  Minor improvements to Jira Node

*  Add download field to issueAttachment get and getAll

Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
Ricardo Espinoza 2021-01-29 14:08:27 -05:00 committed by GitHub
parent 5b371ce994
commit 46fe96b72c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 623 additions and 120 deletions

View file

@ -1,6 +1,6 @@
import { import {
OptionsWithUri, OptionsWithUri,
} from 'request'; } from 'request';
import { import {
IExecuteFunctions, IExecuteFunctions,
@ -14,7 +14,7 @@ import {
IDataObject, IDataObject,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, query?: IDataObject, uri?: string): Promise<any> { // tslint:disable-line:no-any export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, query?: IDataObject, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
let data; let domain; let data; let domain;
const jiraVersion = this.getNodeParameter('jiraVersion', 0) as string; const jiraVersion = this.getNodeParameter('jiraVersion', 0) as string;
@ -43,6 +43,7 @@ export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecut
Authorization: `Basic ${data}`, Authorization: `Basic ${data}`,
Accept: 'application/json', Accept: 'application/json',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Atlassian-Token': 'no-check',
}, },
method, method,
qs: query, qs: query,
@ -51,6 +52,18 @@ export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecut
json: true, json: true,
}; };
if (Object.keys(option).length !== 0) {
Object.assign(options, option);
}
if (Object.keys(body).length === 0) {
delete options.body;
}
if (Object.keys(query || {}).length === 0) {
delete options.qs;
}
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
@ -82,7 +95,7 @@ export async function jiraSoftwareCloudApiRequestAllItems(this: IHookFunctions |
query.startAt = 0; query.startAt = 0;
body.startAt = 0; body.startAt = 0;
query.maxResults = 100; query.maxResults = 100;
body.maxResults = 100; body.maxResults = 100;
do { do {
responseData = await jiraSoftwareCloudApiRequest.call(this, endpoint, method, body, query); responseData = await jiraSoftwareCloudApiRequest.call(this, endpoint, method, body, query);
@ -106,7 +119,7 @@ export function validateJSON(json: string | undefined): any { // tslint:disable-
return result; return result;
} }
export function eventExists (currentEvents : string[], webhookEvents: string[]) { export function eventExists(currentEvents: string[], webhookEvents: string[]) {
for (const currentEvent of currentEvents) { for (const currentEvent of currentEvents) {
if (!webhookEvents.includes(currentEvent)) { if (!webhookEvents.includes(currentEvent)) {
return false; return false;
@ -115,7 +128,7 @@ export function eventExists (currentEvents : string[], webhookEvents: string[])
return true; return true;
} }
export function getId (url: string) { export function getId(url: string) {
return url.split('/').pop(); return url.split('/').pop();
} }

View file

@ -0,0 +1,266 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const issueAttachmentOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'issueAttachment',
],
},
},
options: [
{
name: 'Add',
value: 'add',
description: 'Add attachment to issue',
},
{
name: 'Get',
value: 'get',
description: 'Get an attachment',
},
{
name: 'Get All',
value: 'getAll',
description: 'Get all attachments',
},
{
name: 'Remove',
value: 'remove',
description: 'Remove an attachment',
},
],
default: 'add',
description: 'The operation to perform.',
},
] as INodeProperties[];
export const issueAttachmentFields = [
/* -------------------------------------------------------------------------- */
/* issueAttachment:add */
/* -------------------------------------------------------------------------- */
{
displayName: 'Issue Key',
name: 'issueKey',
type: 'string',
required: true,
displayOptions: {
show: {
resource: [
'issueAttachment',
],
operation: [
'add',
],
},
},
default: '',
description: 'Issue Key',
},
{
displayName: 'Binary Property',
displayOptions: {
show: {
resource: [
'issueAttachment',
],
operation: [
'add',
],
},
},
name: 'binaryPropertyName',
type: 'string',
default: 'data',
description: 'Object property name which holds binary data.',
required: true,
},
/* -------------------------------------------------------------------------- */
/* issueAttachment:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Attachment ID',
name: 'attachmentId',
type: 'string',
required: true,
displayOptions: {
show: {
resource: [
'issueAttachment',
],
operation: [
'get',
],
},
},
default: '',
description: 'The ID of the attachment.',
},
{
displayName: 'Download',
name: 'download',
type: 'boolean',
default: false,
required: true,
displayOptions: {
show: {
resource: [
'issueAttachment',
],
operation: [
'get',
],
},
},
},
{
displayName: 'Binary Property',
name: 'binaryProperty',
type: 'string',
default: 'data',
displayOptions: {
show: {
resource: [
'issueAttachment',
],
operation: [
'get',
],
download: [
true,
],
},
},
description: 'Object property name which holds binary data.',
required: true,
},
/* -------------------------------------------------------------------------- */
/* issueAttachment:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Issue Key',
name: 'issueKey',
type: 'string',
required: true,
displayOptions: {
show: {
resource: [
'issueAttachment',
],
operation: [
'getAll',
],
},
},
default: '',
description: 'Issue Key',
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: [
'issueAttachment',
],
operation: [
'getAll',
],
},
},
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: [
'issueAttachment',
],
operation: [
'getAll',
],
returnAll: [
false,
],
},
},
typeOptions: {
minValue: 1,
maxValue: 100,
},
default: 50,
description: 'How many results to return.',
},
{
displayName: 'Download',
name: 'download',
type: 'boolean',
default: false,
required: true,
displayOptions: {
show: {
resource: [
'issueAttachment',
],
operation: [
'getAll',
],
},
},
},
{
displayName: 'Binary Property',
name: 'binaryProperty',
type: 'string',
default: 'data',
displayOptions: {
show: {
resource: [
'issueAttachment',
],
operation: [
'getAll',
],
download: [
true,
],
},
},
description: 'Object property name which holds binary data.',
required: true,
},
/* -------------------------------------------------------------------------- */
/* issueAttachment:remove */
/* -------------------------------------------------------------------------- */
{
displayName: 'Attachment ID',
name: 'attachmentId',
type: 'string',
required: true,
displayOptions: {
show: {
resource: [
'issueAttachment',
],
operation: [
'remove',
],
},
},
default: '',
description: 'The ID of the attachment.',
},
] as INodeProperties[];

View file

@ -63,9 +63,9 @@ export const issueOperations = [
export const issueFields = [ export const issueFields = [
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* issue:create */ /* issue:create */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Project', displayName: 'Project',
name: 'project', name: 'project',
@ -155,7 +155,6 @@ export const issueFields = [
loadOptionsMethod: 'getUsers', loadOptionsMethod: 'getUsers',
}, },
default: '', default: '',
required : false,
description: 'Assignee', description: 'Assignee',
}, },
{ {
@ -163,9 +162,46 @@ export const issueFields = [
name: 'description', name: 'description',
type: 'string', type: 'string',
default: '', default: '',
required : false,
description: 'Description', description: 'Description',
}, },
{
displayName: 'Custom Fields',
name: 'customFieldsUi',
type: 'fixedCollection',
default: '',
placeholder: 'Add Custom Field',
typeOptions: {
multipleValues: true,
},
options: [
{
name: 'customFieldsValues',
displayName: 'Custom Field',
values: [
{
displayName: 'Field ID',
name: 'fieldId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getCustomFields',
loadOptionsDependsOn: [
'project',
],
},
description: 'ID of the field to set.',
default: '',
},
{
displayName: 'Field Value',
name: 'fieldValue',
type: 'string',
description: 'Value of the field to set.',
default: '',
},
],
},
],
},
{ {
displayName: 'Labels', displayName: 'Labels',
name: 'labels', name: 'labels',
@ -174,9 +210,8 @@ export const issueFields = [
loadOptionsMethod: 'getLabels', loadOptionsMethod: 'getLabels',
}, },
default: [], default: [],
required : false,
description: 'Labels', description: 'Labels',
displayOptions: { displayOptions: {
show: { show: {
'/jiraVersion': [ '/jiraVersion': [
'cloud', 'cloud',
@ -189,9 +224,8 @@ export const issueFields = [
name: 'serverLabels', name: 'serverLabels',
type: 'string', type: 'string',
default: [], default: [],
required : false,
description: 'Labels', description: 'Labels',
displayOptions: { displayOptions: {
show: { show: {
'/jiraVersion': [ '/jiraVersion': [
'server', 'server',
@ -206,7 +240,6 @@ export const issueFields = [
displayName: 'Parent Issue Key', displayName: 'Parent Issue Key',
name: 'parentIssueKey', name: 'parentIssueKey',
type: 'string', type: 'string',
required: false,
default: '', default: '',
description: 'Parent Issue Key', description: 'Parent Issue Key',
}, },
@ -218,7 +251,6 @@ export const issueFields = [
loadOptionsMethod: 'getPriorities', loadOptionsMethod: 'getPriorities',
}, },
default: '', default: '',
required : false,
description: 'Priority', description: 'Priority',
}, },
{ {
@ -226,16 +258,15 @@ export const issueFields = [
name: 'updateHistory', name: 'updateHistory',
type: 'boolean', type: 'boolean',
default: false, default: false,
required : false,
description: `Whether the project in which the issue is created is added to the user's<br/> description: `Whether the project in which the issue is created is added to the user's<br/>
Recently viewed project list, as shown under Projects in Jira.`, Recently viewed project list, as shown under Projects in Jira.`,
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* issue:update */ /* issue:update */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Issue Key', displayName: 'Issue Key',
name: 'issueKey', name: 'issueKey',
@ -279,7 +310,6 @@ export const issueFields = [
loadOptionsMethod: 'getUsers', loadOptionsMethod: 'getUsers',
}, },
default: '', default: '',
required : false,
description: 'Assignee', description: 'Assignee',
}, },
{ {
@ -287,14 +317,50 @@ export const issueFields = [
name: 'description', name: 'description',
type: 'string', type: 'string',
default: '', default: '',
required : false,
description: 'Description', description: 'Description',
}, },
{
displayName: 'Custom Fields',
name: 'customFieldsUi',
type: 'fixedCollection',
default: '',
placeholder: 'Add Custom Field',
typeOptions: {
multipleValues: true,
},
options: [
{
name: 'customFieldsValues',
displayName: 'Custom Field',
values: [
{
displayName: 'Field ID',
name: 'fieldId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getCustomFields',
loadOptionsDependsOn: [
'issueKey',
],
},
description: 'ID of the field to set.',
default: '',
},
{
displayName: 'Field Value',
name: 'fieldValue',
type: 'string',
description: 'Value of the field to set.',
default: '',
},
],
},
],
},
{ {
displayName: 'Issue Type', displayName: 'Issue Type',
name: 'issueType', name: 'issueType',
type: 'options', type: 'options',
required: false,
typeOptions: { typeOptions: {
loadOptionsMethod: 'getIssueTypes', loadOptionsMethod: 'getIssueTypes',
}, },
@ -309,9 +375,8 @@ export const issueFields = [
loadOptionsMethod: 'getLabels', loadOptionsMethod: 'getLabels',
}, },
default: [], default: [],
required : false,
description: 'Labels', description: 'Labels',
displayOptions: { displayOptions: {
show: { show: {
'/jiraVersion': [ '/jiraVersion': [
'cloud', 'cloud',
@ -324,9 +389,8 @@ export const issueFields = [
name: 'serverLabels', name: 'serverLabels',
type: 'string', type: 'string',
default: [], default: [],
required : false,
description: 'Labels', description: 'Labels',
displayOptions: { displayOptions: {
show: { show: {
'/jiraVersion': [ '/jiraVersion': [
'server', 'server',
@ -341,7 +405,6 @@ export const issueFields = [
displayName: 'Parent Issue Key', displayName: 'Parent Issue Key',
name: 'parentIssueKey', name: 'parentIssueKey',
type: 'string', type: 'string',
required: false,
default: '', default: '',
description: 'Parent Issue Key', description: 'Parent Issue Key',
}, },
@ -353,14 +416,12 @@ export const issueFields = [
loadOptionsMethod: 'getPriorities', loadOptionsMethod: 'getPriorities',
}, },
default: '', default: '',
required : false,
description: 'Priority', description: 'Priority',
}, },
{ {
displayName: 'Summary', displayName: 'Summary',
name: 'summary', name: 'summary',
type: 'string', type: 'string',
required: false,
default: '', default: '',
description: 'Summary', description: 'Summary',
}, },
@ -371,16 +432,15 @@ export const issueFields = [
typeOptions: { typeOptions: {
loadOptionsMethod: 'getTransitions', loadOptionsMethod: 'getTransitions',
}, },
required: false,
default: '', default: '',
description: 'The ID of the issue status.', description: 'The ID of the issue status.',
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* issue:delete */ /* issue:delete */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Issue Key', displayName: 'Issue Key',
name: 'issueKey', name: 'issueKey',
@ -418,9 +478,9 @@ export const issueFields = [
description: 'Delete Subtasks', description: 'Delete Subtasks',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* issue:get */ /* issue:get */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Issue Key', displayName: 'Issue Key',
name: 'issueKey', name: 'issueKey',
@ -460,7 +520,6 @@ export const issueFields = [
displayName: 'Expand', displayName: 'Expand',
name: 'expand', name: 'expand',
type: 'string', type: 'string',
required: false,
default: '', default: '',
description: `Use expand to include additional information about the issues in the response.<br/> description: `Use expand to include additional information about the issues in the response.<br/>
This parameter accepts a comma-separated list. Expand options include:<br/> This parameter accepts a comma-separated list. Expand options include:<br/>
@ -477,7 +536,6 @@ export const issueFields = [
displayName: 'Fields', displayName: 'Fields',
name: 'fields', name: 'fields',
type: 'string', type: 'string',
required: false,
default: '', default: '',
description: `A list of fields to return for the issue.<br/> description: `A list of fields to return for the issue.<br/>
This parameter accepts a comma-separated list.<br/> This parameter accepts a comma-separated list.<br/>
@ -490,7 +548,6 @@ export const issueFields = [
displayName: 'Fields By Key', displayName: 'Fields By Key',
name: 'fieldsByKey', name: 'fieldsByKey',
type: 'boolean', type: 'boolean',
required: false,
default: false, default: false,
description: `Indicates whether fields in fields are referenced by keys rather than IDs.<br/> description: `Indicates whether fields in fields are referenced by keys rather than IDs.<br/>
This parameter is useful where fields have been added by a connect app and a field's key<br/> This parameter is useful where fields have been added by a connect app and a field's key<br/>
@ -500,7 +557,6 @@ export const issueFields = [
displayName: 'Properties', displayName: 'Properties',
name: 'properties', name: 'properties',
type: 'string', type: 'string',
required: false,
default: '', default: '',
description: `A list of issue properties to return for the issue.<br/> description: `A list of issue properties to return for the issue.<br/>
This parameter accepts a comma-separated list. Allowed values:<br/> This parameter accepts a comma-separated list. Allowed values:<br/>
@ -516,7 +572,6 @@ export const issueFields = [
displayName: 'Update History', displayName: 'Update History',
name: 'updateHistory', name: 'updateHistory',
type: 'boolean', type: 'boolean',
required: false,
default: false, default: false,
description: `Whether the project in which the issue is created is added to the user's 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 Recently viewed project list, as shown under Projects in Jira. This also populates the
@ -525,9 +580,9 @@ export const issueFields = [
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* issue:getAll */ /* issue:getAll */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Return All', displayName: 'Return All',
name: 'returnAll', name: 'returnAll',
@ -649,7 +704,6 @@ export const issueFields = [
displayName: 'Fields By Key', displayName: 'Fields By Key',
name: 'fieldsByKey', name: 'fieldsByKey',
type: 'boolean', type: 'boolean',
required: false,
default: false, default: false,
description: `Indicates whether fields in fields are referenced by keys rather than IDs.<br/> description: `Indicates whether fields in fields are referenced by keys rather than IDs.<br/>
This parameter is useful where fields have been added by a connect app and a field's key<br/> This parameter is useful where fields have been added by a connect app and a field's key<br/>
@ -667,9 +721,9 @@ export const issueFields = [
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* issue:changelog */ /* issue:changelog */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Issue Key', displayName: 'Issue Key',
name: 'issueKey', name: 'issueKey',
@ -729,9 +783,9 @@ export const issueFields = [
default: 50, default: 50,
description: 'How many results to return.', description: 'How many results to return.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* issue:notify */ /* issue:notify */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Issue Key', displayName: 'Issue Key',
name: 'issueKey', name: 'issueKey',
@ -791,7 +845,6 @@ export const issueFields = [
typeOptions: { typeOptions: {
alwaysOpenEditWindow: true, alwaysOpenEditWindow: true,
}, },
required: false,
default: '', default: '',
description: 'The HTML body of the email notification for the issue.', description: 'The HTML body of the email notification for the issue.',
}, },
@ -799,7 +852,6 @@ export const issueFields = [
displayName: 'Subject', displayName: 'Subject',
name: 'subject', name: 'subject',
type: 'string', type: 'string',
required: false,
default: '', default: '',
description: `The subject of the email notification for the issue. If this is not specified, 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.`, then the subject is set to the issue key and summary.`,
@ -811,7 +863,6 @@ export const issueFields = [
typeOptions: { typeOptions: {
alwaysOpenEditWindow: true, alwaysOpenEditWindow: true,
}, },
required: false,
default: '', default: '',
description: `The subject of the email notification for the issue. 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.`, If this is not specified, then the subject is set to the issue key and summary.`,
@ -906,7 +957,6 @@ export const issueFields = [
typeOptions: { typeOptions: {
alwaysOpenEditWindow: true, alwaysOpenEditWindow: true,
}, },
required: false,
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -983,7 +1033,6 @@ export const issueFields = [
typeOptions: { typeOptions: {
alwaysOpenEditWindow: true, alwaysOpenEditWindow: true,
}, },
required: false,
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -1001,9 +1050,9 @@ export const issueFields = [
description: 'Restricts the notifications to users with the specified permissions.', description: 'Restricts the notifications to users with the specified permissions.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* issue:transitions */ /* issue:transitions */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Issue Key', displayName: 'Issue Key',
name: 'issueKey', name: 'issueKey',
@ -1043,7 +1092,6 @@ export const issueFields = [
displayName: 'Expand', displayName: 'Expand',
name: 'expand', name: 'expand',
type: 'string', type: 'string',
required: false,
default: '', default: '',
description: `Use expand to include additional information about transitions in the response.<br/> description: `Use expand to include additional information about transitions in the response.<br/>
This parameter accepts transitions.fields, which returns information about the fields in the<br/> This parameter accepts transitions.fields, which returns information about the fields in the<br/>
@ -1054,7 +1102,6 @@ export const issueFields = [
displayName: 'Transition ID', displayName: 'Transition ID',
name: 'transitionId', name: 'transitionId',
type: 'string', type: 'string',
required: false,
default: '', default: '',
description: 'The ID of the transition.', description: 'The ID of the transition.',
}, },
@ -1062,7 +1109,6 @@ export const issueFields = [
displayName: 'Skip Remote Only Condition', displayName: 'Skip Remote Only Condition',
name: 'skipRemoteOnlyCondition', name: 'skipRemoteOnlyCondition',
type: 'boolean', type: 'boolean',
required: false,
default: false, default: false,
description: `Indicates whether transitions with the condition Hide<br/> description: `Indicates whether transitions with the condition Hide<br/>
From User Condition are included in the response.`, From User Condition are included in the response.`,

View file

@ -1,6 +1,6 @@
import { import {
IDataObject, IDataObject,
} from 'n8n-workflow'; } from 'n8n-workflow';
export interface IFields { export interface IFields {
assignee?: IDataObject; assignee?: IDataObject;

View file

@ -1,8 +1,11 @@
import { import {
BINARY_ENCODING,
IExecuteFunctions, IExecuteFunctions,
} from 'n8n-core'; } from 'n8n-core';
import { import {
IBinaryData,
IBinaryKeyData,
IDataObject, IDataObject,
ILoadOptionsFunctions, ILoadOptionsFunctions,
INodeExecutionData, INodeExecutionData,
@ -17,10 +20,15 @@ import {
validateJSON, validateJSON,
} from './GenericFunctions'; } from './GenericFunctions';
import {
issueAttachmentFields,
issueAttachmentOperations,
} from './IssueAttachmentDescription';
import { import {
issueCommentFields, issueCommentFields,
issueCommentOperations, issueCommentOperations,
} from './IssueCommentDescription'; } from './IssueCommentDescription';
import { import {
issueFields, issueFields,
@ -33,13 +41,13 @@ import {
INotificationRecipients, INotificationRecipients,
INotify, INotify,
NotificationRecipientsRestrictions, NotificationRecipientsRestrictions,
} from './IssueInterface'; } from './IssueInterface';
export class Jira implements INodeType { export class Jira implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
displayName: 'Jira Software', displayName: 'Jira Software',
name: 'jira', name: 'jira',
icon: 'file:jira.png', icon: 'file:jira.svg',
group: ['output'], group: ['output'],
version: 1, version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
@ -101,6 +109,11 @@ export class Jira implements INodeType {
value: 'issue', value: 'issue',
description: 'Creates an issue or, where the option to create subtasks is enabled in Jira, a subtask', description: 'Creates an issue or, where the option to create subtasks is enabled in Jira, a subtask',
}, },
{
name: 'Issue Attachment',
value: 'issueAttachment',
description: 'Add, remove, and get an attachment from an issue.',
},
{ {
name: 'Issue Comment', name: 'Issue Comment',
value: 'issueComment', value: 'issueComment',
@ -112,6 +125,8 @@ export class Jira implements INodeType {
}, },
...issueOperations, ...issueOperations,
...issueFields, ...issueFields,
...issueAttachmentOperations,
...issueAttachmentFields,
...issueCommentOperations, ...issueCommentOperations,
...issueCommentFields, ...issueCommentFields,
], ],
@ -176,7 +191,7 @@ export class Jira implements INodeType {
} }
} else { } else {
for (const issueType of issueTypes) { for (const issueType of issueTypes) {
if (issueType.scope === undefined || issueType.scope.project.id === projectId) { if (issueType.scope !== undefined && issueType.scope.project.id === projectId) {
const issueTypeName = issueType.name; const issueTypeName = issueType.name;
const issueTypeId = issueType.id; const issueTypeId = issueType.id;
@ -193,7 +208,6 @@ export class Jira implements INodeType {
if (a.name > b.name) { return 1; } if (a.name > b.name) { return 1; }
return 0; return 0;
}); });
return returnData; return returnData;
}, },
@ -342,6 +356,32 @@ export class Jira implements INodeType {
return returnData; return returnData;
}, },
// Get all the custom fields to display them to user so that he can
// select them easily
async getCustomFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const operation = this.getCurrentNodeParameter('operation') as string;
let projectId;
if (operation === 'create') {
projectId = this.getCurrentNodeParameter('project');
} else {
const issueKey = this.getCurrentNodeParameter('issueKey');
const { fields: { project: { id } } } = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}`, 'GET', {}, {});
projectId = id;
}
const fields = await jiraSoftwareCloudApiRequest.call(this, `/api/2/field`, 'GET');
for (const field of fields) {
if (field.custom === true && field.scope && field.scope.project && field.scope.project.id === projectId) {
returnData.push({
name: field.name,
value: field.id,
});
}
}
return returnData;
},
}, },
}; };
@ -356,11 +396,10 @@ export class Jira implements INodeType {
const operation = this.getNodeParameter('operation', 0) as string; const operation = this.getNodeParameter('operation', 0) as string;
const jiraVersion = this.getNodeParameter('jiraVersion', 0) as string; const jiraVersion = this.getNodeParameter('jiraVersion', 0) as string;
if (resource === 'issue') {
for (let i = 0; i < length; i++) { //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-post
if (resource === 'issue') { if (operation === 'create') {
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-post for (let i = 0; i < length; i++) {
if (operation === 'create') {
const summary = this.getNodeParameter('summary', i) as string; const summary = this.getNodeParameter('summary', i) as string;
const projectId = this.getNodeParameter('project', i) as string; const projectId = this.getNodeParameter('project', i) as string;
const issueTypeId = this.getNodeParameter('issueType', i) as string; const issueTypeId = this.getNodeParameter('issueType', i) as string;
@ -403,6 +442,13 @@ export class Jira implements INodeType {
if (additionalFields.updateHistory) { if (additionalFields.updateHistory) {
qs.updateHistory = additionalFields.updateHistory as boolean; qs.updateHistory = additionalFields.updateHistory as boolean;
} }
if (additionalFields.customFieldsUi) {
const customFields = (additionalFields.customFieldsUi as IDataObject).customFieldsValues as IDataObject[];
if (customFields) {
const data = customFields.reduce((obj, value) => Object.assign(obj, { [`${value.fieldId}`]: value.fieldValue }), {});
Object.assign(fields, data);
}
}
const issueTypes = await jiraSoftwareCloudApiRequest.call(this, '/api/2/issuetype', 'GET', body, qs); const issueTypes = await jiraSoftwareCloudApiRequest.call(this, '/api/2/issuetype', 'GET', body, qs);
const subtaskIssues = []; const subtaskIssues = [];
for (const issueType of issueTypes) { for (const issueType of issueTypes) {
@ -422,9 +468,12 @@ export class Jira implements INodeType {
} }
body.fields = fields; body.fields = fields;
responseData = await jiraSoftwareCloudApiRequest.call(this, '/api/2/issue', 'POST', body); responseData = await jiraSoftwareCloudApiRequest.call(this, '/api/2/issue', 'POST', body);
returnData.push(responseData);
} }
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-put }
if (operation === 'update') { //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-put
if (operation === 'update') {
for (let i = 0; i < length; i++) {
const issueKey = this.getNodeParameter('issueKey', i) as string; const issueKey = this.getNodeParameter('issueKey', i) as string;
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
const body: IIssue = {}; const body: IIssue = {};
@ -462,6 +511,13 @@ export class Jira implements INodeType {
if (updateFields.description) { if (updateFields.description) {
fields.description = updateFields.description as string; fields.description = updateFields.description as string;
} }
if (updateFields.customFieldsUi) {
const customFields = (updateFields.customFieldsUi as IDataObject).customFieldsValues as IDataObject[];
if (customFields) {
const data = customFields.reduce((obj, value) => Object.assign(obj, { [`${value.fieldId}`]: value.fieldValue }), {});
Object.assign(fields, data);
}
}
const issueTypes = await jiraSoftwareCloudApiRequest.call(this, '/api/2/issuetype', 'GET', body); const issueTypes = await jiraSoftwareCloudApiRequest.call(this, '/api/2/issuetype', 'GET', body);
const subtaskIssues = []; const subtaskIssues = [];
for (const issueType of issueTypes) { for (const issueType of issueTypes) {
@ -486,10 +542,12 @@ export class Jira implements INodeType {
} }
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}`, 'PUT', body); responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}`, 'PUT', body);
responseData = { success: true }; returnData.push({ success: true });
} }
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-get }
if (operation === 'get') { //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-get
if (operation === 'get') {
for (let i = 0; i < length; i++) {
const issueKey = this.getNodeParameter('issueKey', i) as string; const issueKey = this.getNodeParameter('issueKey', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
if (additionalFields.fields) { if (additionalFields.fields) {
@ -507,12 +565,13 @@ export class Jira implements INodeType {
if (additionalFields.updateHistory) { if (additionalFields.updateHistory) {
qs.updateHistory = additionalFields.updateHistory as string; qs.updateHistory = additionalFields.updateHistory as string;
} }
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}`, 'GET', {}, qs); responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}`, 'GET', {}, qs);
returnData.push(responseData);
} }
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-search-post }
if (operation === 'getAll') { //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-search-post
if (operation === 'getAll') {
for (let i = 0; i < length; i++) {
const returnAll = this.getNodeParameter('returnAll', i) as boolean; const returnAll = this.getNodeParameter('returnAll', i) as boolean;
const options = this.getNodeParameter('options', i) as IDataObject; const options = this.getNodeParameter('options', i) as IDataObject;
const body: IDataObject = {}; const body: IDataObject = {};
@ -533,21 +592,27 @@ export class Jira implements INodeType {
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/search`, 'POST', body); responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/search`, 'POST', body);
responseData = responseData.issues; responseData = responseData.issues;
} }
returnData.push.apply(returnData, responseData);
} }
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-changelog-get }
if (operation === 'changelog') { //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-changelog-get
if (operation === 'changelog') {
for (let i = 0; i < length; i++) {
const issueKey = this.getNodeParameter('issueKey', i) as string; const issueKey = this.getNodeParameter('issueKey', i) as string;
const returnAll = this.getNodeParameter('returnAll', i) as boolean; const returnAll = this.getNodeParameter('returnAll', i) as boolean;
if (returnAll) { if (returnAll) {
responseData = await jiraSoftwareCloudApiRequestAllItems.call(this, 'values',`/api/2/issue/${issueKey}/changelog`, 'GET'); responseData = await jiraSoftwareCloudApiRequestAllItems.call(this, 'values', `/api/2/issue/${issueKey}/changelog`, 'GET');
} else { } else {
qs.maxResults = this.getNodeParameter('limit', i) as number; qs.maxResults = this.getNodeParameter('limit', i) as number;
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}/changelog`, 'GET', {}, qs); responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}/changelog`, 'GET', {}, qs);
responseData = responseData.values; responseData = responseData.values;
} }
returnData.push.apply(returnData, responseData);
} }
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-notify-post }
if (operation === 'notify') { //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-notify-post
if (operation === 'notify') {
for (let i = 0; i < length; i++) {
const issueKey = this.getNodeParameter('issueKey', i) as string; const issueKey = this.getNodeParameter('issueKey', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const jsonActive = this.getNodeParameter('jsonParameters', 0) as boolean; const jsonActive = this.getNodeParameter('jsonParameters', 0) as boolean;
@ -606,7 +671,7 @@ export class Jira implements INodeType {
const notificationRecipientsRestrictions: NotificationRecipientsRestrictions = {}; const notificationRecipientsRestrictions: NotificationRecipientsRestrictions = {};
if (notificationRecipientsRestrictionsValues) { if (notificationRecipientsRestrictionsValues) {
// @ts-ignore // @ts-ignore
if (notificationRecipientsRestrictionsValues.groups. length > 0) { if (notificationRecipientsRestrictionsValues.groups.length > 0) {
// @ts-ignore // @ts-ignore
notificationRecipientsRestrictions.groups = notificationRecipientsRestrictionsValues.groups.map(group => { notificationRecipientsRestrictions.groups = notificationRecipientsRestrictionsValues.groups.map(group => {
return { return {
@ -627,10 +692,12 @@ export class Jira implements INodeType {
} }
} }
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}/notify`, 'POST', body, qs); responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}/notify`, 'POST', body, qs);
returnData.push(responseData);
} }
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-transitions-get }
if (operation === 'transitions') { //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-transitions-get
if (operation === 'transitions') {
for (let i = 0; i < length; i++) {
const issueKey = this.getNodeParameter('issueKey', i) as string; const issueKey = this.getNodeParameter('issueKey', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
if (additionalFields.transitionId) { if (additionalFields.transitionId) {
@ -644,19 +711,118 @@ export class Jira implements INodeType {
} }
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}/transitions`, 'GET', {}, qs); responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}/transitions`, 'GET', {}, qs);
responseData = responseData.transitions; responseData = responseData.transitions;
returnData.push.apply(returnData, responseData);
} }
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-delete }
if (operation === 'delete') { //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-delete
if (operation === 'delete') {
for (let i = 0; i < length; i++) {
const issueKey = this.getNodeParameter('issueKey', i) as string; const issueKey = this.getNodeParameter('issueKey', i) as string;
const deleteSubtasks = this.getNodeParameter('deleteSubtasks', i) as boolean; const deleteSubtasks = this.getNodeParameter('deleteSubtasks', i) as boolean;
qs.deleteSubtasks = deleteSubtasks; qs.deleteSubtasks = deleteSubtasks;
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}`, 'DELETE', {}, qs); responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}`, 'DELETE', {}, qs);
returnData.push({ success: true });
} }
} }
if (resource === 'issueComment') { }
//https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-comments/#api-rest-api-3-issue-issueidorkey-comment-post if (resource === 'issueAttachment') {
if (operation === 'add') { //https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-attachments/#api-rest-api-3-issue-issueidorkey-attachments-post
if (operation === 'add') {
for (let i = 0; i < length; i++) {
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string;
const issueKey = this.getNodeParameter('issueKey', i) as string;
if (items[i].binary === undefined) {
throw new Error('No binary data exists on item!');
}
const item = items[i].binary as IBinaryKeyData;
const binaryData = item[binaryPropertyName] as IBinaryData;
if (binaryData === undefined) {
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`);
}
responseData = await jiraSoftwareCloudApiRequest.call(
this,
`/api/3/issue/${issueKey}/attachments`,
'POST',
{},
{},
undefined,
{
formData: {
file: {
value: Buffer.from(binaryData.data, BINARY_ENCODING),
options: {
filename: binaryData.fileName,
},
},
},
},
);
returnData.push.apply(returnData, responseData);
}
}
//https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-attachments/#api-rest-api-3-attachment-id-delete
if (operation === 'remove') {
for (let i = 0; i < length; i++) {
const attachmentId = this.getNodeParameter('attachmentId', i) as string;
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/attachment/${attachmentId}`, 'DELETE', {}, qs);
returnData.push({ success: true });
}
}
//https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-attachments/#api-rest-api-3-attachment-id-get
if (operation === 'get') {
const download = this.getNodeParameter('download', 0) as boolean;
for (let i = 0; i < length; i++) {
const attachmentId = this.getNodeParameter('attachmentId', i) as string;
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/attachment/${attachmentId}`, 'GET', {}, qs);
returnData.push({ json: responseData });
}
if (download) {
const binaryPropertyName = this.getNodeParameter('binaryProperty', 0) as string;
for (const [index, attachment] of returnData.entries()) {
returnData[index]['binary'] = {};
//@ts-ignore
const buffer = await jiraSoftwareCloudApiRequest.call(this, '', 'GET', {}, {}, attachment?.json!.content, { json: false, encoding: null });
//@ts-ignore
returnData[index]['binary'][binaryPropertyName] = await this.helpers.prepareBinaryData(buffer, attachment.json.filename, attachment.json.mimeType);
}
}
}
if (operation === 'getAll') {
const download = this.getNodeParameter('download', 0) as boolean;
for (let i = 0; i < length; i++) {
const issueKey = this.getNodeParameter('issueKey', i) as string;
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
const { fields: { attachment } } = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}`, 'GET', {}, qs);
responseData = attachment;
if (returnAll === false) {
const limit = this.getNodeParameter('limit', i) as number;
responseData = responseData.slice(0, limit);
}
responseData = responseData.map((data: IDataObject) => ({ json: data }));
returnData.push.apply(returnData, responseData);
}
if (download) {
const binaryPropertyName = this.getNodeParameter('binaryProperty', 0) as string;
for (const [index, attachment] of returnData.entries()) {
returnData[index]['binary'] = {};
//@ts-ignore
const buffer = await jiraSoftwareCloudApiRequest.call(this, '', 'GET', {}, {}, attachment.json.content, { json: false, encoding: null });
//@ts-ignore
returnData[index]['binary'][binaryPropertyName] = await this.helpers.prepareBinaryData(buffer, attachment.json.filename, attachment.json.mimeType);
}
}
}
}
if (resource === 'issueComment') {
//https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-comments/#api-rest-api-3-issue-issueidorkey-comment-post
if (operation === 'add') {
for (let i = 0; i < length; i++) {
const jsonParameters = this.getNodeParameter('jsonParameters', 0) as boolean; const jsonParameters = this.getNodeParameter('jsonParameters', 0) as boolean;
const issueKey = this.getNodeParameter('issueKey', i) as string; const issueKey = this.getNodeParameter('issueKey', i) as string;
const options = this.getNodeParameter('options', i) as IDataObject; const options = this.getNodeParameter('options', i) as IDataObject;
@ -697,18 +863,23 @@ export class Jira implements INodeType {
} }
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/issue/${issueKey}/comment`, 'POST', body, qs); responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/issue/${issueKey}/comment`, 'POST', body, qs);
returnData.push(responseData);
} }
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-get }
if (operation === 'get') { //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-get
if (operation === 'get') {
for (let i = 0; i < length; i++) {
const issueKey = this.getNodeParameter('issueKey', i) as string; const issueKey = this.getNodeParameter('issueKey', i) as string;
const commentId = this.getNodeParameter('commentId', i) as string; const commentId = this.getNodeParameter('commentId', i) as string;
const options = this.getNodeParameter('options', i) as IDataObject; const options = this.getNodeParameter('options', i) as IDataObject;
Object.assign(qs, options); Object.assign(qs, options);
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/issue/${issueKey}/comment/${commentId}`, 'GET', {}, qs); responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/issue/${issueKey}/comment/${commentId}`, 'GET', {}, qs);
returnData.push(responseData);
} }
//https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-comments/#api-rest-api-3-issue-issueidorkey-comment-get }
if (operation === 'getAll') { //https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-comments/#api-rest-api-3-issue-issueidorkey-comment-get
if (operation === 'getAll') {
for (let i = 0; i < length; i++) {
const issueKey = this.getNodeParameter('issueKey', i) as string; const issueKey = this.getNodeParameter('issueKey', i) as string;
const returnAll = this.getNodeParameter('returnAll', i) as boolean; const returnAll = this.getNodeParameter('returnAll', i) as boolean;
const options = this.getNodeParameter('options', i) as IDataObject; const options = this.getNodeParameter('options', i) as IDataObject;
@ -722,16 +893,21 @@ export class Jira implements INodeType {
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/issue/${issueKey}/comment`, 'GET', body, qs); responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/issue/${issueKey}/comment`, 'GET', body, qs);
responseData = responseData.comments; responseData = responseData.comments;
} }
returnData.push.apply(returnData, responseData);
} }
//https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-comments/#api-rest-api-3-issue-issueidorkey-comment-id-delete }
if (operation === 'remove') { //https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-comments/#api-rest-api-3-issue-issueidorkey-comment-id-delete
if (operation === 'remove') {
for (let i = 0; i < length; i++) {
const issueKey = this.getNodeParameter('issueKey', i) as string; const issueKey = this.getNodeParameter('issueKey', i) as string;
const commentId = this.getNodeParameter('commentId', i) as string; const commentId = this.getNodeParameter('commentId', i) as string;
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/issue/${issueKey}/comment/${commentId}`, 'DELETE', {}, qs); responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/issue/${issueKey}/comment/${commentId}`, 'DELETE', {}, qs);
responseData = { success: true }; returnData.push({ success: true });
} }
//https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-comments/#api-rest-api-3-issue-issueidorkey-comment-id-put }
if (operation === 'update') { //https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-comments/#api-rest-api-3-issue-issueidorkey-comment-id-put
if (operation === 'update') {
for (let i = 0; i < length; i++) {
const issueKey = this.getNodeParameter('issueKey', i) as string; const issueKey = this.getNodeParameter('issueKey', i) as string;
const commentId = this.getNodeParameter('commentId', i) as string; const commentId = this.getNodeParameter('commentId', i) as string;
const options = this.getNodeParameter('options', i) as IDataObject; const options = this.getNodeParameter('options', i) as IDataObject;
@ -771,14 +947,15 @@ export class Jira implements INodeType {
Object.assign(body, { body: json }); Object.assign(body, { body: json });
} }
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/issue/${issueKey}/comment/${commentId}`, 'PUT', body, qs); responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/issue/${issueKey}/comment/${commentId}`, 'PUT', body, qs);
returnData.push(responseData);
} }
} }
if (Array.isArray(responseData)) {
returnData.push.apply(returnData, responseData as IDataObject[]);
} else {
returnData.push(responseData as IDataObject);
}
} }
return [this.helpers.returnJsonArray(returnData)];
if (resource === 'issueAttachment' && (operation === 'getAll' || operation === 'get')) {
return this.prepareOutputData(returnData as unknown as INodeExecutionData[]);
} else {
return [this.helpers.returnJsonArray(returnData)];
}
} }
} }

View file

@ -23,7 +23,7 @@ export class JiraTrigger implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
displayName: 'Jira Trigger', displayName: 'Jira Trigger',
name: 'jiraTrigger', name: 'jiraTrigger',
icon: 'file:jira.png', icon: 'file:jira.svg',
group: ['trigger'], group: ['trigger'],
version: 1, version: 1,
description: 'Starts the workflow when Jira events occurs.', description: 'Starts the workflow when Jira events occurs.',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 872 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 68.25 71.25" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><use xlink:href="#C" x="3.125" y="3.125"/><defs><linearGradient id="A" x1="91.90%" y1="40.22%" x2="28.49%" y2="81.63%"><stop offset="18%" stop-color="#0052cc"/><stop offset="100%" stop-color="#2684ff"/></linearGradient><linearGradient id="B" x1="8.70%" y1="59.17%" x2="72.26%" y2="17.99%"><stop offset="18%" stop-color="#0052cc"/><stop offset="100%" stop-color="#2684ff"/></linearGradient></defs><symbol id="C" overflow="visible"><g stroke="none" fill-rule="nonzero"><path d="M61.161 30.211L30.95 0 .74 30.211a2.54 2.54 0 0 0 0 3.581l30.211 30.21 30.211-30.21a2.54 2.54 0 0 0 0-3.581zM30.95 41.46l-9.462-9.462 9.462-9.462 9.462 9.462z" fill="#2684ff"/><path d="M30.95 22.599C24.755 16.405 24.724 6.37 30.881.138L10.114 20.774l11.268 11.268z" fill="url(#A)"/><path d="M40.437 31.973L30.95 41.46a15.93 15.93 0 0 1 0 22.536l20.749-20.749z" fill="url(#B)"/></g></symbol></svg>

After

Width:  |  Height:  |  Size: 1 KiB