2023-01-27 03:22:44 -08:00
import type {
2022-07-10 01:41:32 -07:00
ICredentialDataDecryptedObject ,
2020-07-25 01:22:02 -07:00
IDataObject ,
2023-03-09 09:13:15 -08:00
IHookFunctions ,
IWebhookFunctions ,
2020-07-25 01:22:02 -07:00
INodeType ,
2020-10-01 05:01:39 -07:00
INodeTypeDescription ,
2020-07-25 01:22:02 -07:00
IWebhookResponseData ,
} from 'n8n-workflow' ;
2023-01-27 03:22:44 -08:00
import { NodeOperationError } from 'n8n-workflow' ;
2020-07-25 01:22:02 -07:00
2022-08-17 08:50:24 -07:00
import { allEvents , eventExists , getId , jiraSoftwareCloudApiRequest } from './GenericFunctions' ;
2020-07-25 01:22:02 -07:00
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' ] ,
2024-04-30 21:26:09 -07:00
version : [ 1 , 1.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 : [
{
2024-04-30 21:26:09 -07:00
displayName : 'Credentials to Connect to Jira' ,
2020-07-25 01:22:02 -07:00
name : 'jiraSoftwareCloudApi' ,
required : true ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
jiraVersion : [ 'cloud' ] ,
2020-07-25 01:22:02 -07:00
} ,
} ,
} ,
{
2024-04-30 21:26:09 -07:00
displayName : 'Credentials to Connect to Jira' ,
2020-07-25 01:22:02 -07:00
name : 'jiraSoftwareServerApi' ,
required : true ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
jiraVersion : [ 'server' ] ,
2020-07-25 01:22:02 -07:00
} ,
} ,
} ,
2022-07-10 01:41:32 -07:00
{
2023-08-09 01:10:08 -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' ,
2024-04-30 21:26:09 -07:00
displayName : 'Credentials to Authenticate Webhook' ,
displayOptions : {
show : {
authenticateWebhook : [ true ] ,
} ,
} ,
} ,
{
name : 'httpQueryAuth' ,
displayName : 'Credentials to Authenticate Webhook' ,
2022-07-10 01:41:32 -07:00
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
incomingAuthentication : [ 'queryAuth' ] ,
2022-07-10 01:41:32 -07:00
} ,
} ,
} ,
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
{
2024-04-30 21:26:09 -07:00
displayName : 'Authenticate Incoming Webhook' ,
name : 'authenticateWebhook' ,
type : 'boolean' ,
default : false ,
description :
'Whether authentication should be activated for the incoming webhooks (makes it more secure)' ,
displayOptions : {
show : {
'@version' : [ { _cnd : { gte : 1.1 } } ] ,
} ,
} ,
} ,
{
displayName : 'Authenticate Webhook With' ,
2022-07-10 01:41:32 -07:00
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)' ,
2024-04-30 21:26:09 -07:00
displayOptions : {
show : {
'@version' : [ 1 ] ,
} ,
} ,
2022-07-10 01:41:32 -07:00
} ,
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-08-17 08:50:24 -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' ,
default : '' ,
placeholder : 'Project = JRA AND resolution = Fixed' ,
2022-08-17 08:50:24 -07:00
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.' ,
2020-07-25 01:22:02 -07:00
} ,
{
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 : [ ] ,
} ,
] ,
} ,
] ,
} ;
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 [ ] ;
2022-12-29 03:20:43 -08:00
const endpoint = '/webhooks/1.0/webhook' ;
2020-07-25 01:22:02 -07:00
const webhooks = await jiraSoftwareCloudApiRequest . call ( this , endpoint , 'GET' , { } ) ;
for ( const webhook of webhooks ) {
2023-02-27 19:39:43 -08:00
if ( webhook . url === webhookUrl && eventExists ( events , webhook . events as string [ ] ) ) {
webhookData . webhookId = getId ( webhook . self as string ) ;
2020-07-25 01:22:02 -07:00
return true ;
}
}
return false ;
} ,
async create ( this : IHookFunctions ) : Promise < boolean > {
2024-04-30 21:26:09 -07:00
const nodeVersion = this . getNode ( ) . typeVersion ;
2020-07-25 01:22:02 -07:00
const webhookUrl = this . getNodeWebhookUrl ( 'default' ) as string ;
let events = this . getNodeParameter ( 'events' , [ ] ) as string [ ] ;
const additionalFields = this . getNodeParameter ( 'additionalFields' ) as IDataObject ;
2022-12-29 03:20:43 -08:00
const endpoint = '/webhooks/1.0/webhook' ;
2020-07-25 01:22:02 -07:00
const webhookData = this . getWorkflowStaticData ( 'node' ) ;
2024-04-30 21:26:09 -07:00
let authenticateWebhook = false ;
if ( nodeVersion === 1 ) {
const incomingAuthentication = this . getNodeParameter ( 'incomingAuthentication' ) as string ;
if ( incomingAuthentication === 'queryAuth' ) {
authenticateWebhook = true ;
}
} else {
authenticateWebhook = this . getNodeParameter ( 'authenticateWebhook' ) as boolean ;
}
2022-07-10 01:41:32 -07:00
2020-07-25 01:22:02 -07:00
if ( events . includes ( '*' ) ) {
events = allEvents ;
}
const body = {
name : ` n8n-webhook: ${ webhookUrl } ` ,
url : webhookUrl ,
events ,
2022-08-17 08:50:24 -07:00
filters : { } ,
2020-07-25 01:22:02 -07:00
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
const parameters : any = { } ;
2024-04-30 21:26:09 -07:00
if ( authenticateWebhook ) {
2022-07-10 01:41:32 -07:00
let httpQueryAuth ;
2022-08-17 08:50:24 -07:00
try {
2022-07-10 01:41:32 -07:00
httpQueryAuth = await this . getCredentials ( 'httpQueryAuth' ) ;
} catch ( e ) {
2022-08-17 08:50:24 -07:00
throw new NodeOperationError (
this . getNode ( ) ,
2022-12-16 09:47:20 -08:00
new Error ( 'Could not retrieve HTTP Query Auth credentials' , { cause : e } ) ,
2022-08-17 08:50:24 -07:00
) ;
2022-07-10 01:41:32 -07:00
}
if ( ! httpQueryAuth . name && ! httpQueryAuth . value ) {
2022-12-29 03:20:43 -08:00
throw new NodeOperationError ( this . getNode ( ) , 'HTTP Query Auth credentials are empty' ) ;
2022-07-10 01:41:32 -07:00
}
2022-08-17 08:50:24 -07:00
parameters [ encodeURIComponent ( httpQueryAuth . name as string ) ] = Buffer . from (
httpQueryAuth . value as string ,
) . toString ( 'base64' ) ;
2022-07-10 01:41:32 -07:00
}
2020-07-25 01:22:02 -07:00
if ( additionalFields . includeFields ) {
for ( const field of additionalFields . includeFields as string [ ] ) {
2022-12-29 05:57:20 -08:00
// eslint-disable-next-line n8n-local-rules/no-interpolation-in-regular-string
2020-07-25 01:22:02 -07:00
parameters [ field ] = '${' + field + '}' ;
}
2022-07-10 01:41:32 -07:00
}
2023-02-27 19:39:43 -08:00
if ( Object . keys ( parameters as IDataObject ) . length ) {
const params = new URLSearchParams ( parameters as string ) . toString ( ) ;
2022-09-23 07:48:45 -07:00
body . url = ` ${ body . url } ? ${ decodeURIComponent ( params ) } ` ;
2020-07-25 01:22:02 -07:00
}
const responseData = await jiraSoftwareCloudApiRequest . call ( this , endpoint , 'POST' , body ) ;
2023-02-27 19:39:43 -08:00
webhookData . webhookId = getId ( responseData . self as string ) ;
2020-07-25 01:22:02 -07:00
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
2023-03-03 09:49:19 -08:00
// that no webhooks are registered anymore
2020-07-25 01:22:02 -07:00
delete webhookData . webhookId ;
}
return true ;
} ,
} ,
} ;
async webhook ( this : IWebhookFunctions ) : Promise < IWebhookResponseData > {
2024-04-30 21:26:09 -07:00
const nodeVersion = this . getNode ( ) . typeVersion ;
2020-07-25 01:22:02 -07:00
const bodyData = this . getBodyData ( ) ;
2022-07-10 01:41:32 -07:00
const queryData = this . getQueryData ( ) as IDataObject ;
const response = this . getResponseObject ( ) ;
2024-04-30 21:26:09 -07:00
let authenticateWebhook = false ;
if ( nodeVersion === 1 ) {
const incomingAuthentication = this . getNodeParameter ( 'incomingAuthentication' ) as string ;
if ( incomingAuthentication === 'queryAuth' ) {
authenticateWebhook = true ;
}
} else {
authenticateWebhook = this . getNodeParameter ( 'authenticateWebhook' ) as boolean ;
}
2022-07-10 01:41:32 -07:00
2024-04-30 21:26:09 -07:00
if ( authenticateWebhook ) {
2022-07-10 01:41:32 -07:00
let httpQueryAuth : ICredentialDataDecryptedObject | undefined ;
try {
httpQueryAuth = await this . getCredentials ( 'httpQueryAuth' ) ;
2022-08-17 08:50:24 -07:00
} catch ( error ) { }
2022-07-10 01:41:32 -07:00
if ( httpQueryAuth === undefined || ! httpQueryAuth . name || ! httpQueryAuth . value ) {
2022-08-17 08:50:24 -07:00
response
. status ( 403 )
. json ( { message : 'Auth settings are not valid, some data are missing' } ) ;
2022-07-10 01:41:32 -07:00
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 ) ;
} else {
Object . assign ( bodyData , queryData ) ;
}
2020-07-25 01:22:02 -07:00
return {
2022-08-17 08:50:24 -07:00
workflowData : [ this . helpers . returnJsonArray ( bodyData ) ] ,
2020-07-25 01:22:02 -07:00
} ;
}
}