diff --git a/packages/nodes-base/credentials/JiraApi.credentials.ts b/packages/nodes-base/credentials/JiraApi.credentials.ts index ddb1996454..e6230c6fc0 100644 --- a/packages/nodes-base/credentials/JiraApi.credentials.ts +++ b/packages/nodes-base/credentials/JiraApi.credentials.ts @@ -3,14 +3,25 @@ import { NodePropertyTypes, } from 'n8n-workflow'; - export class JiraApi implements ICredentialType { name = 'jiraApi'; displayName = 'Jira API'; properties = [ { - displayName: 'API Key', - name: 'apiKey', + displayName: 'Email', + name: 'email', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'API Token', + name: 'apiToken', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Domain', + name: 'domain', type: 'string' as NodePropertyTypes, default: '', }, diff --git a/packages/nodes-base/nodes/Jira/GenericFunctions.ts b/packages/nodes-base/nodes/Jira/GenericFunctions.ts index f52062f8ae..8e71e1aa1b 100644 --- a/packages/nodes-base/nodes/Jira/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Jira/GenericFunctions.ts @@ -4,7 +4,8 @@ import { IExecuteFunctions, IHookFunctions, ILoadOptionsFunctions, - IExecuteSingleFunctions + IExecuteSingleFunctions, + BINARY_ENCODING } from 'n8n-core'; import { @@ -16,15 +17,16 @@ export async function jiraApiRequest(this: IHookFunctions | IExecuteFunctions | if (credentials === undefined) { throw new Error('No credentials got returned!'); } - + const data = Buffer.from(`${credentials!.email}:${credentials!.apiToken}`).toString(BINARY_ENCODING); + console.log(data) const headerWithAuthentication = Object.assign({}, - { Authorization: `Bearer ${credentials.apiKey}`, Accept: 'application/json' }); + { Authorization: `Basic ${data}`, Accept: 'application/json', 'Content-Type': 'application/json' }); const options: OptionsWithUri = { headers: headerWithAuthentication, method, qs: query, - uri: uri || `https://api.intercom.io${endpoint}`, + uri: uri || `${credentials.domain}/rest/api/2${endpoint}`, body, json: true }; diff --git a/packages/nodes-base/nodes/Jira/IssueDescription.ts b/packages/nodes-base/nodes/Jira/IssueDescription.ts new file mode 100644 index 0000000000..fd58d07b2a --- /dev/null +++ b/packages/nodes-base/nodes/Jira/IssueDescription.ts @@ -0,0 +1,191 @@ +import { INodeProperties } from "n8n-workflow"; + +export const issueOpeations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'issue', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new issue', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const issueFields = [ + +/* -------------------------------------------------------------------------- */ +/* issue:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Project', + name: 'project', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'create' + ] + }, + }, + typeOptions: { + loadOptionsMethod: 'getProjects', + }, + description: 'Project', + }, + { + displayName: 'Issue Type', + name: 'issueType', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'create' + ] + }, + }, + typeOptions: { + loadOptionsMethod: 'getIssueTypes', + }, + description: 'Issue Types', + }, + { + displayName: 'Summary', + name: 'summary', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'create', + ], + }, + }, + default: '', + description: 'Summary', + }, + { + displayName: 'Parent Issue Identifier', + name: 'parentIssueId', + type: 'options', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'create', + ], + }, + }, + default: 'id', + options: [ + { + name: 'ID', + value: 'id', + description: 'Issue ID', + }, + { + name: 'Key', + value: 'key', + description: 'Issue Key', + + } + ], + description: 'Parent Issue Identifier', + }, + { + displayName: 'Parent Issue Identifier Value', + name: 'parentIssueIdValue', + type: 'string', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'create', + ], + }, + }, + default: '', + description: 'Parent Issue ID/Key valie', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + 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', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + default: [], + required : false, + description: 'Assignee', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Jira/Jira.node.ts b/packages/nodes-base/nodes/Jira/Jira.node.ts index df38733e83..aac2b21d31 100644 --- a/packages/nodes-base/nodes/Jira/Jira.node.ts +++ b/packages/nodes-base/nodes/Jira/Jira.node.ts @@ -14,6 +14,10 @@ import { jiraApiRequestAllItems, validateJSON, } from './GenericFunctions'; +import { + issueOpeations, + issueFields, +} from './IssueDescription'; export class Jira implements INodeType { description: INodeTypeDescription = { @@ -51,10 +55,194 @@ export class Jira implements INodeType { default: 'issue', description: 'Resource to consume.', }, + ...issueOpeations, + ...issueFields, ], }; + methods = { + loadOptions: { + // Get all the projects to display them to user so that he can + // select them easily + async getProjects(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let projects; + try { + projects = await jiraApiRequest.call(this, '/project/search', 'GET'); + } catch (err) { + throw new Error(`Jira Error: ${err}`); + } + for (const project of projects.values) { + const projectName = project.name; + const projectId = project.id; + + returnData.push({ + name: projectName, + value: projectId, + }); + } + return returnData; + }, + + // Get all the issue types to display them to user so that he can + // select them easily + async getIssueTypes(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let issueTypes; + try { + issueTypes = await jiraApiRequest.call(this, '/issuetype', 'GET'); + } catch (err) { + throw new Error(`Jira Error: ${err}`); + } + for (const issueType of issueTypes) { + const issueTypeName = issueType.name; + const issueTypeId = issueType.id; + + returnData.push({ + name: issueTypeName, + value: issueTypeId, + }); + } + return returnData; + }, + + // Get all the labels to display them to user so that he can + // select them easily + async getLabels(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let labels; + try { + labels = await jiraApiRequest.call(this, '/label', 'GET'); + } catch (err) { + throw new Error(`Jira Error: ${err}`); + } + for (const label of labels.values) { + const labelName = label; + const labelId = label; + + returnData.push({ + name: labelName, + value: labelId, + }); + } + return returnData; + }, + + // Get all the priorities to display them to user so that he can + // select them easily + async getPriorities(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let priorities; + try { + priorities = await jiraApiRequest.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; + + returnData.push({ + name: priorityName, + value: priorityId, + }); + } + return returnData; + }, + + // Get all the users to display them to user so that he can + // select them easily + async getUsers(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let users; + try { + users = await jiraApiRequest.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; + + returnData.push({ + name: userName, + value: userId, + }); + } + return returnData; + }, + } + }; + async execute(this: IExecuteFunctions): Promise { - return [this.helpers.returnJsonArray({})]; + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + let responseData; + let qs: IDataObject = {}; + for (let i = 0; i < length; i++) { + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + if (resource === 'issue') { + if (operation === 'create') { + const summary = this.getNodeParameter('summary', i) as string; + const projectId = this.getNodeParameter('project', i) as string; + const issueTypeId = this.getNodeParameter('issueType', i) as string; + const parentIssueId = this.getNodeParameter('parentIssueId', i) as string; + const parentIssueIdValue = this.getNodeParameter('parentIssueIdValue', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const body = { + fields: { + summary, + project: { + id: projectId, + }, + issuetype: { + id: issueTypeId + }, + } + }; + if (additionalFields.labels) { + body.fields.labels = additionalFields.labels as string[]; + } + if (additionalFields.priority) { + body.fields.priority = { + id: additionalFields.priority as string, + }; + } + if (additionalFields.assignee) { + body.fields.assignee = { + id: additionalFields.assignee as string, + }; + } + if (!parentIssueIdValue && issueTypeId === 'sub-task') { + throw new Error('You must define a Parent ID/Key when Issue type is sub-task'); + + } else if (parentIssueIdValue && issueTypeId === 'sub-task') { + if (parentIssueId === 'id') { + body.fields.parent = { + id: parentIssueIdValue, + }; + } + if (parentIssueId === 'key') { + body.fields.parent = { + key: parentIssueIdValue, + }; + } + } + try { + responseData = await jiraApiRequest.call(this, '/issue', 'POST', body); + } catch (err) { + throw new Error(`Jira Error: ${JSON.stringify(err)}`); + } + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + } + return [this.helpers.returnJsonArray(returnData)]; } } diff --git a/packages/nodes-base/nodes/Jira/jira.png b/packages/nodes-base/nodes/Jira/jira.png index 665ee5a217..db28f59c87 100644 Binary files a/packages/nodes-base/nodes/Jira/jira.png and b/packages/nodes-base/nodes/Jira/jira.png differ