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:
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:
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)
Any help is greatly appreciated!
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.
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
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()
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.
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:
[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!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I think I'll heed the warnings, at least for now. Here's where I have landed.
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)
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!!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Following line is wrong
issue.store()
This is the correct one
updatedIssue.store()
And thank you for all the effort you put into this!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
It's been running for a solid year now, quick update:
I'm going to start a yearly tradition of mailing Peter roses and chocolates on Valentines Day, because I'm in love with this script. Thanks again!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Since going to Jira v9.7, started having a problem with issues not indexing after being updated. The values were being written, but the issues weren't being indexed, so it would appear that hundreds of issues a day were not being updated when in fact they were. For a few days, I just ran the background reindex a bunch of times to keep issues current while I figured out a solution.
I've made a couple changes:
Reindex script:
import com.atlassian.jira.event.issue.AbstractIssueEventListener;
import com.atlassian.jira.event.issue.IssueEvent;
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.util.ImportUtils;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.MutableIssue;
import com.atlassian.jira.issue.index.IssueIndexingService;
MutableIssue mutableIssue = ComponentAccessor.getIssueManager().getIssueObject(issue.getKey());
boolean isIndex = ImportUtils.isIndexIssues();
ImportUtils.setIndexIssues(true);
IssueIndexingService IssueIndexingService = (IssueIndexingService) ComponentAccessor.getComponent(IssueIndexingService.class);
IssueIndexingService.reIndex(mutableIssue);
ImportUtils.setIndexIssues(isIndex);
After these changes, the Auto Resolution Rule is killing it! I haven't had to modify a single workflow post function in almost 12 months, which has saved a significant amount of admin time!
Still one of the best things I've ever implemented on our instance!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Timothy Ryner , I am lookin for something similar. Would you be able to suggest if your script could be used for my use case.
So, I want to run a script from the console to move an issue back from DONE to the IN PROGRESS status. Though I am able to achieve this, the Resolution of the issue still shows as 'done". . No matter what, the issues moves to the "In progress' status but the resolution is still showing as 'done"
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
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.
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.
Taken from the Audit Log of the Automation Rule that runs the scripts:
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
So again, I appreciate the help!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
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
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()
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Script Runner is giving me a warning about the issueMutable.store() line:
Any suggestions?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I elected to use it anyway. Not sure if it will have long-term implications, but so far so good.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.