Forums

Articles
Create
cancel
Showing results for 
Search instead for 
Did you mean: 

Scriptrunner post-function script failing intermittently on defect creations - NullPointerException

djohnsoncainc
May 4, 2020

Hi Adaptavist Gurus,

We created a custom post-function Groovy script a little over a year ago that we use in certain project defect transition workflows.

The script was working consistently to use 2 custom field values to set the defect priority.

It now is failing intermittently on defect creation only. I have tried to reproduce it consistently. On our test server I modified the script version to try to add some logging to help me debug the issue, but I am not a coder and what I added and how/where I did it did end up being helpful. 

The failure rate of the script is higher now than it was before our recent upgrade and is project specific. Some projects it very rarely fails while others, it is failing more than it succeeds.

The update defect post function code is very similar, and it does not ever fail.

Here is a runtime error for a specific issue (defect) and project where the script failed:

2020-05-01 09:59:03,945 WARN [workflow.AbstractScriptWorkflowFunction]: Not able to get priority for new defect. Leaving priority not set (default).
2020-05-01 09:59:03,946 ERROR [workflow.AbstractScriptWorkflowFunction]: *************************************************************************************
2020-05-01 09:59:03,946 ERROR [workflow.AbstractScriptWorkflowFunction]: Script function failed on issue: DFP-3889, actionId: 1, file: /data/jira-home/scripts/CreatePriorityPostScriptPOCv2.groovy
java.lang.NullPointerException: Cannot get property 'id' on null object
	at CreatePriorityPostScriptPOCv2.run(CreatePriorityPostScriptPOCv2.groovy:94)


Here is payload:

{
    "full.module.key": "com.onresolve.jira.groovy.groovyrunnerrungroovy-function (java.lang.String)",
    "canned-script": "com.onresolve.scriptrunner.canned.jira.workflow.postfunctions.CustomScriptFunction (java.lang.String)",
    "class.name": "com.onresolve.jira.groovy.GroovyFunctionPlugin (java.lang.String)",
    "issue": "DFP-3889 (com.atlassian.jira.issue.IssueImpl)",
    "passesCondition": "true (java.lang.Boolean)",
    "transientVars": {
        "entry": "com.opensymphony.workflow.spi.SimpleWorkflowEntry@53a846f6",
        "issue": "DFP-3889 (com.atlassian.jira.issue.IssueImpl)",
        "configuration": "com.opensymphony.workflow.config.DefaultConfiguration@793ce9de",
        "context": "com.opensymphony.workflow.basic.BasicWorkflowContext@43305764",
        "createdStep": "SimpleStep@2[owner=, actionId=0, status=open] (com.opensymphony.workflow.spi.SimpleStep)",
        "originalissueobject": "null (org.codehaus.groovy.runtime.NullObject)",
        "actionId": "1 (java.lang.Integer)",
        "currentSteps": "[SimpleStep@2[owner=, actionId=0, status=open]] (java.util.ArrayList)",
        "store": "com.opensymphony.workflow.spi.ofbiz.OfbizWorkflowStore@db4af2a",
        "descriptor": "com.atlassian.jira.workflow.ImmutableWorkflowDescriptor@348cd251"
    },
    "log": "org.apache.log4j.Logger@2e44aced"
}



Below is the script: Line 94 which is the supposed culprit is in bold. Any help greatly appreciated!

// Set a priority using Post-function script on defect create
// Changes the priority of an issue depending on 2 other custom fields (severity and business impact ("bizimpact") with Script Runner
// Map this to issue creation transition - needs to be LAST post-function
// useful reference: https://community.atlassian.com/t5/Answers-Developer-Questions/How-to-change-issue-priority-with-script-runner-groovy/qaq-p/478700
// last updated: 1/25/2019 based on pilot feedback

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.comments.CommentManager;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.priority.Priority;
import com.atlassian.jira.issue.IssueInputParametersImpl;
import com.atlassian.jira.issue.customfields.option.Option;


// Get the current Issue
Issue issue = issue

// Get the Manager classes required
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def commentManager = ComponentAccessor.getCommentManager()
def constantsManager = ComponentAccessor.getConstantsManager()
def issueService = ComponentAccessor.getIssueService()


// Get a pointer to the current logged in user
def user = ComponentAccessor.getJiraAuthenticationContext().getUser()


// Get objects and values for relevant fields associated with the specific issue
// Found you have to explicitly cast the field values as strings for script to work

def bizimpactObject = customFieldManager.getCustomFieldObject("customfield_13402")
def severityObject = customFieldManager.getCustomFieldObject("customfield_13402")

def bizimpact = issue.getCustomFieldValue(customFieldManager.getCustomFieldObject("customfield_13402"));
bizimpact = bizimpact.toString()
def severity = issue.getCustomFieldValue(customFieldManager.getCustomFieldObject("customfield_10201"));
severity = severity.toString()

// for debugging get current issue priority values to compare
// def OrigPriority = issue.getPriorityObject().getName()
// OrigPriority = OrigPriority.toString()
// def OrigPriorityID = issue.getPriorityObject().getId()


// use priorityMatrix below as map. Has linked pair value combinations of severity, and business impact values to priority keeping code in for doc purposes only in this version
// Key at far left is severity and second dimension key is the bizimpact. These values as keys and function returns priority name (in quotes)

def priorityMatrix =
[
Blocker: [Highest: "P0 - Urgent", Significant: "P1 - High" , Average: "P1 - High", Minimal: "P2 - Medium-High"],
Major: [Highest: "P1 - High", Significant: "P2 - Medium-High", Average: "P3 - Medium-Low", Minimal: "P4 - Low"],
Minor: [Highest:"P2 - Medium-High" , Significant: "P3 - Medium-Low" , Average: "P4 - Low", Minimal: "P4 - Low"],
Trivial: [Highest:"P3 - Medium-Low" , Significant: "P3 - Medium-Low" , Average: "P4 - Low", Minimal: "P4 - Low"],
]

// use map lookup method to calculate priority and leave default value of Not Set if map is broken
def CalcPriority = "Not Set"
if (priorityMatrix.containsKey(severity) && priorityMatrix.find{bizimpact} != null)
{
CalcPriority = priorityMatrix[severity][bizimpact]
}
else {
log.warn "Business or Severity field selected not in matrix."
}
CalcPriority = CalcPriority.toString()
// once you have looked up the calculated priority name in the matrix, get the associated priority object based on the name

def CalcPriorityObject = constantsManager.priorityObjects.findByName(CalcPriority)

// initialize some messages with default values
def msg1 = "No Special Message."
def errmsg = "No Errors."

// check lookup of priority by name
if (CalcPriorityObject)
{
// msg1 = "Valid priority object found."
log.debug("Able to find priority based on lookup.")
}
else{
// msg1 = "Priority object not found, so leaving priority at current value."
log.warn("Not able to get priority for new defect. Leaving priority not set (default).")
}

// for debugging create a string you can write to issue comments
// def stringbody = "Current Values of Business Impact, Severity, Original Priority, Original Priority ID, New Derived Priority, New Priority ID: \n" + bizimpact + ", " + severity + ", " + OrigPriority + ", " + CalcPriority.toString() + ", " + OrigPriorityID.toString() + ", " + msg1.toString()

// this is start of code to actually set field value based on id of the Calculated priority value on the create transition only.
// first create object to hold all parameters to pass into the issue service for new issue creation
def issueInputParameters = new IssueInputParametersImpl();

issueInputParameters.setPriorityId(CalcPriorityObject.id);
issueInputParameters.setSkipScreenCheck(true);

// validate object

def validationResult = issueService.validateUpdate(user, issue.id, issueInputParameters);

if (validationResult.isValid())
{
// actually update the issue and store to db
issueService.update(user, validationResult)


// next section is for debugging - writes to comments
// errmsg = "validation succeeded. check priority. should be updated. " + issueInputParameters
// stringbody = stringbody + ", " + errmsg.toString()
// commentManager.create(issue,user,stringbody,true)
}
else {
// log warning
log.warn validationResult.errorCollection.errors
// errmsg = "could not update priority field. check logs."
// stringbody = stringbody + ", " + errmsg.toString()
// commentManager.create(issue,user,stringbody,true)
}

1 answer

Suggest an answer

Log in or Sign up to answer
0 votes
Ram Kumar Aravindakshan _Adaptavist_
Community Champion
April 15, 2022

Hi @Tommaso Scorteccia

For your requirement, you can try to trigger the email using the ScriptRunner console with something like this:-

import com.atlassian.greenhopper.service.rapid.view.RapidViewService
import com.atlassian.greenhopper.web.rapid.view.RapidViewHelper
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.mail.Email
import com.onresolve.scriptrunner.runner.customisers.JiraAgileBean
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import groovy.xml.MarkupBuilder

@WithPlugin("com.pyxis.greenhopper.jira")

@JiraAgileBean
RapidViewService rapidViewService

@JiraAgileBean
RapidViewHelper rapidViewHelper

def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def applicationProperties = ComponentAccessor.applicationProperties
def hostUrl = applicationProperties.getString('jira.baseurl')
def views = rapidViewService.getRapidViews(loggedInUser).value
final def projectKey = 'MOCK'

def emailBody = new StringWriter()
def html = new MarkupBuilder(emailBody)

html.html {
body {
views.each {
if (it.name == "${projectKey} board") {
a ( href : "${hostUrl}/secure/RapidBoard.jspa?rapidView=${it.id}&projectKey=${projectKey}") {
mkp.yield "Link to ${projectKey} board"
}
}
}
}
}

final static creatMessage(String to, String subject, String content) {
def mailServerManager = ComponentAccessor.mailServerManager
def mailServer = mailServerManager.defaultSMTPMailServer

def email = new Email(to)
email.setSubject(subject)
email.setMimeType("text/html")
email.setBody(content)

def threadClassLoader = Thread.currentThread().contextClassLoader
Thread.currentThread().contextClassLoader = mailServer.class.classLoader
mailServer.send(email)
Thread.currentThread().contextClassLoader = threadClassLoader
}

creatMessage('user@samplemail.com', 'Board Link', emailBody.toString())

Please note that the sample code provided is not 100% exact to your environment. Hence, you will need to modify it accordingly.

Below is a print screen of the email link received:-

email_link.png

Upon clicking the link, the receiver is re-directed to the board. However, you must ensure that the receiver has the required permission to view the board. 

I hope this helps to answer your question. :)

 

Thank you and Kind regards,

Ram

Tommaso Scorteccia
April 16, 2022

Hi @Ram Kumar Aravindakshan _Adaptavist_ , thank you for the example

I'm trying your script, but my board structure is quite difference, I have a one project with different board inside. For example

Project "Apple"

Boar "Develop Board"

Board "Testing Board" and so on..

I see that to identify the board you use the condition

if (it.name == "${projectKey} board")

but I can't use this condition for my scope 

What i want to do is a button in the board that send a link like window.location.href in javascript, or a function like "getcurrentboard" :)

Thank in advance

TAGS
AUG Leaders

Atlassian Community Events