Hello,
What I am trying to do is simple, and I had a partial success following other posts but not yet completely.
I want to create a ScriptRunner Listener custom script that executes every time an issue is updated, if the issue is associated with a sprint, take its end date and set it as the due date of the issue.
So far I achieved to get the code to set the due date of the issue, but I need to get the sprint end date in "dd/MMM/aa" format to work with this code (according to the documentation of the method here):
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.bc.issue.IssueService
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def issue = event.issue
IssueService issueService = ComponentAccessor.getIssueService()
// Get sprint end date
def SprintEndDate = ¿?¿?¿?
// Set Due Date
def issueInputParameters = issueService.newIssueInputParameters()
issueInputParameters.setDueDate(SprintEndDate)
def updateValidationResult = issueService.validateUpdate(loggedInUser, issue.id, issueInputParameters)
if (updateValidationResult.isValid()) {
// Validate the update
def issueResult = issueService.update(loggedInUser, updateValidationResult)
}
I also got the part to get the Sprint End Date like this:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.issue.ModifiedValue
import java.sql.Timestamp;
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def sprint = customFieldManager.getCustomFieldObjectByName("Sprint")
//End Date
Date endDate = event.issue.getCustomFieldValue(sprint)?.endDate?.first()?.toDate()
But I am having trouble merging both parts.
Can anyone help me?
Thank you so much in advance.
Have a look at this script that combines both concepts along with an additional trick of only running when the change contains an actual update to the sprint field.
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.issue.IssueEvent
import com.atlassian.greenhopper.service.sprint.Sprint
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
@WithPlugin("com.pyxis.greenhopper.jira")
def issueService = ComponentAccessor.issueService
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def event = event as IssueEvent //re-cast to help with static checking error in the Script Editor. Don't need this if you edit your script in line
def issue = event.issue
def changeItems = ComponentAccessor.changeHistoryManager.getChangeHistoryById(event.changeLog.id as Long).changeItemBeans
if (!changeItems.any { it.field == 'Sprint' }) {
//skip this listener if the sprint field wasn't changed
return
}
def sprintCf = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("Sprint")[0]
//get the current list of sprints on the issue
def sprints = issue.getCustomFieldValue(sprintCf) as List<Sprint>
if (!sprints) {
//if sprint field is empty, the change was to delete the last remaining sprint on the issue.
// You might choose to clear the due date here
return
}
//using last sprint. If there are more than one, the last one is most likely to be active the others should be closed
def sprint = sprints.last()
//convert from default dateTime to Date class so we can use the format method later
def endDate = sprint.endDate.toDate()
// Set Due Date
def issueInputParameters = issueService.newIssueInputParameters()
issueInputParameters.setDueDate(endDate.format('dd/MMM/yyyy'))
def updateValidationResult = issueService.validateUpdate(loggedInUser, issue.id, issueInputParameters)
if (!updateValidationResult.isValid()) {
log.error "Unable to edit the due date to $endDate. Error: $updateValidationResult.errorCollection"
return
}
def issueResult = issueService.update(loggedInUser, updateValidationResult)
Thank you Peter!
This helped me greatly.
I also added a slight modification to be able to keep the selected due date (if any) during the 'Issue Created' event:
// Get the event type
def eventTypeManager = ComponentAccessor.getEventTypeManager()
def eventTypeName = eventTypeManager.getEventType(event.eventTypeId).getName()
if (eventTypeName == "Issue Created") {
def DueDate = issue.getDueDate()
if (DueDate) {
// Skip this listener if the due date is set on creation
return
}
} else {
def changeItems = ComponentAccessor.changeHistoryManager.getChangeHistoryById(event.changeLog.id as Long).changeItemBeans
if (!changeItems.any { it.field == 'Sprint' }) {
// Skip this listener if the sprint field wasn't changed
return
}
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
The complete listener behaviour is:
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I thought that maybe one may want to update the subtasks' due date when the parent is changed for the previous reasons. I'll leave what I added in case it helps anybody:
// Set Due Date of sub-tasks (if any)
if (issue.isSubTask()) {
return
} else {
Collection<Issue> subTasksCol = issue.getSubTaskObjects();
// Tterate colleaction with subtasks and update duedate of each of them
for (Issue subTaskEl : subTasksCol) {
def subupdateValidationResult = issueService.validateUpdate(loggedInUser, subTaskEl.id, issueInputParameters)
if (!subupdateValidationResult.isValid()) {
log.error "Unable to edit the due date to $endDate. Error: $subupdateValidationResult.errorCollection"
return
}
def subissueResult = issueService.update(loggedInUser, subupdateValidationResult)
}
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Here is a shortcut you might appreciate to filter based on event type rather than getting and using the eventTypeManager
import com.atlassian.jira.event.type.EventType
if( event.eventTypeId == EventType.ISSUE_CREATED_ID && event.issue.dueDate){
// Skip this listener if the due date is set on creation
return
}
You still get code that's easy to read and understand.
You don't need to extract the due date from the issue, you can check that right in the if statement. This will return a groovy truthy value.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi All!
This is something I am trying to implement in our Jira Server instance.
I started with just copying the original from @Peter-Dave Sheehan with the combined code just to see what would occur.
This fails. Logs and payload showing below.
Error points to "IssueEvent" which was created in the event = event as IssueEvent code snippet.
def event = event as IssueEvent //re-cast to help with static checking error in the Script Editor. Don't need this if you edit your script in line
Code
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.issue.IssueEvent
import com.atlassian.greenhopper.service.sprint.Sprint
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
@WithPlugin("com.pyxis.greenhopper.jira")
def issueService = ComponentAccessor.issueService
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def event = event as IssueEvent //re-cast to help with static checking error in the Script Editor. Don't need this if you edit your script in line
def issue = event.issue
def changeItems = ComponentAccessor.changeHistoryManager.getChangeHistoryById(event.changeLog.id as Long).changeItemBeans
if (!changeItems.any { it.field == 'Sprint' }) {
//skip this listener if the sprint field wasn't changed
return
}
def sprintCf = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("Sprint")[0]
//get the current list of sprints on the issue
def sprints = issue.getCustomFieldValue(sprintCf) as List<Sprint>
if (!sprints) {
//if sprint field is empty, the change was to delete the last remaining sprint on the issue.
// You might choose to clear the due date here
return
}
//using last sprint. If there are more than one, the last one is most likely to be active the others should be closed
def sprint = sprints.last()
//convert from default dateTime to Date class so we can use the format method later
def endDate = sprint.endDate.toDate()
// Set Due Date
def issueInputParameters = issueService.newIssueInputParameters()
issueInputParameters.setDueDate(endDate.format('dd/MMM/yyyy'))
def updateValidationResult = issueService.validateUpdate(loggedInUser, issue.id, issueInputParameters)
if (!updateValidationResult.isValid()) {
log.error "Unable to edit the due date to $endDate. Error: $updateValidationResult.errorCollection"
return
}
def issueResult = issueService.update(loggedInUser, updateValidationResult)
Log
2022-03-31 16:46:25,661 ERROR [runner.AbstractScriptListener]: *************************************************************************************
2022-03-31 16:46:25,667 ERROR [runner.AbstractScriptListener]: Script function failed on event: com.atlassian.greenhopper.api.events.sprint.SprintUpdatedEvent, file: null
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'com.atlassian.greenhopper.api.events.sprint.SprintUpdatedEvent@1162' with class 'com.atlassian.greenhopper.api.events.sprint.SprintUpdatedEvent' to class 'com.atlassian.jira.event.issue.IssueEvent'
at Script2.run(Script2.groovy:9)
Payload
{ "projects": "[VI3] (java.util.ArrayList)", "@class": "com.onresolve.scriptrunner.canned.jira.workflow.listeners.model.CustomListenerCommand (java.lang.String)", "log": "org.apache.log4j.Logger@4ea9f02a", "friendlyEventNames": "Issue Created, SprintUpdatedEvent (java.lang.String)", "version": "7 (java.lang.Integer)", "relatedProjects": "[[key:VI3, name:Vi3 - TEST]] (java.util.ArrayList)", "name": "Custom listener (java.lang.String)", "canned-script": "com.onresolve.scriptrunner.canned.jira.workflow.listeners.CustomListener (java.lang.String)", "disabled": "false (java.lang.Boolean)", "id": "8f5afdd3-08c1-4b04-9db0-3e63b1b791e0 (java.lang.String)", "event": "com.atlassian.greenhopper.api.events.sprint.SprintUpdatedEvent@1162", "\u00a3beanContext": "com.onresolve.scriptrunner.beans.BeanFactoryBackedBeanContext@5b6d03eb", "events": "[1, com.atlassian.greenhopper.api.events.sprint.SprintUpdatedEvent] (java.util.ArrayList)"
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
The code I suggested was for a listener that was configured to listen to the "Issue Updated" event (so you can detect when an issue is assigned to a sprint).
If you are attempting to listen to the "Sprint Updated" event, the following line won't work:
def event = event as IssueEvent
Neither will
def issue = event.issue
Since I don't think Sprint events contain an issue object.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Peter-Dave Sheehan , thank you for that detail.
I was certainly confused by the events.
I updated this on my end and now I get the basic function of:
I will look to incorporate those other changes as the cases outlined by @Reno are the behavior I would expect also.
Thank you!!!!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Reno
I see you note the completed behaviors for the listener and I may have made some mistakes in my code.
The behavior I am trying to solve for:
End result is issue's due date stays in sync with the sprint end date.
Currently, I have to move an issue out of the sprint and then back in to have it adjusted or I have to make the manual update.
Any advice is appreciated!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
As I have it working now, it does not update the due date of the issue if the end date of the sprint is changed.
Since this is something that does not happen in my projects, and the sprints are fixed in time, I am not bothered, though it is a nice to have feature.
Also, find below my final code working as I descrived:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.event.issue.IssueEvent
import com.atlassian.jira.event.type.EventType
import com.atlassian.greenhopper.service.sprint.Sprint
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
@WithPlugin("com.pyxis.greenhopper.jira")
def issueService = ComponentAccessor.issueService
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def event = event as IssueEvent // Re-cast to help with static checking error in the Script Editor. Not needed when editing the script in line.
def issue = event.issue
// Get the event type
if(event.eventTypeId == EventType.ISSUE_CREATED_ID){
if(event.issue.dueDate) {
// Skip this listener if the due date is set on creation
return
}
} else {
def changeItems = ComponentAccessor.changeHistoryManager.getChangeHistoryById(event.changeLog.id as Long).changeItemBeans
if (!changeItems.any { it.field == 'Sprint' }) {
// Skip this listener if the sprint field wasn't changed
return
}
}
def sprintCf = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName("Sprint")[0]
// Get the current list of sprints on the issue
def sprints = issue.getCustomFieldValue(sprintCf) as List<Sprint>
if (!sprints) {
// If sprint field is empty, the change was to delete the last remaining sprint on the issue.
// We might choose to clear the due date here
return
}
// Using the last sprint. If there are more than one, the last one is most likely to be active, the others should be closed.
def sprint = sprints.last()
// Convert from default dateTime to Date class so we can use the format method later
def endDate = sprint.endDate.toDate()
// Set Due Date
def issueInputParameters = issueService.newIssueInputParameters()
issueInputParameters.setDueDate(endDate.format('dd/MMM/yyyy'))
def updateValidationResult = issueService.validateUpdate(loggedInUser, issue.id, issueInputParameters)
if (!updateValidationResult.isValid()) {
log.error "Unable to edit the due date to $endDate. Error: $updateValidationResult.errorCollection"
return
}
def issueResult = issueService.update(loggedInUser, updateValidationResult)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Thank you, @Reno
Agreed that once a sprint starts, the date does not change. My company does have cases where we plan our sprints but business needs take over and then the update to a sprints end date occurs prior to the start of a sprint.
Appreciate the support and the code!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
need to edit sprit date in cloud jira have permissions but disabled june 6 - june 16 needed? right now have june 6 to june 14
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.