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

Earn badges and make progress

You're on your way to the next level! Join the Kudos program to earn points and save your progress.

Deleted user Avatar
Deleted user

Level 1: Seed

25 / 150 points

Next: Root

Avatar

1 badge earned

Collect

Participate in fun challenges

Challenges come and go, but your rewards stay with you. Do more to earn more!

Challenges
Coins

Gift kudos to your peers

What goes around comes around! Share the love by gifting kudos to your peers.

Recognition
Ribbon

Rise up in the ranks

Keep earning points to reach the top of the leaderboard. It resets every quarter so you always have a chance!

Leaderboard

Come for the products,
stay for the community

The Atlassian Community can help you and your team get more value out of Atlassian products and practices.

Atlassian Community about banner
4,463,816
Community Members
 
Community Events
176
Community Groups

Scriptrunner w/ Automation - Set Resolution

Edited

Seeking assistance writing a Scriptrunner script that would be invoked by a Project Automation Action to set/clear the Resolution.  We cannot use Post Functions or Transition Screens in our instance, since we have thousands of workflows all managed by our Project Admins (who remove & break transitions constantly).  Using a Global Project Automation Rule allows projects to decide how they want their Resolution set, so it can be enabled on a per-project basis.

The script could be written so that it sets the Resolution to "Done" or "None" based status category. The Rule could look like this:

  • Event:  Issue Transitioned
    • Condition: None 
      • Action:  Execute Script
        (which sets Resolution to Done or None based on Status Category)

Or, instead of putting it all in one script, I think I can also configure the rule with Conditions that trigger different scripts, one to set to Done, other to set to None.

The Project Automation Rule will look something like this:

  • Event:  Issue Transitioned
    • Condition: If {{issue.status.statusCategory}} equals "Done"
      • Action: Execute Script1
        (which sets Resolution to Done)
    • Condition: If {{issue.status.statusCategory}} does not equal "Done": 
      • Action: Execute Script2
        (which clears Resolution)
import com.atlassian.jira.util.ImportUtils
import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.component.ComponentAccessor

def is = ComponentAccessor.issueService
def iip =is.newIssueInputParameters()
iip.setSkipScreenCheck(true) //only need this if the resolution is not editable
iip.resolutionId = issue.status.statusCategory.primaryAlias == 'Done' ? '10000' : null

def result = is.validateUpdate(currentUser, issue.id, iip)
assert result.valid : result.errorCollection.errors
def updatedIssue = is.update(currentUser, result, EventDispatchOption.DO_NOT_DISPATCH, false).issue

//updatedIssue.resolutionDate = new Date().toTimestamp()
//issue.store()

//update the JIRA index so that jql  for "resolved" work
//boolean wasIndexing = ImportUtils.isIndexIssues()
//ImportUtils.setIndexIssues(true)
//ComponentAccessor.getComponent(IssueIndexingService.class).reIndex(updatedIssue)
//ImportUtils.setIndexIssues(wasIndexing)

Capture.PNG

Any help is greatly appreciated!

 



Final Update:

I was able to put together an Automation Rule/Scriptrunner Script combo that will edit the Resolution & Resolved date based on the Status Category.

I leveraged more of the Rule Conditions to make the scripts as simple as possible, since I'm not Scriptrunner savvy and don't want to create something I can't fix.  The scripts will probably evolve over time, but for now, it does what it needs to do.

 


Automation Rule Configurations:

  • Event:  Issue Transitioned
    • Condition:  (none)  
      • Action:  Execute Script
        Sets Resolution based on Status Category
        • Status Category = To Do, In Progress
          Resolution = None (null)
        • Status Category = Done
          Resolution = Done
  • Then, if:
    • Condition:  Status Category = To Do, In Progress
      • Action:  Execute Script
        • Set Resolved date = None (null)
  • Else if:
    • Condition:  Status Category = To Do, In Progress
      • Action:  Execute Script
        • Set Resolved date = Now

Set Resolution Rule Breakdown.PNG

Set Resolution Script:

import com.atlassian.jira.util.ImportUtils
import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.component.ComponentAccessor
import java.sql.Timestamp
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.MutableIssue

def is = ComponentAccessor.issueService
def iip =is.newIssueInputParameters()

iip.setSkipScreenCheck(true) //only need this if the resolution is not editable
iip.resolutionId = issue.status.statusCategory.primaryAlias == 'Done' ? '10000' : null

def result = is.validateUpdate(currentUser, issue.id, iip)
assert result.valid : result.errorCollection.errors

def updatedIssue = is.update(currentUser, result, EventDispatchOption.DO_NOT_DISPATCH, false).issue
Set Resolved date to Now Script
import java.sql.Timestamp
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.MutableIssue

def issueMutable = issue as MutableIssue
def issueManager = ComponentAccessor.issueManager
def issueService = ComponentAccessor.issueService
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser

issueMutable.setResolutionDate(new Timestamp(System.currentTimeMillis()))

issueMutable.store()

 

Set Resolved date to Null Script

import java.sql.Timestamp
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.MutableIssue

def issueMutable = issue as MutableIssue
def issueManager = ComponentAccessor.issueManager
def issueService = ComponentAccessor.issueService
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser

issueMutable.setResolutionDate(null)

issueMutable.store()
Thanks again for the help @Peter-Dave Sheehan 
Hopefully this is helpful to others seeking to escape the administrative tyranny of Resolution configurations.

5 answers

2 accepted

3 votes
Answer accepted

I haven't done much with Automation.

So I'm not sure if each actions are expected to be independent, of if they are typically  cummulative  against an ephemeral issue object in memory and only persisted at the end of the automation rule execution (once all actions have been gathered).

But assuming you want the action to be 100% standalone, you could write a script like this:

import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.component.ComponentAccessor
def is = ComponentAccessor.issueService
def iip =is.newIssueInputParameters()
iip.setSkipScreenCheck(true) //only need this if the resolution is not editable
iip.resolutionId =
null

def result = is.validateUpdate(currentUser, issue.id, iip)
assert result.valid : result.errorCollection.errors
is.update(currentUser, result, EventDispatchOption.DO_NOT_DISPATCH, false)

If you want to set the resolution instead of clear it, you can replace "null" with the id of the resolution you want to set (with double quotes).

Might as well put your logic in the script:

iip.resolutionId = issue.status.statusCategory.primaryAlias == 'Done' ? '11' : null

Resolution Done is "11" in my environment and

Status Category Done, actually has the name "Complete" and the value "Done" is fund under primaryAlias.

@Peter-Dave Sheehan ,

Thanks for getting me started, and apologies for not being more Groovy fluent so I can do some of my own troubleshooting.  Java/Groovy training is def in my future.  

Dropped it into the console and was greeted with some red.   Curious if you're on Cloud and I'm on Server?

I'll start with the "is."related ones:

Resolution Script.PNG

[Static type checking] - Cannot find matching method com.atlassian.jira.bc.issue.IssueService#validateUpdate(java.lang.Object, java.lang.Object, com.atlassian.jira.issue.IssueInputParameters). Please check if the declared type is correct and if the method exists.

[Static type checking] - Cannot find matching method com.atlassian.jira.bc.issue.IssueService#update(java.lang.Object, java.lang.Object, com.atlassian.jira.event.type.EventDispatchOption, boolean). Please check if the declared type is correct and if the method exists.

Again, appreciate the assistance!

Hi @Timothy Ryner 

I'm on server. I'm careful to check which questions I can safely answer or note based on the environment type since I don't have much experience with cloud.
The first 2 lines would not be possible at all on cloud. There is no access to any com.atlassian classes.

It's normal to see some red like this in the Script Editor (which looks to be where you are) and in the console.

In any given script context, ScriptRunner will include several variables in what's called the "binding" for the script. Which variables are available will depend on the content.

But in the script editor, there is no awareness of where the script will be used and what variables are expected to be available.

In all those context places, you will see a question mark icon below the script box that you can click to get the details of what variables are available.

In a project automation rule, you will see that "issue" and "currentUser" are both in the list.
Generally, "static type checking errors" are not always fatal. It's just the script editor telling you it can't predict and verify the code. 

Now that you've saved your script, you can go back to project automation rule and point an "execute script" block to that file. You should see the "green dot" indicating the script compiled fine in that context.

Great!  Thanks for the explanation, makes perfect sense!  Doing some testing now. 

I am noticing that the Resolved Date is not being set.  I know there is a bug with Automation with this exact behavior, but I was hoping that setting the Resolution by the script would negate that bug.   Does the script potentially bypass some of Jira's logic for setting the Resolved Date at the same time as Resolution, and thus an additional action needs to be included?

Resolution Script Troubleshooting.PNG

It looks like the only way to set this value is with an internal method that has some very strongly worded warning against using it

But if you want to disregard those warnings:

import com.atlassian.jira.util.ImportUtils
import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.component.ComponentAccessor


def is = ComponentAccessor.issueService
def iip =is.newIssueInputParameters()
iip.setSkipScreenCheck(true) //only need this if the resolution is not editable
iip.resolutionId = issue.status.statusCategory.primaryAlias == 'Done' ? '11' : null

def
result = is.validateUpdate(currentUser, issue.id, iip)
assert result.valid : result.errorCollection.errors
def updatedIssue = is.update(currentUser, result, EventDispatchOption.DO_NOT_DISPATCH, false).issue

updatedIssue.resolutionDate = new Date().toTimestamp()
issue.store()

//update the JIRA index so that jql  for "resolved" work
boolean wasIndexing = ImportUtils.isIndexIssues()
ImportUtils.setIndexIssues(true)
ComponentAccessor.getComponent(IssueIndexingService.class).reIndex(updatedIssue)
ImportUtils.setIndexIssues(wasIndexing)

It would be possible to combine setting the resolutionId and the date the same way,m but I felt that the littlest change using "store()" possible was best.

I think I'll heed the warnings, at least for now.  Here's where I have landed. 

  • Using your script with few edits:
    • update the ResolutionID (10000)
    • commented out the Resolution Date 
import com.atlassian.jira.util.ImportUtils
import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.component.ComponentAccessor

def is = ComponentAccessor.issueService
def iip =is.newIssueInputParameters()
iip.setSkipScreenCheck(true) //only need this if the resolution is not editable
iip.resolutionId = issue.status.statusCategory.primaryAlias == 'Done' ? '10000' : null

def result = is.validateUpdate(currentUser, issue.id, iip)
assert result.valid : result.errorCollection.errors
def updatedIssue = is.update(currentUser, result, EventDispatchOption.DO_NOT_DISPATCH, false).issue

//updatedIssue.resolutionDate = new Date().toTimestamp()
//issue.store()

//update the JIRA index so that jql  for "resolved" work
boolean wasIndexing = ImportUtils.isIndexIssues()
ImportUtils.setIndexIssues(true)
ComponentAccessor.getComponent(IssueIndexingService.class).reIndex(updatedIssue)
ImportUtils.setIndexIssues(wasIndexing)

 

  • Made a wildly simply Automation Rule
    • Event:  Issue Transitioned
    • Action: Run Script
      • Set it to execute immediately, which sets the Resolution ahead of the page refresh, so the issue shows the new Resolution immediately.

Resolution Script Rule.PNG

 

  • Tested with an issue that has none of the standard Resolution configs 
    • Not a Simplified Workflow
    • No Post-functions
    • No Transition Screens for setting Resolution
      (did use transition screens for Comments, just to ensure those configs don't interfere)

Resolution Script Function.PNG

  • The Outcome
    • Resolution = Done when Status Category = Done.
    • Resolution = Unresolved (null) when Status Category != Done
    • Resolved Date is always None.

This is as-close to a solution as I think we can safely get.  The Resolved Date is a bummer, but setting the Resolution automatically is a great improvement for our admin team and our users.  I appreciate the help!!  I hope other folks also find this useful!!

FYI, you don't need the indexing block (last 4 lines) and corresponding imports if you are not updating the resolutionDate and storing the value.

The issueService.update() method will take care of indexing the changes presented in the issueInputParameter (iip)

@Peter-Dave Sheehan ,

You've helped a bunch, so feel free to tap out if you don't want to go down this rabbit hole.  Not being able to set/clear the Resolved Date is pretty significant. 

But then how does Scriptrunner's bulk resolution edit built-in script work so effectively?  

I wonder if that script's configs can be located somehow, to see if there is any magic that can be replicated.

I swear, if Adaptivist were to rewrite that bulk resolution script into a standalone feature for project admins to use, give it similar functionality to what we're doing here, make it into a plugin, charge $10...they'd make hundreds of dollars!

One would need access to the scriptrunner sourcecode.

The .jar file includes a class you can attempt to decompile, but it won't make much sense.

It could very well be that under the covers, both the jira default functionality as well as the scriptrunner BulkFixResolution both use the issue.store() method. 

But even looking through the Jira source code, I don't understand why setting the resolution doesn't set the date correctly.

public void setResolution(Resolution resolution) {
final Resolution oldValue = getResolution();

this.resolution = resolution != null ? constantsManager.getResolution(resolution.getId()) : null;

modifiedFields.put(IssueFieldConstants.RESOLUTION, new ModifiedValue(oldValue, this.resolution));

if (resolution != null && resolution.getId() != null) {
String oldResolutionId = resolutionId;
resolutionId = resolution.getId();
//changing the resolution, also needs to update the resolution date.
if (!resolutionId.equals(oldResolutionId) || resolutionDate == null) {
setResolutionDate(new Timestamp(System.currentTimeMillis()));
}
} else {
resolutionId = null;
//if we are changing back to an unresolved state, also clear the resolution date.
setResolutionDate(null);
}

updateGV(IssueFieldConstants.RESOLUTION, resolutionId);
}

So it's a puzzle. One that I'm no longer attempting to solve.

Like Timothy Ryner likes this

I'm taking the convo to Adaptivist to see what they say. If I get useful info, you'll be the first to get it.  I'd love to see this puzzle solved.

Resolved Date set/clear via script triggered by Automation - ScriptRunner for Jira Server/Data Center - Service Desk (adaptavist.com)

Like Peter-Dave Sheehan likes this

So, did you get confirmation from Adaptavist that using the issue.store() is what they're doing in their built-in script behind the scene?

I indirectly asked, but the individual who assisted me didn't divulge, and instead focused on my script.

I'll inquire more-directly, maybe they'll show a little of how the fudge is made.

For the record, I can't view the ticket you linked above.

But if they validated your script that contains

issueMutable.setResolutionDate(null)
issueMutable.store()

Without raising a warning similar to what's in the documentation, then that's good enough for me.

I knew you wouldn't be able to get to my support request.  Thought I'd provide it anyway, and if you wanted to see it, you'd pass over your email so I could share it.  :)

 

And I asked more-directly.  We'll see what they say!

Adaptivist's response.  They were great to work with.  

If I had more dev chops, I'd decompile, but I'm good for now.  Thanks again for the help!

Great, glad to know I can help with the requirement.

Related to the built-in "Bulk Fix Resolutions", you can attempt to decompile class in the jar file. You will be able to access the code accordingly. It seems to me the script design is similar. However, keep in mind that the built-in script only runs once, and the approach you are taking will require to trigger every time the event is triggered. It seems to me Atlassian is trying to prevent the user from setting the resolution date programmatically as it may impact the issue consistency. For example, the user can set a resolution date while the issue is still in progress.

Unfortunately, I have tested on my end using a specific approach. Still, it does not update the Resolution date, and the other method uses a different format to set the Resolution date value. For the API usage, it is provided by Atlassian to update the issue, and we have a limitation that we can proceed with Jira API. If you are looking for a different method, my suggestion would be to reach out to Atlassian support if they can suggest the usage of the API.

I hope this answers your questions. Please let me know if you have further questions

Like Peter-Dave Sheehan likes this

A couple new notes:

Special "Automation" Account

I needed to create a special 'automation' account that has enough permissions in all projects to execute the rule/script, instead of tying it to my account.

A Few Errors

The scripts/rules run thousands of times per day, and I see about 3 errors per day on average (usually on the same issue being moved around a lot on the same day).

Those errors are always related to some issue configuration that doesn't seem like it should impact the script, but it does for some reason.  I've not looked into this further.

Most of the time, the error doesn't affect the outcome. 
Since the script runs even for every transition, even if the transition is to the same status category (To Do category > To Do category), the Resolution wouldn't change anyway, even if the script was successful.  So I'm good with the error in this instance.

Error Workaround

The only time it affects the outcome is when a transition would have resulted in a change to the Resolution.  My workaround for now is to run the Scriptrunner Bulk Resolution script.  I have 2 filters that I run against about once per week, edits 1 or 2 issues.  To me, this is very low up-keep, compared to how much time I spent modifying workflows previously, so it's acceptable for now.

Error Examples

Taken from the Audit Log of the Automation Rule that runs the scripts:


Action details:

Execute a ScriptRunner script action for Automation for Jira

Unexpected error executing rule:
{components=Components <scrubbed component name> are not valid for project '<scrubbed project name>'.}. Expression: result.valid
Script function failed on Automation for Jira rule:
Script function failed on Automation for Jira rule: **AUTO RESOLUTION RULE**, file: Auto Resolution Scripts/Set_Resolution.groovy, error: java.lang.AssertionError: {components=<scrubbed component name> are not valid for project '<scrubbed project name>'.}. Expression: result.valid
Action details:

Execute a ScriptRunner script action for Automation for Jira

Unexpected error executing rule:
{issuetype=Issue type is a sub-task but parent issue key or id not specified.}. Expression: result.valid
Script function failed on Automation for Jira rule:
Script function failed on Automation for Jira rule: **AUTO RESOLUTION RULE**, file: Auto Resolution Scripts/Set_Resolution.groovy, error: java.lang.AssertionError: {issuetype=Issue type is a sub-task but parent issue key or id not specified.}.
Expression: result.valid
Action details:

Execute a ScriptRunner script action for Automation for Jira

Unexpected error executing rule:
{customfield_10504=User '<scrubbed username>' is not valid for this user picker.}. Expression: result.valid
Script function failed on Automation for Jira rule:
Script function failed on Automation for Jira rule: **AUTO RESOLUTION RULE**, file: Auto Resolution Scripts/Set_Resolution.groovy, error: java.lang.AssertionError: {customfield_10504=User '<scrubbed username>' is not valid for this user picker.}. Expression: result.valid

Three months later, and I can say that this has been wildly successful. 

The auto-resolution script runs around 7000 times a day, happily setting/clearing the Resolution & Resolved date.  I created a new field "Outcome", populated it with the same field options as Resolution, copied over the values globally, and placed it on the transition screens in place of Resolution. 

  • No customers are throwing a fit.  If you didn't know better, the system now functions how you'd think it would, and wouldn't suspect that it's a customization.
  • It passively fixed dozens of project workflows that were missing the post-function configurations to set the Resolution.  So instead of having thousands of issues with missing Resolutions, they're always set, regardless of how many configuration changes the Project Admins make to their workflows.
  • Customers can still capture data granularity with the "Outcome" field, just as they could before with Resolution (and with more options, because Project Admins can edit their field options).
  • Significant time has been freed up for the Admin Team not having to reconfigure workflows or teaching people about Simplified Workflows.

So again, I appreciate the help!  

Final Update:

I was able to put together an Automation Rule/Scriptrunner Script combo that will edit the Resolution & Resolved date based on the Status Category.

I leveraged more of the Rule Conditions to make the scripts as simple as possible, since I'm not Scriptrunner savvy and don't want to create something I can't fix.  The scripts will probably evolve over time, but for now, it does what it needs to do.

 


Automation Rule Configurations:

  • Event:  Issue Transitioned
    • Condition:  (none)  
      • Action:  Execute Script
        Sets Resolution based on Status Category
        • Status Category = To Do, In Progress
          Resolution = None (null)
        • Status Category = Done
          Resolution = Done
  • Then, if:
    • Condition:  Status Category = To Do, In Progress
      • Action:  Execute Script
        • Set Resolved date = None (null)
  • Else if:
    • Condition:  Status Category = To Do, In Progress
      • Action:  Execute Script
        • Set Resolved date = Now

Set Resolution Rule Breakdown.PNG

Set Resolution Script:

import com.atlassian.jira.util.ImportUtils
import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.component.ComponentAccessor
import java.sql.Timestamp
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.MutableIssue

def is = ComponentAccessor.issueService
def iip =is.newIssueInputParameters()

iip.setSkipScreenCheck(true) //only need this if the resolution is not editable
iip.resolutionId = issue.status.statusCategory.primaryAlias == 'Done' ? '10000' : null

def result = is.validateUpdate(currentUser, issue.id, iip)
assert result.valid : result.errorCollection.errors

def updatedIssue = is.update(currentUser, result, EventDispatchOption.DO_NOT_DISPATCH, false).issue
Set Resolved date to Now Script
import java.sql.Timestamp
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.MutableIssue

def issueMutable = issue as MutableIssue
def issueManager = ComponentAccessor.issueManager
def issueService = ComponentAccessor.issueService
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser

issueMutable.setResolutionDate(new Timestamp(System.currentTimeMillis()))

issueMutable.store()

 

Set Resolved date to Null Script

import java.sql.Timestamp
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.MutableIssue

def issueMutable = issue as MutableIssue
def issueManager = ComponentAccessor.issueManager
def issueService = ComponentAccessor.issueService
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser

issueMutable.setResolutionDate(null)

issueMutable.store()
Thanks again for the help @Peter-Dave Sheehan 
Hopefully this is helpful to others seeking to escape the administrative tyranny of Resolution configurations.

Script Runner is giving me a warning about the issueMutable.store() line:

  • Use the Object's Service or Manager to save values. Since v5.0. DO NOT USE THIS as it overwrites all the fields of the issue which can result in difficult to reproduce bugs Prefer to use QueryDslAccessor to change only needed fields

Any suggestions?

Suggest an answer

Log in or Sign up to answer
DEPLOYMENT TYPE
SERVER
VERSION
8.22.4
TAGS

Atlassian Community Events