Hello everyone,
We want to have a field that shows the (last) user who solved an issue. We define resolving by the transition when resolution field is set.
I tried to use scriprunner Custom Script Field (User Picker - single user).
I didn't find any best practices for scriprunner and tried to make my own scripted field. However, I failed by returning a com.atlassian.jira.user.ApplicationUser. I can get the userkey, but how do I get an ApplicationUser from a given userkey.
Can anyone help me with this?
import com.atlassian.jira.issue.changehistory.ChangeHistoryItem
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.component.ComponentAccessor
def changeHistoryManager = ComponentAccessor.changeHistoryManager
def changer = ApplicationUser
def changerKeyString
// Every resolution change is checked
def changeItems = changeHistoryManager.getAllChangeItems(issue)
changeItems.reverse().each { ChangeHistoryItem item ->
if(item.getField() == "resolution" && item.getTos()){
log.warn("resolutino changed")
changer = item.getUserKey()
changerKeyString = item.getUserKey()
log.warn(changerKeyString)
log.warn(item)
}
}
In an test example I get the following:
2023-12-21 15:01:51,004 WARN [runner.ScriptBindingsManager]: resolutino changed
2023-12-21 15:01:51,004 WARN [runner.ScriptBindingsManager]: JIRAUSER152025
2023-12-21 15:01:51,004 WARN [runner.ScriptBindingsManager]: ChangeHistoryItem{id=18724097, changeGroupId=14400601, userKey='JIRAUSER152025', field='resolution', projectId=1234, issueId=123789, issueKey='PLAN-9959', created=2023-10-20 16:14:17.0, nextChangeCreated=292278994-08-17 08:12:55.807, fromValues={}, toValues={10200=Rejected}}
The problem with your original code is that the ChangeHistoryItem "userKey" is not actually the user's key or user's name. It's actually the friendly "user display".
You need to fetch the "ChangeHistory" object to know the author of the change.
Here is a short script that should work in your Scripted Field:
import com.atlassian.jira.component.ComponentAccessor
def historyManager = ComponentAccessor.changeHistoryManager
def changeItems = historyManager.getAllChangeItems(issue)
def lastResolutionItem = changeItems.findAll { it.field == 'resolution' && it.tos }.max { it.created }
def user = historyManager.getChangeHistoryById(lastResolutionItem.changeGroupId).authorObject
if (!user) {
log.warn "Could not find user for $issue.key changeHistory with last change of resolution: $lastResolutionItem"
return null
}
return user
As you can see, you can use Groovy collection processing to quickly reduce the list of all change items to A) just those where the resolution field was changed and where the "tos" value is not blank (to exclude elements when the resolution is cleared) and then B) to only show the latest using the "max" method.
Then we get the ChangeHistory record using the changeGroupId and retrieve the authorObject (as ApplicationUser automatically)
If you're interested in my suggestions in the other response, you can use a listener for "issueUpdated" event:
import com.atlassian.jira.component.ComponentAccessor
def changeHistory = ComponentAccessor.changeHistoryManager.getChangeHistoryById(event.changeLog.id as Long)
def changeItems = changeHistory.changeItemBeans
if(!changeItems.any{it.field == 'resolution' && it.toString}) return //resolution field was not set during this event
event.issue.update{
setCustomFieldValue('Resolved By', changeHistory.authorObject)
}
And a console script to back-fill existing issues (adjust the JQL as needed, I used that for my own tests):
import com.atlassian.jira.component.ComponentAccessor
def historyManager = ComponentAccessor.changeHistoryManager
def jql = 'project=ASP and resolved > -80d and resolution is not empty '
Issues.search(jql).each{issue->
def changeItems = historyManager.getAllChangeItems(issue)
def lastResolutionItem = changeItems.findAll{it.field == 'resolution' && it.tos }.max{it.created}
def user = historyManager.getChangeHistoryById(lastResolutionItem.changeGroupId).authorObject
if(!user){
log.warn "Could not find user for $issue.key changeHistory with last change of resolution: $lastResolutionItem"
return null
}
issue.update{
setCustomFieldValue('Resolved By', user)
}
}
You would need to create a new Resolved By field, as this console script and the listener will not work with a scripted field.
I would not recommend the scripted field approach in this case.
A user resolving an issue typically happens only once in the lifetime of the issue. But with a scripted field, the script will have to be executed each time you view or index the issue forever and ever.
Scripted fields work best for data that is very dynamic.
In your case, I would recommend either setting the field during a workflow postfunction or using a listener. This way, the code will run once when the is resolved and never again (unless the workflow has a path to go back and re-resolve). This should be more efficient in the long run.
You can run a simple script in your console to back-fill issues that are already resolved (only need to do this once).
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @PD Sheehan ,
appreciate your idea. Seems more logic to me, to do this in postfunction or listener (issue resolved event).
I guess this is the more wise approche :D BUT...
I will think about that. I guess this will "cost" me more time since there are many workflows in this instance and I have to go over all of them. Checking / changeing postfunctions for setting the field or checking if they throw the correct event. I'm not sure if this was maintained properly in the past.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Another downside (apart from modifying each workflow) of this approach is that it will not work for already resolved tickets.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You have two options:
1. Continue with your current code and use usermanager.getUserByKey(item.getUserKey()) to cast to an ApplicationUser.
2. Alternatively, use `changeHistoryManager.getChangeHistories(issue)` to retrieve all change histories. Then, check if they are related to the resolution field, and if so, use the changeHistory.getAuthorObject() method to return an `ApplicationUser` object."
I hope it helps!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Tuncay,
thanks for your fast response! I tried both. Howeever, its still not working :(
I did not manage to cast to ApplicationUser in my first idea.
Then I started to do your suggested alternative. But this results in Anonymous-User
Any guess?
import com.atlassian.jira.issue.changehistory.ChangeHistory
import com.atlassian.jira.issue.history.ChangeItemBean
import com.atlassian.jira.user.util.UserManager
import com.atlassian.jira.issue.changehistory.ChangeHistoryItem
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.component.ComponentAccessor
def changeHistoryManager = ComponentAccessor.changeHistoryManager
def changer = ApplicationUser
def changerKeyString
//Liste <ChangeHistory>
def historys = changeHistoryManager.getChangeHistories(issue)
historys.reverse().each { ChangeHistory history ->
changer = history.getAuthorObject()
//log.debug(history.getAuthorObject())
//Liste <ChangeItemBean>
def historyEntries = history.getChangeItemBeans()
//log.error(history)
historyEntries.reverse().each { ChangeItemBean itembean ->
if(itembean.getField() == "resolution" && itembean.getTo()){
log.warn("resolution changed!")
log.warn(itembean)
log.warn(history.getAuthorObject())
changer = history.getAuthorObject()
return changer
}
}
}
2023-12-22 08:15:29,384 WARN [runner.ScriptBindingsManager]: resolution changed!
2023-12-22 08:15:29,384 WARN [runner.ScriptBindingsManager]: com.atlassian.jira.issue.history.ChangeItemBean@73b817ce[fieldType=jira,field=resolution,from=<null>,fromString=<null>,to=1,toString=Fixed,created=2023-12-11 15:15:52.0]
2023-12-22 08:15:29,384 WARN [runner.ScriptBindingsManager]: i00g15(i00g15)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Did you try returning the result (changer) from the upper block of your code? Instead of returning within each closure of the loop, you could declare a variable outside the loop. Populate this variable with the value as you iterate through the loop, and then return this variable after the loop completes.
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.