🔀 Merge branch 'feature/jira' of https://github.com/RicardoE105/n8n into RicardoE105-feature/jira

This commit is contained in:
Jan Oberhauser 2020-04-24 08:39:08 +02:00
commit 57d296d30a
4 changed files with 194 additions and 202 deletions

View file

@ -1,4 +1,6 @@
import { OptionsWithUri } from 'request';
import {
OptionsWithUri,
} from 'request';
import {
IExecuteFunctions,
@ -41,12 +43,12 @@ export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecut
try {
return await this.helpers.request!(options);
} catch (error) {
let errorMessage = error;
if (error.error && error.error.errorMessages) {
errorMessage = error.error.errorMessages;
}
const errorMessage = error.response.body.message || error.response.body.error || error.response.body.errors;
if (errorMessage !== undefined) {
throw new Error(errorMessage);
}
throw error;
}
}
export async function jiraSoftwareCloudApiRequestAllItems(this: IHookFunctions | IExecuteFunctions, propertyName: string, endpoint: string, method: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any

View file

@ -44,7 +44,7 @@ export const issueOperations = [
description: 'Creates an email notification for an issue and adds it to the mail queue.',
},
{
name: 'Transitions',
name: 'Status',
value: 'transitions',
description: `Returns either all transitions or a transition that can be performed by the user on an issue, based on the issue's status.`,
},
@ -101,6 +101,9 @@ export const issueFields = [
},
typeOptions: {
loadOptionsMethod: 'getIssueTypes',
loadOptionsDependsOn: [
'project',
],
},
description: 'Issue Types',
},
@ -139,36 +142,6 @@ export const issueFields = [
},
},
options: [
{
displayName: 'Parent Issue Key',
name: 'parentIssueKey',
type: 'string',
required: false,
default: '',
description: 'Parent Issue Key',
},
{
displayName: 'Labels',
name: 'labels',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getLabels',
},
default: [],
required : false,
description: 'Labels',
},
{
displayName: 'Priority',
name: 'priority',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getPriorities',
},
default: '',
required : false,
description: 'Priority',
},
{
displayName: 'Assignee',
name: 'assignee',
@ -188,6 +161,36 @@ export const issueFields = [
required : false,
description: 'Description',
},
{
displayName: 'Labels',
name: 'labels',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getLabels',
},
default: [],
required : false,
description: 'Labels',
},
{
displayName: 'Parent Issue Key',
name: 'parentIssueKey',
type: 'string',
required: false,
default: '',
description: 'Parent Issue Key',
},
{
displayName: 'Priority',
name: 'priority',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getPriorities',
},
default: '',
required : false,
description: 'Priority',
},
{
displayName: 'Update History',
name: 'updateHistory',
@ -238,55 +241,6 @@ export const issueFields = [
},
},
options: [
{
displayName: 'Issue Type',
name: 'issueType',
type: 'options',
required: false,
typeOptions: {
loadOptionsMethod: 'getIssueTypes',
},
default: '',
description: 'Issue Types',
},
{
displayName: 'Summary',
name: 'summary',
type: 'string',
required: false,
default: '',
description: 'Summary',
},
{
displayName: 'Parent Issue Key',
name: 'parentIssueKey',
type: 'string',
required: false,
default: '',
description: 'Parent Issue Key',
},
{
displayName: 'Labels',
name: 'labels',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getLabels',
},
default: [],
required : false,
description: 'Labels',
},
{
displayName: 'Priority',
name: 'priority',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getPriorities',
},
default: '',
required : false,
description: 'Priority',
},
{
displayName: 'Assignee',
name: 'assignee',
@ -306,6 +260,63 @@ export const issueFields = [
required : false,
description: 'Description',
},
{
displayName: 'Issue Type',
name: 'issueType',
type: 'options',
required: false,
typeOptions: {
loadOptionsMethod: 'getIssueTypes',
},
default: '',
description: 'Issue Types',
},
{
displayName: 'Labels',
name: 'labels',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getLabels',
},
default: [],
required : false,
description: 'Labels',
},
{
displayName: 'Parent Issue Key',
name: 'parentIssueKey',
type: 'string',
required: false,
default: '',
description: 'Parent Issue Key',
},
{
displayName: 'Priority',
name: 'priority',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getPriorities',
},
default: '',
required : false,
description: 'Priority',
},
{
displayName: 'Summary',
name: 'summary',
type: 'string',
required: false,
default: '',
description: 'Summary',
},
{
displayName: 'Status ID',
name: 'statusId',
type: 'string',
required: false,
default: '',
description: 'The ID of the issue status.',
},
],
},
@ -387,6 +398,23 @@ export const issueFields = [
},
},
options: [
{
displayName: 'Expand',
name: 'expand',
type: 'string',
required: false,
default: '',
description: `Use expand to include additional information about the issues in the response.<br/>
This parameter accepts a comma-separated list. Expand options include:<br/>
renderedFields Returns field values rendered in HTML format.<br/>
names Returns the display name of each field.<br/>
schema Returns the schema describing a field type.<br/>
transitions Returns all possible transitions for the issue.<br/>
editmeta Returns information about how each field can be edited.<br/>
changelog Returns a list of recent updates to an issue, sorted by date, starting from the most recent.<br/>
versionedRepresentations Returns a JSON array for each version of a field's value, with the highest number<br/>
representing the most recent version. Note: When included in the request, the fields parameter is ignored.`
},
{
displayName: 'Fields',
name: 'fields',
@ -410,23 +438,6 @@ export const issueFields = [
This parameter is useful where fields have been added by a connect app and a field's key<br/>
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.<br/>
This parameter accepts a comma-separated list. Expand options include:<br/>
renderedFields Returns field values rendered in HTML format.<br/>
names Returns the display name of each field.<br/>
schema Returns the schema describing a field type.<br/>
transitions Returns all possible transitions for the issue.<br/>
editmeta Returns information about how each field can be edited.<br/>
changelog Returns a list of recent updates to an issue, sorted by date, starting from the most recent.<br/>
versionedRepresentations Returns a JSON array for each version of a field's value, with the highest number<br/>
representing the most recent version. Note: When included in the request, the fields parameter is ignored.`
},
{
displayName: 'Properties',
name: 'properties',
@ -715,6 +726,17 @@ export const issueFields = [
},
},
options: [
{
displayName: 'HTML Body',
name: 'htmlBody',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
required: false,
default: '',
description: 'The HTML body of the email notification for the issue.',
},
{
displayName: 'Subject',
name: 'subject',
@ -736,17 +758,6 @@ export const issueFields = [
description: `The subject of the email notification for the issue.
If this is not specified, then the subject is set to the issue key and summary.`
},
{
displayName: 'HTML Body',
name: 'htmlBody',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
required: false,
default: '',
description: 'The HTML body of the email notification for the issue.',
},
],
},
{

View file

@ -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 {

View file

@ -1,28 +1,32 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeTypeDescription,
INodeExecutionData,
INodeType,
ILoadOptionsFunctions,
INodeExecutionData,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import {
jiraSoftwareCloudApiRequest,
jiraSoftwareCloudApiRequestAllItems,
validateJSON,
} from './GenericFunctions';
import {
issueOperations,
issueFields,
} from './IssueDescription';
import {
IIssue,
IFields,
INotify,
IIssue,
INotificationRecipients,
INotify,
NotificationRecipientsRestrictions,
} from './IssueInterface';
@ -37,7 +41,7 @@ export class JiraSoftwareCloud implements INodeType {
description: 'Consume Jira Software API',
defaults: {
name: 'Jira Software',
color: '#c02428',
color: '#4185f7',
},
inputs: ['main'],
outputs: ['main'],
@ -113,11 +117,8 @@ export class JiraSoftwareCloud implements INodeType {
if (jiraCloudCredentials === undefined) {
endpoint = '/project';
}
try {
projects = await jiraSoftwareCloudApiRequest.call(this, endpoint, 'GET');
} catch (err) {
throw new Error(`Jira Error: ${err}`);
}
if (projects.values && Array.isArray(projects.values)) {
projects = projects.values;
}
@ -135,14 +136,14 @@ 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<INodePropertyOptions[]> {
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) {
if (issueType.scope.project.id === projectId) {
const issueTypeName = issueType.name;
const issueTypeId = issueType.id;
@ -151,6 +152,7 @@ export class JiraSoftwareCloud implements INodeType {
value: issueTypeId,
});
}
}
return returnData;
},
@ -159,11 +161,9 @@ export class JiraSoftwareCloud implements INodeType {
async getLabels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
let labels;
try {
labels = await jiraSoftwareCloudApiRequest.call(this, '/label', 'GET');
} catch (err) {
throw new Error(`Jira Error: ${err}`);
}
for (const label of labels.values) {
const labelName = label;
const labelId = label;
@ -181,11 +181,9 @@ export class JiraSoftwareCloud implements INodeType {
async getPriorities(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
let priorities;
try {
priorities = await jiraSoftwareCloudApiRequest.call(this, '/priority', 'GET');
} catch (err) {
throw new Error(`Jira Error: ${err}`);
}
for (const priority of priorities) {
const priorityName = priority.name;
const priorityId = priority.id;
@ -203,11 +201,9 @@ export class JiraSoftwareCloud implements INodeType {
async getUsers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
let users;
try {
users = await jiraSoftwareCloudApiRequest.call(this, '/users/search', 'GET');
} catch (err) {
throw new Error(`Jira Error: ${err}`);
}
for (const user of users) {
const userName = user.displayName;
const userId = user.accountId;
@ -225,11 +221,9 @@ export class JiraSoftwareCloud implements INodeType {
async getGroups(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
let groups;
try {
groups = await jiraSoftwareCloudApiRequest.call(this, '/groups/picker', 'GET');
} catch (err) {
throw new Error(`Jira Error: ${err}`);
}
for (const group of groups.groups) {
const groupName = group.name;
const groupId = group.name;
@ -309,11 +303,7 @@ export class JiraSoftwareCloud implements INodeType {
};
}
body.fields = fields;
try {
responseData = await jiraSoftwareCloudApiRequest.call(this, '/issue', 'POST', body);
} catch (err) {
throw new Error(`Jira Error: ${JSON.stringify(err)}`);
}
}
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-put
if (operation === 'update') {
@ -363,11 +353,13 @@ export class JiraSoftwareCloud implements INodeType {
};
}
body.fields = fields;
try {
responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}`, 'PUT', body);
} catch (err) {
throw new Error(`Jira Error: ${JSON.stringify(err)}`);
if (updateFields.statusId) {
responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}/transitions`, 'POST', { transition: { id: updateFields.statusId } });
}
responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}`, 'PUT', body);
responseData = { success: true };
}
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-get
if (operation === 'get') {
@ -388,11 +380,9 @@ export class JiraSoftwareCloud implements INodeType {
if (additionalFields.updateHistory) {
qs.updateHistory = additionalFields.updateHistory as string;
}
try {
responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}`, 'GET', {}, qs);
} catch (err) {
throw new Error(`Jira Error: ${JSON.stringify(err)}`);
}
}
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-search-post
if (operation === 'getAll') {
@ -421,7 +411,6 @@ 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 {
@ -429,9 +418,6 @@ export class JiraSoftwareCloud implements INodeType {
responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}/changelog`, 'GET', {}, qs);
responseData = responseData.values;
}
} catch (err) {
throw new Error(`Jira Error: ${JSON.stringify(err)}`);
}
}
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-notify-post
if (operation === 'notify') {
@ -513,11 +499,8 @@ export class JiraSoftwareCloud implements INodeType {
body.restrict = notificationRecipientsRestrictionsJson;
}
}
try {
responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}/notify`, 'POST', body, qs);
} catch (err) {
throw new Error(`Jira Error: ${JSON.stringify(err)}`);
}
}
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-transitions-get
if (operation === 'transitions') {
@ -532,23 +515,16 @@ export class JiraSoftwareCloud implements INodeType {
if (additionalFields.skipRemoteOnlyCondition) {
qs.skipRemoteOnlyCondition = additionalFields.skipRemoteOnlyCondition as boolean;
}
try {
responseData = await jiraSoftwareCloudApiRequest.call(this, `/issue/${issueKey}/transitions`, 'GET', {}, qs);
responseData = responseData.transitions;
} catch (err) {
throw new Error(`Jira Error: ${JSON.stringify(err)}`);
}
}
//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)}`);
}
}
}
if (Array.isArray(responseData)) {