2020-07-25 01:22:02 -07:00
import {
IHookFunctions ,
IWebhookFunctions ,
} from 'n8n-core' ;
import {
2022-07-10 01:41:32 -07:00
ICredentialDataDecryptedObject ,
2020-07-25 01:22:02 -07:00
IDataObject ,
INodeType ,
2020-10-01 05:01:39 -07:00
INodeTypeDescription ,
2020-07-25 01:22:02 -07:00
IWebhookResponseData ,
2022-07-10 01:41:32 -07:00
NodeOperationError ,
2020-07-25 01:22:02 -07:00
} from 'n8n-workflow' ;
import {
2020-10-01 05:01:39 -07:00
allEvents ,
2020-07-25 01:22:02 -07:00
eventExists ,
getId ,
2020-10-01 05:01:39 -07:00
jiraSoftwareCloudApiRequest ,
2020-07-25 01:22:02 -07:00
} from './GenericFunctions' ;
import * as queryString from 'querystring' ;
export class JiraTrigger implements INodeType {
description : INodeTypeDescription = {
displayName : 'Jira Trigger' ,
name : 'jiraTrigger' ,
2021-01-29 11:08:27 -08:00
icon : 'file:jira.svg' ,
2020-07-25 01:22:02 -07:00
group : [ 'trigger' ] ,
version : 1 ,
2021-07-03 05:40:16 -07:00
description : 'Starts the workflow when Jira events occur' ,
2020-07-25 01:22:02 -07:00
defaults : {
name : 'Jira Trigger' ,
} ,
inputs : [ ] ,
outputs : [ 'main' ] ,
credentials : [
{
name : 'jiraSoftwareCloudApi' ,
required : true ,
displayOptions : {
show : {
jiraVersion : [
'cloud' ,
] ,
} ,
} ,
} ,
{
name : 'jiraSoftwareServerApi' ,
required : true ,
displayOptions : {
show : {
jiraVersion : [
'server' ,
] ,
} ,
} ,
} ,
2022-07-10 01:41:32 -07:00
{
2022-07-10 02:20:57 -07:00
// eslint-disable-next-line n8n-nodes-base/node-class-description-credentials-name-unsuffixed
2022-07-10 01:41:32 -07:00
name : 'httpQueryAuth' ,
required : true ,
displayOptions : {
show : {
incomingAuthentication : [
'queryAuth' ,
] ,
} ,
} ,
} ,
2020-07-25 01:22:02 -07:00
] ,
webhooks : [
{
name : 'default' ,
httpMethod : 'POST' ,
responseMode : 'onReceived' ,
path : 'webhook' ,
} ,
] ,
properties : [
{
displayName : 'Jira Version' ,
name : 'jiraVersion' ,
type : 'options' ,
options : [
{
name : 'Cloud' ,
value : 'cloud' ,
} ,
{
name : 'Server (Self Hosted)' ,
value : 'server' ,
} ,
] ,
default : 'cloud' ,
} ,
2022-07-10 01:41:32 -07:00
{
displayName : 'Incoming Authentication' ,
name : 'incomingAuthentication' ,
type : 'options' ,
options : [
{
name : 'Query Auth' ,
value : 'queryAuth' ,
} ,
{
name : 'None' ,
value : 'none' ,
} ,
] ,
default : 'none' ,
description : 'If authentication should be activated for the webhook (makes it more secure)' ,
} ,
2020-07-25 01:22:02 -07:00
{
displayName : 'Events' ,
name : 'events' ,
type : 'multiOptions' ,
options : [
{
name : '*' ,
value : '*' ,
} ,
{
2020-07-25 01:22:24 -07:00
name : 'Board Configuration Changed' ,
value : 'board_configuration_changed' ,
2020-07-25 01:22:02 -07:00
} ,
{
2020-07-25 01:22:24 -07:00
name : 'Board Created' ,
value : 'board_created' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Board Deleted' ,
value : 'board_deleted' ,
} ,
{
2020-07-25 01:22:24 -07:00
name : 'Board Updated' ,
value : 'board_updated' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Comment Created' ,
value : 'comment_created' ,
} ,
{
name : 'Comment Deleted' ,
value : 'comment_deleted' ,
} ,
{
2020-07-25 01:22:24 -07:00
name : 'Comment Updated' ,
value : 'comment_updated' ,
2020-07-25 01:22:02 -07:00
} ,
{
2020-07-25 01:22:24 -07:00
name : 'Issue Created' ,
value : 'jira:issue_created' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Issue Deleted' ,
value : 'jira:issue_deleted' ,
} ,
{
2020-07-25 01:22:24 -07:00
name : 'Issue Link Created' ,
value : 'issuelink_created' ,
2020-07-25 01:22:02 -07:00
} ,
{
2020-07-25 01:22:24 -07:00
name : 'Issue Link Deleted' ,
value : 'issuelink_deleted' ,
2020-07-25 01:22:02 -07:00
} ,
{
2020-07-25 01:22:24 -07:00
name : 'Issue Updated' ,
value : 'jira:issue_updated' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Option Attachments Changed' ,
value : 'option_attachments_changed' ,
} ,
{
name : 'Option Issue Links Changed' ,
value : 'option_issuelinks_changed' ,
} ,
2020-07-25 01:22:24 -07:00
{
name : 'Option Subtasks Changed' ,
value : 'option_subtasks_changed' ,
} ,
2020-07-25 01:22:02 -07:00
{
name : 'Option Timetracking Changed' ,
value : 'option_timetracking_changed' ,
} ,
2020-07-25 01:22:24 -07:00
{
name : 'Option Unassigned Issues Changed' ,
value : 'option_unassigned_issues_changed' ,
} ,
{
name : 'Option Voting Changed' ,
value : 'option_voting_changed' ,
} ,
{
name : 'Option Watching Changed' ,
value : 'option_watching_changed' ,
} ,
2020-07-25 01:22:02 -07:00
{
name : 'Project Created' ,
value : 'project_created' ,
} ,
2020-07-25 01:22:24 -07:00
{
name : 'Project Deleted' ,
value : 'project_deleted' ,
} ,
2020-07-25 01:22:02 -07:00
{
name : 'Project Updated' ,
value : 'project_updated' ,
} ,
{
2020-07-25 01:22:24 -07:00
name : 'Sprint Closed' ,
value : 'sprint_closed' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Sprint Created' ,
value : 'sprint_created' ,
} ,
{
name : 'Sprint Deleted' ,
value : 'sprint_deleted' ,
} ,
{
name : 'Sprint Started' ,
value : 'sprint_started' ,
} ,
{
2020-07-25 01:22:24 -07:00
name : 'Sprint Updated' ,
value : 'sprint_updated' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'User Created' ,
value : 'user_created' ,
} ,
{
name : 'User Deleted' ,
value : 'user_deleted' ,
} ,
{
2020-07-25 01:22:24 -07:00
name : 'User Updated' ,
value : 'user_updated' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Version Created' ,
value : 'jira:version_created' ,
} ,
{
2020-07-25 01:22:24 -07:00
name : 'Version Deleted' ,
value : 'jira:version_deleted' ,
2020-07-25 01:22:02 -07:00
} ,
{
2020-07-25 01:22:24 -07:00
name : 'Version Moved' ,
value : 'jira:version_moved' ,
2020-07-25 01:22:02 -07:00
} ,
{
2020-07-25 01:22:24 -07:00
name : 'Version Released' ,
value : 'jira:version_released' ,
2020-07-25 01:22:02 -07:00
} ,
{
2020-07-25 01:22:24 -07:00
name : 'Version Unreleased' ,
value : 'jira:version_unreleased' ,
2020-07-25 01:22:02 -07:00
} ,
{
2020-07-25 01:22:24 -07:00
name : 'Version Updated' ,
value : 'jira:version_updated' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Worklog Created' ,
value : 'worklog_created' ,
} ,
{
name : 'Worklog Deleted' ,
value : 'worklog_deleted' ,
} ,
2020-07-25 01:22:24 -07:00
{
name : 'Worklog Updated' ,
value : 'worklog_updated' ,
} ,
2020-07-25 01:22:02 -07:00
] ,
required : true ,
default : [ ] ,
2022-05-06 14:01:25 -07:00
description : 'The events to listen to' ,
2020-07-25 01:22:02 -07:00
} ,
{
displayName : 'Additional Fields' ,
name : 'additionalFields' ,
type : 'collection' ,
placeholder : 'Add Field' ,
default : { } ,
options : [
{
displayName : 'Exclude Body' ,
name : 'excludeBody' ,
type : 'boolean' ,
default : false ,
2022-06-20 07:54:01 -07:00
description : 'Whether a request with empty body will be sent to the URL. Leave unchecked if you want to receive JSON.' ,
2020-07-25 01:22:02 -07:00
} ,
{
displayName : 'Filter' ,
name : 'filter' ,
type : 'string' ,
typeOptions : {
alwaysOpenEditWindow : true ,
} ,
default : '' ,
placeholder : 'Project = JRA AND resolution = Fixed' ,
description : 'You can specify a JQL query to send only events triggered by matching issues. The JQL filter only applies to events under the Issue and Comment columns.' ,
} ,
{
displayName : 'Include Fields' ,
name : 'includeFields' ,
type : 'multiOptions' ,
options : [
{
name : 'Attachment ID' ,
2020-10-22 06:46:03 -07:00
value : 'attachment.id' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Board ID' ,
2020-10-22 06:46:03 -07:00
value : 'board.id' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Comment ID' ,
2020-10-22 06:46:03 -07:00
value : 'comment.id' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Issue ID' ,
2020-10-22 06:46:03 -07:00
value : 'issue.id' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Merge Version ID' ,
2020-10-22 06:46:03 -07:00
value : 'mergeVersion.id' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Modified User Account ID' ,
2020-10-22 06:46:03 -07:00
value : 'modifiedUser.accountId' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Modified User Key' ,
2020-10-22 06:46:03 -07:00
value : 'modifiedUser.key' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Modified User Name' ,
2020-10-22 06:46:03 -07:00
value : 'modifiedUser.name' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Project ID' ,
2020-10-22 06:46:03 -07:00
value : 'project.id' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Project Key' ,
2020-10-22 06:46:03 -07:00
value : 'project.key' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Propery Key' ,
2020-10-22 06:46:03 -07:00
value : 'property.key' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Sprint ID' ,
2020-10-22 06:46:03 -07:00
value : 'sprint.id' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Version ID' ,
2020-10-22 06:46:03 -07:00
value : 'version.id' ,
2020-07-25 01:22:02 -07:00
} ,
{
name : 'Worklog ID' ,
2020-10-22 06:46:03 -07:00
value : 'worklog.id' ,
2020-07-25 01:22:02 -07:00
} ,
] ,
default : [ ] ,
} ,
] ,
} ,
] ,
} ;
// @ts-ignore (because of request)
webhookMethods = {
default : {
async checkExists ( this : IHookFunctions ) : Promise < boolean > {
const webhookUrl = this . getNodeWebhookUrl ( 'default' ) as string ;
const webhookData = this . getWorkflowStaticData ( 'node' ) ;
const events = this . getNodeParameter ( 'events' ) as string [ ] ;
const endpoint = ` /webhooks/1.0/webhook ` ;
const webhooks = await jiraSoftwareCloudApiRequest . call ( this , endpoint , 'GET' , { } ) ;
for ( const webhook of webhooks ) {
if ( webhook . url === webhookUrl && eventExists ( events , webhook . events ) ) {
webhookData . webhookId = getId ( webhook . self ) ;
return true ;
}
}
return false ;
} ,
async create ( this : IHookFunctions ) : Promise < boolean > {
const webhookUrl = this . getNodeWebhookUrl ( 'default' ) as string ;
let events = this . getNodeParameter ( 'events' , [ ] ) as string [ ] ;
const additionalFields = this . getNodeParameter ( 'additionalFields' ) as IDataObject ;
const endpoint = ` /webhooks/1.0/webhook ` ;
const webhookData = this . getWorkflowStaticData ( 'node' ) ;
2022-07-10 01:41:32 -07:00
const incomingAuthentication = this . getNodeParameter ( 'incomingAuthentication' ) as string ;
2020-07-25 01:22:02 -07:00
if ( events . includes ( '*' ) ) {
events = allEvents ;
}
const body = {
name : ` n8n-webhook: ${ webhookUrl } ` ,
url : webhookUrl ,
events ,
filters : {
} ,
excludeBody : false ,
} ;
if ( additionalFields . filter ) {
body . filters = {
'issue-related-events-section' : additionalFields . filter ,
} ;
}
if ( additionalFields . excludeBody ) {
body . excludeBody = additionalFields . excludeBody as boolean ;
}
2022-07-10 01:41:32 -07:00
// tslint:disable-next-line: no-any
const parameters : any = { } ;
if ( incomingAuthentication === 'queryAuth' ) {
let httpQueryAuth ;
try {
httpQueryAuth = await this . getCredentials ( 'httpQueryAuth' ) ;
} catch ( e ) {
throw new NodeOperationError ( this . getNode ( ) , ` Could not retrieve HTTP Query Auth credentials: ${ e } ` ) ;
}
if ( ! httpQueryAuth . name && ! httpQueryAuth . value ) {
throw new NodeOperationError ( this . getNode ( ) , ` HTTP Query Auth credentials are empty ` ) ;
}
parameters [ encodeURIComponent ( httpQueryAuth . name as string ) ] = Buffer . from ( httpQueryAuth . value as string ) . toString ( 'base64' ) ;
}
2020-07-25 01:22:02 -07:00
if ( additionalFields . includeFields ) {
2022-07-10 01:41:32 -07:00
2020-07-25 01:22:02 -07:00
for ( const field of additionalFields . includeFields as string [ ] ) {
parameters [ field ] = '${' + field + '}' ;
}
2022-07-10 01:41:32 -07:00
}
if ( Object . keys ( parameters ) . length ) {
2020-07-25 01:22:02 -07:00
body . url = ` ${ body . url } ? ${ queryString . unescape ( queryString . stringify ( parameters ) ) } ` ;
}
const responseData = await jiraSoftwareCloudApiRequest . call ( this , endpoint , 'POST' , body ) ;
webhookData . webhookId = getId ( responseData . self ) ;
return true ;
} ,
async delete ( this : IHookFunctions ) : Promise < boolean > {
const webhookData = this . getWorkflowStaticData ( 'node' ) ;
if ( webhookData . webhookId !== undefined ) {
const endpoint = ` /webhooks/1.0/webhook/ ${ webhookData . webhookId } ` ;
const body = { } ;
try {
await jiraSoftwareCloudApiRequest . call ( this , endpoint , 'DELETE' , body ) ;
2021-04-16 09:33:36 -07:00
} catch ( error ) {
2020-07-25 01:22:02 -07:00
return false ;
}
// Remove from the static workflow data so that it is clear
// that no webhooks are registred anymore
delete webhookData . webhookId ;
}
return true ;
} ,
} ,
} ;
async webhook ( this : IWebhookFunctions ) : Promise < IWebhookResponseData > {
const bodyData = this . getBodyData ( ) ;
2022-07-10 01:41:32 -07:00
const queryData = this . getQueryData ( ) as IDataObject ;
const response = this . getResponseObject ( ) ;
const incomingAuthentication = this . getNodeParameter ( 'incomingAuthentication' ) as string ;
if ( incomingAuthentication === 'queryAuth' ) {
let httpQueryAuth : ICredentialDataDecryptedObject | undefined ;
try {
httpQueryAuth = await this . getCredentials ( 'httpQueryAuth' ) ;
} catch ( error ) { }
if ( httpQueryAuth === undefined || ! httpQueryAuth . name || ! httpQueryAuth . value ) {
response . status ( 403 ) . json ( { message : 'Auth settings are not valid, some data are missing' } ) ;
return {
noWebhookResponse : true ,
} ;
}
const paramName = httpQueryAuth . name as string ;
const paramValue = Buffer . from ( httpQueryAuth . value as string ) . toString ( 'base64' ) ;
if ( ! queryData . hasOwnProperty ( paramName ) || queryData [ paramName ] !== paramValue ) {
response . status ( 403 ) . json ( { message : 'Provided authentication data is not valid' } ) ;
return {
noWebhookResponse : true ,
} ;
}
delete queryData [ paramName ] ;
Object . assign ( bodyData , queryData ) ;
2020-07-25 01:22:02 -07:00
2022-07-10 01:41:32 -07:00
} else {
Object . assign ( bodyData , queryData ) ;
}
2020-07-25 01:22:02 -07:00
return {
workflowData : [
2020-10-22 06:46:03 -07:00
this . helpers . returnJsonArray ( bodyData ) ,
2020-07-25 01:22:02 -07:00
] ,
} ;
}
}