ScriptRunner to search history and update a custom field

Brian October 25, 2022

I'm totally new to ScriptRunner and have been tasked to search through a project's issues' change history and find where a now-deleted old custom field was set and set the value to a brand new custom field.  Can anyone help me on this?  

 

Thanks!

2 answers

2 votes
Karim ABO HASHISH
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
October 25, 2022

The below code was tested on JIRA Server V8.20.8,

import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter


//Load needed Managers
def issueManager = ComponentAccessor.getIssueManager()
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()

//Load JQL Managers
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchService = ComponentAccessor.getComponent(SearchService)
def user = ComponentAccessor.getJiraAuthenticationContext().getUser()


//Get your project issues based on with JQL
def query = jqlQueryParser.parseQuery("Your JQL Here ")
def results = searchService .search(user , query, PagerFilter.getUnlimitedFilter())

//Loop on Issues to set the value of the new custom field based on the old one value
results.getResults().each{item ->
def issue = issueManager.getIssueObject(item.key)

// Load the new custom field object of the current issue
def newCustomField = customFieldManager.getCustomFieldObjects(issue).find {it.name == 'Your New Custom Field Name'}

// Get the latest value of the old custom field of the current issue
def oldCustomFieldHisotry = changeHistoryManager.getChangeItemsForField(issue, "Your Old Custom field Name").sort{it.getCreated()}
def oldCustomFieldValue = oldCustomFieldHisotry.size()>0?oldCustomFieldHisotry.last().to:null


//Set the new custom field with the old custom field value
if(oldCustomFieldValue != null)
{
newCustomField.updateValue(null, issue, new ModifiedValue(issue.getCustomFieldValue(newCustomField), oldCustomFieldValue),new DefaultIssueChangeHolder())
}

}

 

Brian October 27, 2022

Thank you!   I'll try it out.

tk May 9, 2023

@Brian I'm having the exact issue. I tried above code but it did nothing.

Did you find the solution?

Ken McClean
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 9, 2023

Did it do *nothing*, or did it throw an error?

 

Here's a version of the script with a lot of logging thrown in. Can you tell us what the log says?


import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter


//Load needed Managers
def issueManager = ComponentAccessor.getIssueManager()
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()

//Load JQL Managers
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchService = ComponentAccessor.getComponent(SearchService)
def user = ComponentAccessor.getJiraAuthenticationContext().getUser()


//Get your project issues based on with JQL
def query = jqlQueryParser.parseQuery("Your JQL Here ")
def results = searchService .search(user , query, PagerFilter.getUnlimitedFilter())



//Loop on Issues to set the value of the new custom field based on the old one value
results.getResults().each{item ->

log.warn("Search result: ${item}")

def issue = issueManager.getIssueObject(item.key)

log.warn("Issue: ${issue}")

// Load the new custom field object of the current issue
def newCustomField = customFieldManager.getCustomFieldObjects(issue).find {it.name == 'Your New Custom Field Name'}

log.warn(newCustomField.toString())

// Get the latest value of the old custom field of the current issue
def oldCustomFieldHisotry = changeHistoryManager.getChangeItemsForField(issue, "Your Old Custom field Name").sort{it.getCreated()}
def oldCustomFieldValue = oldCustomFieldHisotry.size()>0?oldCustomFieldHisotry.last().to:null


//Set the new custom field with the old custom field value
if(oldCustomFieldValue != null)
{
newCustomField.updateValue(null, issue, new ModifiedValue(issue.getCustomFieldValue(newCustomField), oldCustomFieldValue),new DefaultIssueChangeHolder())
}else{log.warn("${oldCustomFieldValue} was null")}

}
Like tk likes this
tk May 9, 2023

Thanks Ken. i see it shows error (null) for the old field value. I wonder why, since the old field is not null (the value in this field is text + SQL query which is bit long)

WARN [runner.ScriptBindingsManager]: null 

However if I check server log results, I see the entire value of the old field. 

 [c.o.scriptrunner.runner.ScriptBindingsManager] [com.atlassian.jira.issue.history.ChangeItemBean@29a6f8d8[fieldType=custom,field=NAME,from=<null>,fromString=<null>,to=<null>,toString=TEXTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT
tk May 10, 2023

I was able to copy it using this for one ticket:

def oldCustomFieldHisotry = changeHistoryManager.getChangeItemsForField(issue, "Custom Field").sort{it.getCreated()}
def oldCustomFieldValue = oldCustomFieldHisotry?.last().toString

//Set the new custom field with the old custom field value
if(oldCustomFieldValue != null)
{
newCustomField.updateValue(null, issue, new ModifiedValue(issue.getCustomFieldValue(newCustomField), oldCustomFieldValue),new DefaultIssueChangeHolder())
}
}

 But when i update the query for more tickets, I get this error

ERROR [common.UserScriptEndpoint]: Script console script failed: java.util.NoSuchElementException: Cannot access last() element from an empty List at Script497$_run_closure1.doCall(Script497.groovy:39) at Script497.run(Script497.groovy:28)

 I think I need to add a line to ignore and search through the list if Custom Field is not in the history search. How do I write this? :) 

Ken McClean
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 10, 2023

You could wrap it in a try/catch, so that any errors it encounters don't halt the script.  We only try/catch the part of the loop that is likely to fail, i.e. the bit that handles the change history.



import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter


//Load needed Managers
def issueManager = ComponentAccessor.getIssueManager()
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()

//Load JQL Managers
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchService = ComponentAccessor.getComponent(SearchService)
def user = ComponentAccessor.getJiraAuthenticationContext().getUser()


//Get your project issues based on with JQL
def query = jqlQueryParser.parseQuery("Your JQL Here ")
def results = searchService .search(user , query, PagerFilter.getUnlimitedFilter())

 

//Loop on Issues to set the value of the new custom field based on the old one value
results.getResults().each{item ->

 

 

log.warn("Search result: ${item}")

def issue = issueManager.getIssueObject(item.key)

log.warn("Issue: ${issue}")

// Load the new custom field object of the current issue
def newCustomField = customFieldManager.getCustomFieldObjects(issue).find {it.name == 'Your New Custom Field Name'}

log.warn(newCustomField.toString())

 

try{

 

// Get the latest value of the old custom field of the current issue
def oldCustomFieldHisotry = changeHistoryManager.getChangeItemsForField(issue, "Your Old Custom field Name").sort{it.getCreated()}
def oldCustomFieldValue = oldCustomFieldHisotry.size()>0?oldCustomFieldHisotry.last().to:null


//Set the new custom field with the old custom field value
if(oldCustomFieldValue != null)
{
newCustomField.updateValue(null, issue, new ModifiedValue(issue.getCustomFieldValue(newCustomField), oldCustomFieldValue),new DefaultIssueChangeHolder())
}else{log.warn("${oldCustomFieldValue} was null")}

}catch(Exception e){
log.warn("Encountered an error: ${e})
}

 

}

Like tk likes this
tk May 10, 2023

Thank you. That didn't solve it, but I finally solved it. Just a small change to the original script is required to solve this issue. :

import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.issue.history.ChangeItemBean

//Load needed Managers
def issueManager = ComponentAccessor.getIssueManager()
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()

//Load JQL Managers
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchService = ComponentAccessor.getComponent(SearchService)
def user = ComponentAccessor.getJiraAuthenticationContext().getUser()


//Get your project issues based on with JQL
def query = jqlQueryParser.parseQuery("Your JQL Here")
def results = searchService .search(user , query, PagerFilter.getUnlimitedFilter())


//Loop on Issues to set the value of the new custom field based on the old one value
results.getResults().each{item ->

def issue = issueManager.getIssueObject(item.key)

// Load the new custom field object of the current issue
def newCustomField = customFieldManager.getCustomFieldObjects(issue).find {it.name == 'Your Old Custom field Name'}

// Get the latest value of the old custom field of the current issue
def oldCustomFieldHisotry = changeHistoryManager.getChangeItemsForField(issue, "Your New Custom field Name").sort{it.getCreated()}
def oldCustomFieldValue = oldCustomFieldHisotry.size() > 0? oldCustomFieldHisotry.last().toString:null

//Set the new custom field with the old custom field value
if(oldCustomFieldValue != null)
{
newCustomField.updateValue(null, issue, new ModifiedValue(issue.getCustomFieldValue(newCustomField), oldCustomFieldValue),new DefaultIssueChangeHolder())
log.warn("${issue} ${oldCustomFieldValue}")
}

}
Kinnu_183
I'm New Here
I'm New Here
Those new to the Atlassian Community have posted less than three times. Give them a warm welcome!
June 5, 2024

If we update a custom field's value by following the method above, does it show up in the issue's history? (I believe it doesn't)

So, what's the right way to update a custom field's value by storing history?

0 votes
Mercedes Rothwell October 25, 2024

Did anyone figure out how to accomplish this in Jira Cloud? I tried using the original script but the "imports" aren't a thing in cloud.

Suggest an answer

Log in or Sign up to answer
DEPLOYMENT TYPE
CLOUD
PRODUCT PLAN
STANDARD
PERMISSIONS LEVEL
Product Admin
TAGS
AUG Leaders

Atlassian Community Events