Missed Team ’24? Catch up on announcements here.

×
Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

Script function failed on event: com.atlassian.jira.event.issue.IssueEvent, file: null

Vivo Bandito January 19, 2023

Hello, I'm having some difficulty getting some code to work with a specific issue type of "Escalation" to the specific DBA webhook. I've been able to verify with our same slack webhook (removed for obvious reasons) is working and that other issue types are working. Here is the error message followed by the code.

 

ERROR

2023-01-19 09:13:50,660 WARN [runner.ScriptBindingsManager]: 
Atl Admin: false
Database Admin: true
Network Admin: false
Sys Admin: false
2023-01-19 09:13:50,660 INFO [runner.ScriptBindingsManager]: Issue Key: EDCO-248989 Issue Type is Escalation
2023-01-19 09:13:50,816 ERROR [runner.AbstractScriptListener]: *************************************************************************************
2023-01-19 09:13:50,816 ERROR [runner.AbstractScriptListener]: Script function failed on event: com.atlassian.jira.event.issue.IssueEvent, file: null
groovyx.net.http.HttpResponseException: status code: 400, reason phrase: Bad Request
at groovyx.net.http.RESTClient.defaultFailureHandler(RESTClient.java:263)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at groovyx.net.http.HTTPBuilder$1.handleResponse(HTTPBuilder.java:503)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:223)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:165)
at groovyx.net.http.HTTPBuilder.doRequest(HTTPBuilder.java:515)
at groovyx.net.http.RESTClient.post(RESTClient.java:141)
at groovyx.net.http.RESTClient$post.call(Unknown Source)
at Script319.run(Script319.groovy:122)

 

SCRIPT

import com.atlassian.jira.issue.Issue
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.config.properties.APKeys
import groovyx.net.http.ContentType
import groovyx.net.http.RESTClient
import groovyx.net.http.HttpResponseDecorator
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.customfields.manager.OptionsManager
import com.atlassian.jira.issue.customfields.option.Option
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.fields.config.FieldConfig

log.setLevel(org.apache.log4j.Level.DEBUG)

def issue = event.issue as Issue
def typesToExclude = ["Change","Epic","Project Milestone","Project Deliverable (Sub-task)","Request Test"]

if(typesToExclude.contains(issue.issueType.name)) { log.info "Not a valid issue for notification"; return }

CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager()
OptionsManager optionsManager = ComponentAccessor.getOptionsManager()

CustomField cfaTeam = customFieldManager.getCustomFieldObject("customfield_14202") // Assigned Team
FieldConfig fcaTeam = cfaTeam.getRelevantConfig(issue)

Option sysa = optionsManager.getOptions(fcaTeam)?.find { it.getValue() == 'Systems Administration' }
Option dba = optionsManager.getOptions(fcaTeam)?.find { it.getValue() == 'Database Administration' }
Option na = optionsManager.getOptions(fcaTeam)?.find { it.getValue() == 'Network Administration' }
Option atl = optionsManager.getOptions(fcaTeam)?.find { it.getValue() == 'Atlassian Administration' }

Option teamOption = issue.getCustomFieldValue(cfaTeam) as Option

def isSysAdmin = sysa == teamOption
def isDba = dba == teamOption
def isNa = na == teamOption
def isAtl = atl == teamOption

def infTeamsList = [isAtl, isDba, isNa, isSysAdmin]


if(!infTeamsList.contains(true)) { log.warn "Not an Infrastructure Team"; return }
log.warn "\nAtl Admin: ${infTeamsList[0] as String}\nDatabase Admin: ${infTeamsList[1] as String}\nNetwork Admin: ${infTeamsList[2] as String}\nSys Admin: ${infTeamsList[3] as String}"

def webhookPath = ''
final webhookPathAtl = '/services/xxxx'
final webhookPathElOne = '/services/xxxxx'
final webhookPathElTwo = '/services/xxxxxx'
final webhookPathElThree = '/services/xxxxxxx'
final webhookPathElLeadership = '/services/xxxxxxxx'
final webhookPathNetworkAdmin = '/services/xxxxxxxxx'
final webhookPathDBA = 'services/xxxxxxxxxx'




if (isAtl) { webhookPath = webhookPathAtl }
else if (isDba) { webhookPath = webhookPathDBA }
else if (isNa) { webhookPath = webhookPathNetworkAdmin }
else if (isSysAdmin)
{
CustomField cfaEl = customFieldManager.getCustomFieldObject("customfield_19303") // Escalation Level
FieldConfig fcaEl = cfaEl.getRelevantConfig(issue)

Option one = optionsManager.getOptions(fcaEl)?.find { it.getValue() == 'Level 1' }
Option two = optionsManager.getOptions(fcaEl)?.find { it.getValue() == 'Level 2' }
Option three = optionsManager.getOptions(fcaEl)?.find { it.getValue() == 'Level 3' }
Option leadership = optionsManager.getOptions(fcaEl)?.find { it.getValue() == 'Leadership' }

Option elOption = issue.getCustomFieldValue(cfaEl) as Option ?: one

def levelOne = one == elOption
def levelTwo = two == elOption
def levelThree = three == elOption
def levelLead = leadership == elOption

if(levelOne) {
log.debug "Sys Admin level 1 webhook path set"
webhookPath = webhookPathElOne
}
else if(levelTwo) {
log.debug "Sys Admin level 2 webhook path set"
webhookPath = webhookPathElTwo
}
else if(levelThree) {
log.debug "Sys Admin level 3 webhook path set"
webhookPath = webhookPathElThree
}
else if(levelLead) {
log.debug "Sys Admin Leadership webhook path set"
webhookPath = webhookPathElLeadership
}
else { log.error "Sys Admin detected, but no EL found. No notification sent."; return }

}
else {
log.error "No Alert Defined"
return
}

final webhookBase = 'https://hooks.slack.com'
def jiraBaseUrl = ComponentAccessor.applicationProperties.getString(APKeys.JIRA_BASEURL)

//Issue detail augmentation
String org_summary = issue.summary
String summary = org_summary
String org_description = issue.description ?: ""
String description = org_description.replace("*Description copied from Parent:*","") ?: ""

String textBlock = "${issue.issueType.name}: <$jiraBaseUrl/browse/$issue.key|${summary}>"
log.info "Issue Key: ${issue.key} Issue Type is ${issue.issueType.name}"

def body = [
text: summary,
blocks: [
[
type: 'section',
text: [
type: 'mrkdwn',
text: textBlock
]
]
]
]

def response = new RESTClient(webhookBase).post(
path: webhookPath,
contentType: ContentType.HTML,
body: body,
requestContentType: ContentType.JSON
) as HttpResponseDecorator

assert response.status == 200: "Request failed with status $response.status. $response.entity.content.text"

1 answer

1 accepted

Suggest an answer

Log in or Sign up to answer
1 vote
Answer accepted
Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
January 19, 2023

Usually ...

HttpResponseException: status code: 400, reason phrase: Bad Request

...means that something is wrong with your request. Either something missing in the headers or in the body and the remote endpoint is rejecting it.

I would add a log above the RESTClient call to output your body as Json then try the exact same request in a tool like postman to see if the API endpoint might return some more detailed information about what the error is.

log.info (new JsonBuilder(body).toPrettyString())

 You might want to add some handlers to the rest client.

E.g.

RESTClient client = new RESTClient(webhookBase)
client.handler.success = {HttpResponseDecorator response, responsePayload ->
//either return the full resposne as httpResponseDecorator or convert it somehow e.g.
return [success:true, status: response?.status, content: responsePayload

}
client.handler.failure = {HttpResponseDecorator response ->
log.error "FailureHandler - Error making $method request to $client.uri"
log.error "FailureHandler - Submitted Payload: $payload"
def content = "$response.entity.content".toString()
log.error "FailureHandler - Response ContentType: $response.contentType"
log.error "FailureHandler - Response: $content"
//return something, either the response, or some error message or transformed the decorator into a simple map, e.g.
return [success:false, status: response?.status, content: content]
}
client.post(method, contentType) {
path: webhookPath,
contentType: ContentType.HTML,
body: body,
requestContentType: ContentType.JSON
} //this will return whatever is returned by the appropriate handler
Vivo Bandito January 30, 2023

Hey @Peter-Dave Sheehan , 

Not too sure how I would implement this into the code. Do you have any suggestions or resources as to how?

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
January 30, 2023

Try it like this:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.config.properties.APKeys
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.customfields.option.Option
import com.atlassian.jira.issue.fields.CustomField
import groovy.json.JsonBuilder
import groovyx.net.http.ContentType
import groovyx.net.http.HttpResponseDecorator
import groovyx.net.http.RESTClient

log.setLevel(org.apache.log4j.Level.DEBUG)

def issue = event.issue as Issue
def typesToExclude = ["Change", "Epic", "Project Milestone", "Project Deliverable (Sub-task)", "Request Test"]

if (typesToExclude.contains(issue.issueType.name)) {
log.info "Not a valid issue for notification"; return
}

CustomFieldManager customFieldManager = ComponentAccessor.customFieldManager

CustomField teamCf = customFieldManager.getCustomFieldObject("customfield_14202") // Assigned Team
Option selectedTeamOption = issue.getCustomFieldValue(teamCf) as Option

def webhookPath = ''
final webhookPathAtl = '/services/xxxx'
final webhookPathElOne = '/services/xxxxx'
final webhookPathElTwo = '/services/xxxxxx'
final webhookPathElThree = '/services/xxxxxxx'
final webhookPathElLeadership = '/services/xxxxxxxx'
final webhookPathNetworkAdmin = '/services/xxxxxxxxx'
final webhookPathDBA = 'services/xxxxxxxxxx'

def infrastructureTeamMap = [
'System Administrations' : [
uselevel: true,
levelMap: [
'Level 1' : webhookPathElOne,
'Level 2' : webhookPathElTwo,
'Level 3' : webhookPathElThree,
'Leadership': webhookPathElLeadership,
]
],
'Database Administration' : webhookPathDBA,
'Network Administration' : webhookPathNetworkAdmin,
'Atlassian Administration': webhookPathAtl
]
def isInfraTeam = infrastructureTeamMap.keySet().any { it == selectedTeamOption.value }

if (!isInfraTeam) {
log.warn "Not an Infrastructure Team"; return
}
if (infrastructureTeamMap[selectedTeamOption.value]?.levelMap) {
CustomField escalationLevelCf = customFieldManager.getCustomFieldObject("customfield_19303") // Escalation Level
Option selectedEscLvl = escalationLevelCf.value as Option

webhookPath = infrastructureTeamMap[selectedTeamOption.value]?.levelMap[selectedEscLvl.value]
if (!webhookPath) {
log.error "$selectedTeamOption.value detected which expects an Escalation Level, but none was found. No notification sent."
return
} else {
log.debug "webhookPath set to $webhookPath because Escalation Level is $selectedEscLvl.value"
}
} else {
webhookPath = infrastructureTeamMap[selectedTeamOption.value]
if (!webhookPath) {
log.error "Assigned Team '$selectedTeamOption.value' which doesn't have a webhook path defined. No notification sent."
return
} else {
log.debug "webhookPath set to $webhookPath because Assigned Team is $selectedTeamOption.value"
}
}

final webhookBase = 'https://hooks.slack.com'
def jiraBaseUrl = ComponentAccessor.applicationProperties.getString(APKeys.JIRA_BASEURL)

//Issue detail augmentation
String org_summary = issue.summary
String org_description = issue.description ?: ""
String description = org_description.replace("*Description copied from Parent:*", "") ?: ""

String textBlock = "$issue.issueType.name: <$jiraBaseUrl/browse/$issue.key|$issue.summary>"
log.info "Issue Key: $issue.key Issue Type is $issue.issueType.name"

def body = [
text : issue.summary,
blocks: [[type: 'section', text: [type: 'mrkdwn', text: textBlock]]]
]

log.info "Making a POST Request to $webhookBase\$webhookPath \n with Payload: \n${new JsonBuilder(body).toPrettyString()}"

RESTClient client = new RESTClient(webhookBase)

client.handler.success = { HttpResponseDecorator response, responsePayload ->
log.debug "success post detected with response: $responsePayload"
return [success: true, status: response?.status, content: responsePayload]
}

client.handler.failure = { HttpResponseDecorator response ->
log.error "FailureHandler - Error making POST request to $client.uri"
log.error "FailureHandler - Submitted Payload: $body"
def content = "$response.entity.content".toString()
log.error "FailureHandler - Response ContentType: $response.contentType"
log.error "FailureHandler - Response: $content"
return [success: false, status: response?.status, content: content]
}
def response = client.post(
path: webhookPath,
contentType: ContentType.HTML,
body: body,
requestContentType: ContentType.JSON
)

log.info "Final response = \n${new JsonBuilder(response).toPrettyString()}"
Vivo Bandito January 31, 2023

Hey @Peter-Dave Sheehan ,

I was able to get the code implemented into the listener. Here is the resulting log after creating an escalation to the Database Administration Team. The log is stating that the post was successful but no message was posted in the channel. 

Once again it is only the escalation tickets that are failing. 

2023-01-31 11:08:14,342 WARN [runner.ScriptBindingsManager]: 
Atl Admin: false
Network Admin: false
Sys Admin: false
Database Admin: true
2023-01-31 11:08:14,342 INFO [runner.ScriptBindingsManager]: Issue Key: EDCO-250771  Issue Type is Escalation
2023-01-31 11:08:14,342 INFO [runner.ScriptBindingsManager]: Making a POST Request to https://hooks.slack.com$webhookPath  with Payload: 
{
    "text": "Escalation: Jira Slack Test",
    "blocks": [
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "Escalation: <https://jira.edcohis.com/browse/EDCO-250771|Escalation: Jira Slack Test>"            }
        }
    ]
}
2023-01-31 11:08:14,529 DEBUG [runner.ScriptBindingsManager]: success post detected with response: ok 
Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
January 31, 2023

Looks like I had a slash in the wrong direction in this line:

log.info "Making a POST Request to $webhookBase\$webhookPath \n with Payload: \n${new JsonBuilder(body).toPrettyString()}"

It should be

log.info "Making a POST Request to $webhookBase/$webhookPath \n with Payload: \n${new JsonBuilder(body).toPrettyString()}"

But after this, seems like the error is perhaps on the slack side.

I would use the information generated in the logs and try to manually create a post using a tool like Postman.

You have a well-formed JSON that can be pasted into the body of the request:

{
    "text": "Escalation: Jira Slack Test",
    "blocks": [
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "Escalation: <https://jira.edcohis.com/browse/EDCO-250771|Escalation: Jira Slack Test>"            }
        }
    ]
}

Just try to make a POST to the same URL as what Jira tried to do and examine all the details of the response from slack. There might be some clues.

Vivo Bandito January 31, 2023

Hey @Peter-Dave Sheehan ,

After the "\" to "/" the webhook url logging part and the actual slack message went through! Do you have any idea as to why the message was previously failing. My only thoughts would be that the following snippet was the culprit.

def response = new RESTClient(webhookBase).post(
path: webhookPath,
contentType: ContentType.HTML,
body: body,
requestContentType: ContentType.JSON
) as HttpResponseDecorator

assert response.status == 200: "Request failed with status $response.status. $response.entity.content.text"
Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
January 31, 2023

Sorry no idea why this failed before.
But glad you got it all to work.

Vivo Bandito January 31, 2023

Either way I appreciate the help! Thank you!

TAGS
AUG Leaders

Atlassian Community Events