Hello, Dear Community,
Long time no see 🙂
I’d like to share a ScriptRunner solution that helped me solve a specific use case.
I had a requirement to populate a Date Picker custom field with the exact date when an issue entered a specific status — in my case, “Counterparty Execution.”
As you may know, Jira does not provide a native field that stores the exact timestamp of when an issue enters a particular status.
However, this information is available in the issue history (changelog).
Using ScriptRunner, I was able to:
This approach was especially useful for backfilling historical data and improving reporting accuracy.
If you’re dealing with similar requirements (e.g., SLA tracking, lifecycle reporting, or data recovery), this approach might help.
Note: The script is for the Jira Data Center.
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
def user = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def searchService = ComponentAccessor.getComponent(SearchService)
def issueManager = ComponentAccessor.issueManager
def changeHistoryManager = ComponentAccessor.changeHistoryManager
def customFieldManager = ComponentAccessor.customFieldManager
// CHANGE THIS: Name of the field you want to update
def contractFinishedDateFieldName = "Contract Finished Date"
// CHANGE THIS: Issue Type you want to process
def targetIssueType = "Sales Contracts"
// CHANGE THIS: Status you want to track
def targetStatusName = "Counterparty Execution"
// CHANGE THIS: JQL to select issues
def jql = "issuetype = \"${targetIssueType}\" AND status WAS \"${targetStatusName}\""
// CHANGE THIS (optional):
// true → take FIRST time issue entered the status
// false → take LAST time issue entered the status
def useFirstOccurrence = true
// CHANGE THIS (optional):
// true → DO NOT overwrite if field already has value
// false → overwrite existing values
def skipIfFieldAlreadyFilled = true
// ================= DO NOT CHANGE BELOW =================
def contractFinishedDateField = customFieldManager.getCustomFieldObjectsByName(contractFinishedDateFieldName)?.first()
if (!contractFinishedDateField) {
log.warn("Field not found: ${contractFinishedDateFieldName}")
return
}
def parseResult = searchService.parseQuery(user, jql)
if (!parseResult.isValid()) {
log.warn("Invalid JQL: ${jql}")
return
}
def searchResult = searchService.search(user, parseResult.query, PagerFilter.unlimitedFilter)
log.warn("Found ${searchResult.results.size()} issues")
searchResult.results.each { documentIssue ->
MutableIssue issue = issueManager.getIssueObject(documentIssue.id)
def statusChanges = changeHistoryManager.getChangeItemsForField(issue, "status")
def matchingTransitions = statusChanges
.findAll { it.toString == targetStatusName }
.sort { it.created }
if (!matchingTransitions) {
log.warn("No '${targetStatusName}' transition found for ${issue.key}")
return
}
def targetTransition = useFirstOccurrence
? matchingTransitions.first()
: matchingTransitions.last()
if (skipIfFieldAlreadyFilled && issue.getCustomFieldValue(contractFinishedDateField)) {
log.warn("Skipped ${issue.key} (field already has value)")
return
}
def newDateValue = new java.sql.Timestamp(targetTransition.created.time)
def existingValue = issue.getCustomFieldValue(contractFinishedDateField)
contractFinishedDateField.updateValue(
null,
issue,
new ModifiedValue(existingValue, newDateValue),
new DefaultIssueChangeHolder()
)
issueManager.updateIssue(
user,
issue,
com.atlassian.jira.event.type.EventDispatchOption.DO_NOT_DISPATCH,
false
)
log.warn("Updated ${issue.key} -> ${newDateValue}")
}
Gor Greyan
0 comments