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 {
OptionsWithUri,
} from 'request';
} from 'request';
import {
IExecuteFunctions,
@ -14,7 +14,7 @@ import {
IDataObject,
} 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;
const jiraVersion = this.getNodeParameter('jiraVersion', 0) as string;
@ -43,6 +43,7 @@ export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecut
Authorization: `Basic ${data}`,
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Atlassian-Token': 'no-check',
},
method,
qs: query,
@ -51,6 +52,18 @@ export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecut
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 {
return await this.helpers.request!(options);
} catch (error) {
@ -82,7 +95,7 @@ export async function jiraSoftwareCloudApiRequestAllItems(this: IHookFunctions |
query.startAt = 0;
body.startAt = 0;
query.maxResults = 100;
body.maxResults = 100;
body.maxResults = 100;
do {
responseData = await jiraSoftwareCloudApiRequest.call(this, endpoint, method, body, query);
@ -106,7 +119,7 @@ export function validateJSON(json: string | undefined): any { // tslint:disable-
return result;
}
export function eventExists (currentEvents : string[], webhookEvents: string[]) {
export function eventExists(currentEvents: string[], webhookEvents: string[]) {
for (const currentEvent of currentEvents) {
if (!webhookEvents.includes(currentEvent)) {
return false;
@ -115,7 +128,7 @@ export function eventExists (currentEvents : string[], webhookEvents: string[])
return true;
}
export function getId (url: string) {
export function getId(url: string) {
return url.split('/').pop();
}
@ -159,4 +172,4 @@ export const allEvents = [
'worklog_created',
'worklog_updated',
'worklog_deleted',
];
];

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

@ -485,4 +485,4 @@ export const issueCommentFields = [
},
],
},
] as INodeProperties[];
] as INodeProperties[];

View file

@ -1,4 +1,4 @@
import {
import {
INodeProperties,
} from 'n8n-workflow';
@ -63,9 +63,9 @@ export const issueOperations = [
export const issueFields = [
/* -------------------------------------------------------------------------- */
/* issue:create */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* issue:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'Project',
name: 'project',
@ -155,7 +155,6 @@ export const issueFields = [
loadOptionsMethod: 'getUsers',
},
default: '',
required : false,
description: 'Assignee',
},
{
@ -163,9 +162,46 @@ export const issueFields = [
name: 'description',
type: 'string',
default: '',
required : false,
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',
name: 'labels',
@ -174,9 +210,8 @@ export const issueFields = [
loadOptionsMethod: 'getLabels',
},
default: [],
required : false,
description: 'Labels',
displayOptions: {
displayOptions: {
show: {
'/jiraVersion': [
'cloud',
@ -189,9 +224,8 @@ export const issueFields = [
name: 'serverLabels',
type: 'string',
default: [],
required : false,
description: 'Labels',
displayOptions: {
displayOptions: {
show: {
'/jiraVersion': [
'server',
@ -206,7 +240,6 @@ export const issueFields = [
displayName: 'Parent Issue Key',
name: 'parentIssueKey',
type: 'string',
required: false,
default: '',
description: 'Parent Issue Key',
},
@ -218,7 +251,6 @@ export const issueFields = [
loadOptionsMethod: 'getPriorities',
},
default: '',
required : false,
description: 'Priority',
},
{
@ -226,16 +258,15 @@ export const issueFields = [
name: 'updateHistory',
type: 'boolean',
default: false,
required : false,
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.`,
},
],
},
/* -------------------------------------------------------------------------- */
/* issue:update */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* issue:update */
/* -------------------------------------------------------------------------- */
{
displayName: 'Issue Key',
name: 'issueKey',
@ -279,7 +310,6 @@ export const issueFields = [
loadOptionsMethod: 'getUsers',
},
default: '',
required : false,
description: 'Assignee',
},
{
@ -287,14 +317,50 @@ export const issueFields = [
name: 'description',
type: 'string',
default: '',
required : false,
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',
name: 'issueType',
type: 'options',
required: false,
typeOptions: {
loadOptionsMethod: 'getIssueTypes',
},
@ -309,9 +375,8 @@ export const issueFields = [
loadOptionsMethod: 'getLabels',
},
default: [],
required : false,
description: 'Labels',
displayOptions: {
displayOptions: {
show: {
'/jiraVersion': [
'cloud',
@ -324,9 +389,8 @@ export const issueFields = [
name: 'serverLabels',
type: 'string',
default: [],
required : false,
description: 'Labels',
displayOptions: {
displayOptions: {
show: {
'/jiraVersion': [
'server',
@ -341,7 +405,6 @@ export const issueFields = [
displayName: 'Parent Issue Key',
name: 'parentIssueKey',
type: 'string',
required: false,
default: '',
description: 'Parent Issue Key',
},
@ -353,14 +416,12 @@ export const issueFields = [
loadOptionsMethod: 'getPriorities',
},
default: '',
required : false,
description: 'Priority',
},
{
displayName: 'Summary',
name: 'summary',
type: 'string',
required: false,
default: '',
description: 'Summary',
},
@ -371,16 +432,15 @@ export const issueFields = [
typeOptions: {
loadOptionsMethod: 'getTransitions',
},
required: false,
default: '',
description: 'The ID of the issue status.',
},
],
},
/* -------------------------------------------------------------------------- */
/* issue:delete */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* issue:delete */
/* -------------------------------------------------------------------------- */
{
displayName: 'Issue Key',
name: 'issueKey',
@ -418,9 +478,9 @@ export const issueFields = [
description: 'Delete Subtasks',
},
/* -------------------------------------------------------------------------- */
/* issue:get */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* issue:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Issue Key',
name: 'issueKey',
@ -460,7 +520,6 @@ export const issueFields = [
displayName: 'Expand',
name: 'expand',
type: 'string',
required: false,
default: '',
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/>
@ -477,7 +536,6 @@ export const issueFields = [
displayName: 'Fields',
name: 'fields',
type: 'string',
required: false,
default: '',
description: `A list of fields to return for the issue.<br/>
This parameter accepts a comma-separated list.<br/>
@ -490,7 +548,6 @@ export const issueFields = [
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.<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',
name: 'properties',
type: 'string',
required: false,
default: '',
description: `A list of issue properties to return for the issue.<br/>
This parameter accepts a comma-separated list. Allowed values:<br/>
@ -516,7 +572,6 @@ export const issueFields = [
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
@ -525,9 +580,9 @@ export const issueFields = [
],
},
/* -------------------------------------------------------------------------- */
/* issue:getAll */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* issue:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Return All',
name: 'returnAll',
@ -649,7 +704,6 @@ export const issueFields = [
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.<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',
name: 'issueKey',
@ -729,9 +783,9 @@ export const issueFields = [
default: 50,
description: 'How many results to return.',
},
/* -------------------------------------------------------------------------- */
/* issue:notify */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* issue:notify */
/* -------------------------------------------------------------------------- */
{
displayName: 'Issue Key',
name: 'issueKey',
@ -791,7 +845,6 @@ export const issueFields = [
typeOptions: {
alwaysOpenEditWindow: true,
},
required: false,
default: '',
description: 'The HTML body of the email notification for the issue.',
},
@ -799,7 +852,6 @@ export const issueFields = [
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.`,
@ -811,7 +863,6 @@ export const issueFields = [
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.`,
@ -906,7 +957,6 @@ export const issueFields = [
typeOptions: {
alwaysOpenEditWindow: true,
},
required: false,
displayOptions: {
show: {
resource: [
@ -983,7 +1033,6 @@ export const issueFields = [
typeOptions: {
alwaysOpenEditWindow: true,
},
required: false,
displayOptions: {
show: {
resource: [
@ -1001,9 +1050,9 @@ export const issueFields = [
description: 'Restricts the notifications to users with the specified permissions.',
},
/* -------------------------------------------------------------------------- */
/* issue:transitions */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* issue:transitions */
/* -------------------------------------------------------------------------- */
{
displayName: 'Issue Key',
name: 'issueKey',
@ -1043,7 +1092,6 @@ export const issueFields = [
displayName: 'Expand',
name: 'expand',
type: 'string',
required: false,
default: '',
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/>
@ -1054,7 +1102,6 @@ export const issueFields = [
displayName: 'Transition ID',
name: 'transitionId',
type: 'string',
required: false,
default: '',
description: 'The ID of the transition.',
},
@ -1062,11 +1109,10 @@ export const issueFields = [
displayName: 'Skip Remote Only Condition',
name: 'skipRemoteOnlyCondition',
type: 'boolean',
required: false,
default: false,
description: `Indicates whether transitions with the condition Hide<br/>
From User Condition are included in the response.`,
},
],
},
] as INodeProperties[];
] as INodeProperties[];

View file

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

View file

@ -1,8 +1,11 @@
import {
BINARY_ENCODING,
IExecuteFunctions,
} from 'n8n-core';
import {
IBinaryData,
IBinaryKeyData,
IDataObject,
ILoadOptionsFunctions,
INodeExecutionData,
@ -17,10 +20,15 @@ import {
validateJSON,
} from './GenericFunctions';
import {
issueAttachmentFields,
issueAttachmentOperations,
} from './IssueAttachmentDescription';
import {
issueCommentFields,
issueCommentOperations,
} from './IssueCommentDescription';
} from './IssueCommentDescription';
import {
issueFields,
@ -33,13 +41,13 @@ import {
INotificationRecipients,
INotify,
NotificationRecipientsRestrictions,
} from './IssueInterface';
} from './IssueInterface';
export class Jira implements INodeType {
description: INodeTypeDescription = {
displayName: 'Jira Software',
name: 'jira',
icon: 'file:jira.png',
icon: 'file:jira.svg',
group: ['output'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
@ -101,6 +109,11 @@ export class Jira implements INodeType {
value: 'issue',
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',
value: 'issueComment',
@ -112,6 +125,8 @@ export class Jira implements INodeType {
},
...issueOperations,
...issueFields,
...issueAttachmentOperations,
...issueAttachmentFields,
...issueCommentOperations,
...issueCommentFields,
],
@ -176,7 +191,7 @@ export class Jira implements INodeType {
}
} else {
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 issueTypeId = issueType.id;
@ -193,7 +208,6 @@ export class Jira implements INodeType {
if (a.name > b.name) { return 1; }
return 0;
});
return returnData;
},
@ -342,6 +356,32 @@ export class Jira implements INodeType {
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 jiraVersion = this.getNodeParameter('jiraVersion', 0) as string;
for (let i = 0; i < length; i++) {
if (resource === 'issue') {
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-post
if (operation === 'create') {
if (resource === 'issue') {
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-post
if (operation === 'create') {
for (let i = 0; i < length; i++) {
const summary = this.getNodeParameter('summary', i) as string;
const projectId = this.getNodeParameter('project', i) as string;
const issueTypeId = this.getNodeParameter('issueType', i) as string;
@ -403,6 +442,13 @@ export class Jira implements INodeType {
if (additionalFields.updateHistory) {
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 subtaskIssues = [];
for (const issueType of issueTypes) {
@ -422,9 +468,12 @@ export class Jira implements INodeType {
}
body.fields = fields;
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 updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
const body: IIssue = {};
@ -462,6 +511,13 @@ export class Jira implements INodeType {
if (updateFields.description) {
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 subtaskIssues = [];
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 = { 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 additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
if (additionalFields.fields) {
@ -507,12 +565,13 @@ export class Jira implements INodeType {
if (additionalFields.updateHistory) {
qs.updateHistory = additionalFields.updateHistory as string;
}
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 options = this.getNodeParameter('options', i) as IDataObject;
const body: IDataObject = {};
@ -533,21 +592,27 @@ export class Jira implements INodeType {
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/search`, 'POST', body);
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 returnAll = this.getNodeParameter('returnAll', i) as boolean;
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 {
qs.maxResults = this.getNodeParameter('limit', i) as number;
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}/changelog`, 'GET', {}, qs);
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 additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const jsonActive = this.getNodeParameter('jsonParameters', 0) as boolean;
@ -606,7 +671,7 @@ export class Jira implements INodeType {
const notificationRecipientsRestrictions: NotificationRecipientsRestrictions = {};
if (notificationRecipientsRestrictionsValues) {
// @ts-ignore
if (notificationRecipientsRestrictionsValues.groups. length > 0) {
if (notificationRecipientsRestrictionsValues.groups.length > 0) {
// @ts-ignore
notificationRecipientsRestrictions.groups = notificationRecipientsRestrictionsValues.groups.map(group => {
return {
@ -627,10 +692,12 @@ export class Jira implements INodeType {
}
}
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 additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
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 = 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 deleteSubtasks = this.getNodeParameter('deleteSubtasks', i) as boolean;
qs.deleteSubtasks = deleteSubtasks;
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 (operation === 'add') {
}
if (resource === 'issueAttachment') {
//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 issueKey = this.getNodeParameter('issueKey', i) as string;
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);
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 commentId = this.getNodeParameter('commentId', i) as string;
const options = this.getNodeParameter('options', i) as IDataObject;
Object.assign(qs, options);
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 returnAll = this.getNodeParameter('returnAll', i) as boolean;
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 = 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 commentId = this.getNodeParameter('commentId', i) as string;
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 commentId = this.getNodeParameter('commentId', i) as string;
const options = this.getNodeParameter('options', i) as IDataObject;
@ -771,14 +947,15 @@ export class Jira implements INodeType {
Object.assign(body, { body: json });
}
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 = {
displayName: 'Jira Trigger',
name: 'jiraTrigger',
icon: 'file:jira.png',
icon: 'file:jira.svg',
group: ['trigger'],
version: 1,
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