Forums

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

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 Champion
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