One of the challenges that Jira admins face is monitoring the health of their Jira instance. While there are some built-in tools for doing this, it’s useful to know how to perform routine maintenance using ScriptRunner.
One such routine maintenance task is the monitoring of Jira Project sizes.   There’s no direct method or way of doing this; instead we get all of the issues for a given project, and sum up the size of the attachments on each issue.  In this way, we get an idea of which Jira Projects are becoming unwieldy.

The code required to perform this calculation isn’t complicated. However, if the script runs too long it’ll simply time out. This is especially true if you’re calculating the size of multiple Projects.  Here’s the code to calculate the size of a single project:

 import org.ofbiz.core.entity.GenericValue;
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.project.Project;
import com.atlassian.jira.project.ProjectManager

def totalSize = 0
def attachmentManager = ComponentAccessor.getAttachmentManager()

ProjectManager projectManager = ComponentAccessor.getProjectManager()

def projectName = "<projectName>"

Project proj = projectManager.getProjectByCurrentKey(projectName)

IssueManager issueManager = ComponentAccessor.getIssueManager()

for (GenericValue issueValue: issueManager.getProjectIssues(proj.genericValue)) {
    Issue issue = issueManager.getIssueObject(issueValue.id)
    attachmentManager.getAttachments(issue).each {
      attachment ->
        totalSize += attachment.filesize
    }
  }
  log.warn("Total size of attachments for ${proj.name} is ${totalSize / 1024} 

Here’s a relatively simple one for you. Say you had a Jira Cloud instance with hundreds or thousands of projects, and you wanted to swap them all over to using a new notification scheme.  How would you go about it?

Well you could certainly do it by hand. That’s an option.  Or you could write a little script and us an API endpoint that I’ve only just discovered.

The script below fetches all of the projects in the system that use a notification scheme. We then filter out only the ones we want to adjust, and update each of those projects to use the target notification scheme.

This script is a great example of the kind of thing that ScriptRunner excels at, and it’s a great script to use to start learning the REST API.

 

 import groovy.json.JsonSlurper
 
// Notification scheme ID to search for
def searchSchemeId = "10000"
 
// Notification scheme ID to use for update
def updateSchemeId = 10002
 
def response = get("/rest/api/3/notificationscheme/project")
  .header('Content-Type', 'application/json')
  .asJson()
 
// Parse the JSON response
def jsonSlurper = new JsonSlurper()
def json = jsonSlurper.parseText(response.getBody().toString())
 
// Access the nodes in the JSON
json.values.each {
  project ->
 
    logger.warn(project.toString())
 
//We need to account for -1, 

The Problem

One of the problems we encounter with migrating large Jira instances is that when it comes to JCMA, you either have to add all of the Advanced Roadmaps plans or none of them. There’s no facility for selectively adding Roadmaps plans.

One such migration involved moving a subset or portion of the instance data, rather than the whole thing.   Though the instance had 1400+ Roadmaps plans, we only needed to migrate about 100 of them.

The solution we came up with was:

  1. Take a backup of the current production instance
  2. Delete all of the Advanced Roadmaps plans that didn’t need to be migrated
  3. Perform the migration
  4. Restore the production instance from the backup

Naturally, none of us fancied deleting 1300 Roadmaps plans by hand, especially if we had to do it more than once during the course of testing.  So we scripted it.

The Solution

The actual deleting of an Advanced Roadmaps plan is pretty simple.  To delete a plan, just send a DELETE request to the plan’s API endpoint, /rest/jpo/1.0/plans/<planID> .  So long as you have a list of plan names or IDs that you want to keep, you can tell the script to delete any plans

Threading is a fantastic and (relatively) simple way to run parallel HTTP requests against a Jira or Confluence instance.  Multithreading can drastically cut down the length of time it takes for a piece of code to run, as each HTTP request is not waiting for the previous request to finish.  In Groovy we can achieve threading by using the Thread class or the ExecutorService framework.

However, threading isn’t a viable solution when it comes to Jira (or Confluence) on the Cloud.   Because Jira Cloud is a shared hosting environment, there are a number of reasons why Atlassian has put strict limitations on the use of threading.    Chief among these concerns are performance and security.   If anyone can run any number of threads that they want, this necessarily impacts the other users of the shared hosting environment.

Similarly, multithreading in a shared hosting environment can cause data inconsistencies and synchronization issues, which easily lend themselves to visions of major security issues.

 

Though we cannot use threading on Jira Cloud, we can use async.  Simply put, the difference between the two is that threading uses concurrent or parallel threads of execution to achieve concurrency.  That is, it literally splits the

In an attempt to become a stronger user of Groovy and Jira, I’m challenging myself to learn something new each day for 100 days.  These aren’t always going to be especially long blog posts, but they’ll at least be something that I find interesting or novel.

If we want to work with the elements in a collection, we have a number of options.  My favourite method is to use a closure with .each, which could be as simple as this:

 def eachList = [5, 10, 15]

eachList.each{element ->
log.warn(element.toString())

}
 

The closure allows us to iterate through each element in the collection.

Groovy also has a .collect method.  Implementing it would look something like this:

 def collectList = [1, 2, 3]

def squaredCollectList = collectList.collect { element ->
    element * element
}

return squaredCollectList
 

So what’s the practical difference?

With .each, we’re simply iterating through a collection of elements that already exists.  With .collect, we’re defining a new collection (squaredCollectList). We then iterate through all of the elements of the predefined list (collectList), square the element, and add the result to the newly defined collection.

In simple terms, .each iterates through a list. .collect iterates through a

TrustedRequestFactory is a Jira-supplied way of authenticating against Jira itself.  If I wanted to authenticate against the current instance, or an external instance, this is what I’d consider using.

This script iterates through a collection of Jira URLs, and processes them as TrustedRequestFactory GET requests in parallel. This is useful in cases when a large number of requests need to be submitted as HTTP; instead of waiting for each one to finish in turn, we can run them all at once.

As it stands, this script authenticates using the logged-in user.  However, this could be amended pretty easily by simply adding the correct type of authentication as a header on the request.  As well, if you wanted to authenticate against a different Jira instance, the URL structure would need to be amended slightly.

I’ve commented the code as best I can, but quite frankly I’m still learning about parallel execution myself.   I’m next going to dig into async vs. threading, and see what I can discover.

 

 import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.config.properties.APKeys
import com.atlassian.sal.api.net.Response
import com.atlassian.sal.api.net.ResponseException
import com.atlassian.sal.api.net.ReturningResponseHandler
import com.atlassian.sal.api.net.TrustedRequest
import com.atlassian.sal.api.net.TrustedRequestFactory
import com.atlassian.sal.api.net.Request
import groovy.json.JsonSlurper
import groovyx.net.http.ContentType
import groovyx.net.http.URIBuilder
import java.net.URL;
import java.util.concurrent.Callable
import java.util.concurrent.Executors
 
def currentUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser

Intro

(If you haven’t read the previous post in this series, I highly recommend starting with that.)

In order to make use of the Jira API documentation, we need to understand what classes, packages, and methods are.   That’s because the JIRA API is documented, but only in the most technical sense.  It is predicated on you having some knowledge of Java.    You can go to https://docs.atlassian.com/software/jira/docs/api/latest and get information about all of the internal libraries that Jira uses to function, but it’s not much good to you without that prior knowledge.

At the same time, knowing how to read and make use of the API documentation is a vital skill when it comes to working with ScriptRunner.   All of Jira’s functionality is available for you to use, but only if you can harness it through the power of classes.

What’s the Problem?

The problem I ran into when I was first starting to learn how to use ScriptRunner is that very little is written in a context that a beginner could make use of, and few examples are provided.  A lot of the Jira’s internal functions are interdependent, and someone who is new to both Groovy and Jira

I try to learn something new every day when it comes to Groovy, or at the very least to challenging my understanding of some of the tenets of the syntax that I think I already know.

This morning I was playing around with the assert statement in ScriptRunner, trying to understand the nuances of it.   I was getting frustrated because I wanted the assertion to fail, but then for the script to keep going.  I couldn’t figure out how to capture the error without having the script stop.

I tried a few things.   I know you can include a log statement to the end of an assertion, like so:

 assert (1 == 2) : "Assertion failed"  

 

 java.lang.AssertionError: Assertion failed. Expression: (1 == 2)
	at org.codehaus.groovy.runtime.InvokerHelper.assertFailed(InvokerHelper.java:438)
	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.assertFailed(ScriptBytecodeAdapter.java:670)
	at Script205.run(Script205.groovy:2) 

But even with the inclusion of the log statement, my script would grind to a halt when the assertion failed.    I also tried putting the assertion in a try/catch statement, but that didn’t work at all.

I then realized that I had no idea what the functional difference between an assertion and a try/catch statement actually was.  Moreover, I didn’t know when it was appropriate to use one or the

This is an extension of a previous post that I did, on returning all of the users in a Jira Cloud instance.

The request was for a script that returns all users in a Jira Cloud instance who have been inactive for over 30 days. Unfortunately, only part of this request is doable.  The Jira Cloud REST API does not currently provide any way of returning login or access timestamp information.  The most it will tell you is whether a user account is active or inactive. The only way to get access information is to export the Jira userbase through the Atlassian portal.

So the script below simply returns all Jira users in a Cloud instance who have an inactive account.   If/when the day comes that Atlassian adds login information to the API, I’ll update this post.

 import groovy.json.JsonSlurper

def sb = []
def page = 0

def lastPage = false

while (lastPage == false) {
  //run this loop until we detect the last page of results

  def x = 0
  //Every time the loop runs, set x to 0. We use x to count the number of users returned in this batch of results

  def getUsers = get("/rest/api/2/user/search?query=&maxResults=200&startAt=" + 

A long long time ago, I posted a complaint to this blog about how I had no idea what a Jira SearchContext was, and how it was very frustrating to try to figure it out.

Yesterday I realized that I had never really made any strides toward fixing my lack of knowledge around the search functionality of Jira.  I’m not talking JQL, I’m talking the actual Search Service waayyy down in the guts of Jira.

I set out wanting to update the permissions for a list of filters on Jira Server, based on the name of the server.  The list of filter names came from a migration we were doing from Jira Server to Jira Cloud. JCMA returned a list of the names of forty filters that were publicly accessible.  My task was to go in and update those permissions.  As I was going to have to repeat this process multiple times, it seemed an excellent candidate for a scripted solution.

As it turns out, it is extremely difficult to search for filters by name.   You can search for filters by their ID without issue, but searching by name is essentially not possible.

My next idea was to simply wipe