Trigger a script runner script on sprint events

Mads Stavang September 22, 2016

I want to transit all non-resolved issues in a Sprint to a specific workflow status, whenever a Sprint is closed. In order to do so, I need to latch onto the Sprint end event.

So, I have found out that the only place to latch onto this event, is at JIRA Webhooks. My idea was to add ScriptRunner REST Endpoints to reroute the Webhook back to ScriptRunner through the Webhook and go on from there.

Now as you can see, this is a cumbersome way to accomplish this. My questions are: 

  • Is there a better way to do this?
  • If yes, how can I accomplish this
  • If no, I would appreciate if anyone has a working example of something similar

 

5 answers

1 accepted

3 votes
Answer accepted
adammarkham
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.
September 23, 2016

JIRA Software 7.1+ seems to have a com.atlassian.greenhopper.api.events.sprint.SprintClosedEvent. Unfortunately ScriptRunner only handles JIRA events for scripted event handlers (ones that extend JiraEvent or IssueEvent). The sprint closed event only extends AbstactSprintEvent so you can't write a scripted event handler at present.

Please track and vote on this issue SRJIRA-1910 which would let you do that.

Its true that you can programatically register an event handler using the event publisher but then you have to handle registering and unregistering on startup and you could get into a bit of a mess so we will avoid it for this solution.

You should setup a web hook for the sprint closed event and your url should look something like:

<jira-base-url>/rest/scriptrunner/latest/custom/sprintClosed/${sprint.id}

You'll also need to add the following script REST endpoint script: 

import com.atlassian.greenhopper.service.rapid.view.RapidViewService
import com.atlassian.greenhopper.service.sprint.SprintManager
import com.atlassian.greenhopper.web.rapid.chart.HistoricSprintDataFactory
import com.atlassian.jira.component.ComponentAccessor
import com.onresolve.scriptrunner.runner.customisers.PluginModuleCompilationCustomiser
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript

import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

@WithPlugin("com.pyxis.greenhopper.jira")

@BaseScript CustomEndpointDelegate customEndpointDelegate

sprintClosed(httpMethod: "POST") { MultivaluedMap queryParams, String body -&gt;
    def extraPath = extraPath as String
    def sprintId = extraPath.replace("/", "")
    def userKey = queryParams.getFirst("user_key")

    def sprintManager = PluginModuleCompilationCustomiser.getGreenHopperBean(SprintManager)
    def historicSprintDataFactory = PluginModuleCompilationCustomiser.getGreenHopperBean(HistoricSprintDataFactory)
    def rapidViewService = PluginModuleCompilationCustomiser.getGreenHopperBean(RapidViewService)

    def issueService = ComponentAccessor.getIssueService()
    def userManager = ComponentAccessor.getUserManager()

    def user = userManager.getUserByKey(userKey)

    def closedSprint = sprintManager.getSprint(sprintId.toLong()).value
    def view = rapidViewService.getRapidView(user, closedSprint.rapidViewId).value
    def sprintContents = historicSprintDataFactory.getSprintOriginalContents(user, view, closedSprint)

    def sprintData = sprintContents.value

    if (sprintData) {
        def incompleteIssues = sprintData.contents.issuesNotCompletedInCurrentSprint*.issueId

        incompleteIssues.each { issueId -&gt;
            // This is the id of the transition you want to make
            def actionId = 21

            def transitionValidationResult = issueService.validateTransition(user, issueId, actionId, issueService.newIssueInputParameters())

            if (transitionValidationResult.isValid()) {
                issueService.transition(user, transitionValidationResult)
            } else {
                log.warn transitionValidationResult.errorCollection.errorMessages
            }
        }
    }

    return Response.noContent().build()
}

That will transition all the incomplete issues to a specified status. You will need to replace actionId in the script with the id you want to transition to which you can find in the workflow.

The user that has closed the sprint will need to have permissions to transition the issue as well.

When you close the sprint the page it shows the incomplete issues on doesn't seem to have the status updated to reflect the transition we did, there doesn't seem like a way I can fix this small bug. But if you click into the issue it will have the correct status.

Note that the body in the script contains data like:

{
	"timestamp": 1474627199320,
	"webhookEvent": "sprint_closed",
	"sprint": {
		"id": 8,
		"self": "http://localhost:8080/jira/rest/agile/1.0/sprint/8",
		"state": "closed",
		"name": "Sample Sprint 7",
		"startDate": "2016-09-23T11:39:43.555+01:00",
		"endDate": "2016-10-07T11:39:00.000+01:00",
		"completeDate": "2016-09-23T11:39:59.318+01:00",
		"originBoardId": 1
	}
}

In case you wanted to extract anymore data out but you should be able to get most of that from the closedSprint object in the script.

Hope this helps.

Adam

 

Mads Stavang September 23, 2016

Adam, you're the best! I will test this immediately.

Mads Stavang September 23, 2016

Yup, that did it. So, I am trying to understand your code, and I am already stumbling in your first line in the closure:

def extrapath = extrapath as String

This is also related to something else I do not understand, the REST Endpoint URL. So, in your documentation, <jira-base-url>/rest/scriptrunner/latest/custom/sprintClosed should be matched with the sprintClosed Method. But, we append /${sprint.id} to the Webhook-URL. Does this mean that is <jira-base-url>/rest/scriptrunner/latest/custom/sprintClosed/<whatever> always matched with the sprintClosed Endpoint, and /<whatever> is put into the extrapath variable? I have never written a groovy code ever, so perhaps I am missing some syntax magic.

adammarkham
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.
September 23, 2016

Yes thats correct. The extraPath is not really documented in ScriptRunner but it will give you anything in the path after "sprintClosed".

Mads Stavang September 23, 2016

Thanks again, really appreciate your help here smile

Yogesh Mude
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.
April 30, 2019

HI @adammarkham 

I have the same requirement as..while sprint closing/completing..the incomplete issues will be moved either future sprint or backlog...so whenever we moved the incomplete issues to backlog then the sprint field should be reset to null...i mean sprint value should be removed.

Normally when we add the incomplete issues to backlog the sprint field contains the sprint name to which the ticket has belonged.

Could you please help me on this...i tried to update the sprint field but getting below error.

 

2019-04-30 20:23:03,055 http-nio-8712-exec-2 ERROR admin 1222x1118x1 1lhqugt 0:0:0:0:0:0:0:1 /rest/greenhopper/1.0/sprint/106/complete [c.o.scriptrunner.runner.AbstractScriptListener] Script function failed on event: com.atlassian.greenhopper.api.events.sprint.SprintClosedEvent, file: <inline script>
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '[10201, 10601]' with class 'java.util.ArrayList' to class 'com.atlassian.jira.issue.MutableIssue' due to: groovy.lang.GroovyRuntimeException: Could not find matching constructor for: com.atlassian.jira.issue.MutableIssue(java.lang.Long, java.lang.Long)
at Script9.run(Script9.groovy:62)
0 votes
Eduard Diez
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.
October 25, 2018

Hi, Somebody know how can I do in Cloud?

Serach in event the number Sprint or boardId

thank's

0 votes
Mads Stavang September 23, 2016

Also, there is an entry in the Atlasssian Issue DB regarding Sprint events:

https://jira.atlassian.com/browse/JSW-6395

 

0 votes
Mads Stavang September 23, 2016

Thanks for you time! We are using JIRA Software 7.1.1 (JIRA Agile does not exist anymore) and JIRA Core 7.1.0. 

0 votes
adammarkham
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.
September 22, 2016

It took me a while to investigate the best approach for this.

What version of JIRA Agile are you using? This may be possible using a script that registers an event listener rather than using a webhook but it depends if you have JIRA Agile 7.1+ or not as the SprintClosedEvent is only available in that version onwards.

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events