How can I access the list of GH Sprints programmatically (from workflow post-functions)

I'm trying to access the list of GreenHopper Sprints, with associated metadata (name and dates), from workflow post-functions. Can it be done?

What I'm trying to do is simple: automatically set the GH Sprint field when resolving an issue that was not planned (i.e. included in a sprint).

Thanks in advance,

David.

6 answers

1 accepted

This widget could not be displayed.

All right, I've implemented the solution. I thought I'd share it with the rest of the community:

/**
 *
 */

import org.apache.commons.httpclient.HttpClient
import org.apache.commons.httpclient.methods.GetMethod
import org.apache.commons.httpclient.HttpException
import org.apache.commons.httpclient.HttpStatus
import groovy.json.JsonSlurper
import com.atlassian.jira.ComponentManager
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.util.ImportUtils
import com.atlassian.jira.issue.util.IssueChangeHolder
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.index.IssueIndexManager
import com.atlassian.jira.issue.customfields.view.CustomFieldParams
import com.atlassian.jira.issue.customfields.view.CustomFieldParamsImpl
import com.atlassian.jira.issue.customfields.CustomFieldType
import org.apache.log4j.Category
import com.atlassian.jira.issue.history.ChangeItemBean


//create a class to wrap our post-function, so that it can be debugged in a Groovy debugger
class AddCurrentSprintPostFunctionClass {
  private static Category log = Category.getInstance("com.polyspot.AddCurrentSprintPostFunction")

  static void execute(Map transientVars, Issue issue, IssueChangeHolder changeHolder) {
    ComponentManager componentManager = ComponentManager.getInstance()
    CustomFieldManager customFieldManager = componentManager.getCustomFieldManager()
    IssueIndexManager indexManager = ComponentManager.getInstance().getIndexManager()

    //get the Sprints the isssue is already in
    CustomField cfSource = customFieldManager.getCustomFieldObject("customfield_11200")  //Sprint field id - may need to be adjusted for each Jira instance
    CustomFieldType cfType = cfSource.getCustomFieldType();
    Object sprintsOfIssue = issue.getCustomFieldValue(cfSource)

    // Create an instance of HttpClient.
    HttpClient client = new HttpClient();

    // Create a method instance.
    //We must go through the public URL (not localhost) otherwise, although it works, it generates Crowd WARNings in the logs
    GetMethod method = new GetMethod("https://<URL>/rest/greenhopper/1.0/sprints/14?os_username=<username>&os_password=<password>");

    try {
      // Execute the method.
      int statusCode = client.executeMethod(method);

      if (statusCode != HttpStatus.SC_OK) {
        log.error("REST call failed: " + method.getStatusLine());
        return;
      }

      //parse the result into a "struct"
      JsonSlurper slurp = new JsonSlurper();
      Reader reader = new InputStreamReader(method.getResponseBodyAsStream());
      def result = slurp.parse(reader);

      //look for the (only) active sprint
      for (Object sprint : result.sprints)
        if (!sprint.closed) {
          //we've found the current sprint, add it to the issue
          def sprintSet = [] as Set
          if (sprintsOfIssue != null)
            sprintSet = sprintsOfIssue.collect {it.id}
          sprintSet.add(sprint.id as String)
          def value = sprintSet.join(",")

          //now set the issue field value
          boolean wasIndexing = ImportUtils.isIndexIssues();
          ImportUtils.setIndexIssues(true);

          try {
            //convert from string to Object
            CustomFieldParams fieldParams = new CustomFieldParamsImpl(cfSource, value);
            value = cfType.getValueFromCustomFieldParams(fieldParams);
            cfSource.updateValue(null, issue, new ModifiedValue(issue.getCustomFieldValue(cfSource), value), changeHolder)
          }
          finally {
            indexManager.reIndex(issue);
            ImportUtils.setIndexIssues(wasIndexing);
          }
        }

    } catch (HttpException e) {
      log.error("Fatal protocol violation: " + e.getMessage());
    } catch (IOException e) {
      log.error("Fatal transport error: " + e.getMessage());
    } catch (Exception e) {
      log.error("Error during post-function execution: " + e.getMessage());
    } finally {
      // Release the connection.
      method.releaseConnection();
    }
  }

  //wrapper call to manage history logging
  static void executeWithHistory(Map transientVars, Issue issue) {
    IssueChangeHolder holder = createChangeHolder(transientVars);

    try {
      execute(transientVars, issue, holder);
    } finally {
      releaseChangeHolder(holder, transientVars);
    }

  }

  /**
   * Create new holder with changes from transient vars
   */
  private static IssueChangeHolder createChangeHolder(Map<String, Object> transientVars) {
    List<ChangeItemBean> changeItems = (List<ChangeItemBean>) transientVars.get(CHANGE_ITEMS);

    if (changeItems == null) {
      changeItems = new LinkedList<ChangeItemBean>();
    }

    if (log.isDebugEnabled()) {
      log.debug("Create new holder with items - " + changeItems.toString());
    }

    IssueChangeHolder holder = new DefaultIssueChangeHolder();

    holder.setChangeItems(changeItems);

    return holder;
  }

  /**
   * Release change holder
   */
  private static void releaseChangeHolder(IssueChangeHolder holder, Map<String, Object> transientVars) {
    List<ChangeItemBean> items = holder.getChangeItems();

    if (log.isDebugEnabled()) {
      log.debug("Release holder with items - " + items.toString());
    }

    transientVars.put(CHANGE_ITEMS, items);
  }

  private static final String CHANGE_ITEMS = "changeItems";
};


//call our static method
AddCurrentSprintPostFunctionClass.executeWithHistory(transientVars,issue)

This widget could not be displayed.

You mean rapid board of the regular Greenhopper? If rapidboard this is a custom field.

if it is a regular one it is the fixVersion field.

This widget could not be displayed.

I'm talking about the RapidBoard Sprints. But what I'm trying to do is get the list of active Sprints in order to set the Sprint custom field automatically.

This widget could not be displayed.

Rapidboard allows only one active sprint at a time and the details can be fetched used REST. See a sample url below

http://localhost:8080/rest/greenhopper/1.0/sprints/4

The '4' in the url is the Rapid View Id.

This returns a list of sprints and the last object in the list will indicate the active sprint ("closed":false)

This widget could not be displayed.

Just to clarify, the Rapid Board allows many Sprints to be active at once, but will not allow Plan mode to be used to start another when one is already active on Work mode.

That said, Renjith is totally right, this REST end point will list all of the Sprints that are active on the work mode of the specified Rapid Board.

This widget could not be displayed.

Thanks Renjith for the idea.

But how do I consume Jira's REST services from within a Jira plugin - more precisely a workflow function? More specifically, how do I find out about Jira's REST endpoint, and more importantly, how do I handle authentication since I'm already within Jira server-side code?

Suggest an answer

Log in or Sign up to answer
Community showcase
Published Apr 22, 2018 in Jira Software

How-to setup a secured Jira Software 7.9.0 on Ubuntu 16.04.4 in less than 30 minutes

...PermissionsStartOnly=true User=www-data Group=www-data ExecStart=/opt/jira/bin/startup.sh ExecStop=/opt/jira/bin/shutdown.sh TimeoutStartSec=120 TimeoutStopSec=600 PrivateTmp=true [Install] WantedBy...

1,500 views 10 12
Read article

Atlassian User Groups

Connect with like-minded Atlassian users at free events near you!

Find a group

Connect with like-minded Atlassian users at free events near you!

Find my local user group

Unfortunately there are no AUG chapters near you at the moment.

Start an AUG

You're one step closer to meeting fellow Atlassian users at your local meet up. Learn more about AUGs

Groups near you