Much like yesterday’s post about injections, it took me a little bit to figure out what was going on with pipelines and the left shift << operator in Groovy.

In the end, it was simplest for me to say “X << Y basically means feed the data in Y into the function of X”.  Pipelines are the closure functions that X would represent.

 

Let’s look at a Jira-related example:

 import com.atlassian.jira.component.ComponentAccessor

// Get the Jira issue manager
def issueManager = ComponentAccessor.getIssueManager()

// Get all the issues in a target project
def issues = issueManager.getIssueObjects(issueManager.getIssueIdsForProject(10100))

log.warn("This is the full list of issues: " + issues)
//Assume one of those issues, ZT-4, has a priority of "lowest"

//Define a new pipeline into which we'll feed the list of issue objects
def pipeline = {
 it.findAll { !(it.priority.name == "Lowest") }
}

// Apply the pipeline to the list of issues
def filteredAndSortedIssues = pipeline << issues

return filteredAndSortedIssues
 

 

So what are we doing?  First we’re grabbing a list of issues from a target project.  Then we’re defining a pipeline, which is simply a closure that performs an operation on an as-yet undefined set of data.

Finally, we’re invoking the pipeline

When I sat down to write about injections, I thought it’d be a quick little blog post.  However, it took me a lot longer than I expected to get my head around even the basic concept of what the injection was actually doing.
I get the general idea now, but I don’t see myself putting them into action in my code very often.

This is the best example I could find of an injection in action.

Here’s a quick exploration of some injection code, as I understood it:

 (1..5).inject(1) { runningTotal, itemInRange ->

 log.warn("$runningTotal * $itemInRange = ${runningTotal * itemInRange}")

log.warn("The running total is $runningTotal and the current item from the range is $itemInRange")

 runningTotal * itemInRange 

} 

 

The results looked like this:

 2023-02-22T03:55:55,063 WARN [runner.ScriptBindingsManager]: 1 * 1 = 1
2023-02-22T03:55:55,063 WARN [runner.ScriptBindingsManager]: The running total is 1 and the current item from the range is 1
2023-02-22T03:55:55,063 WARN [runner.ScriptBindingsManager]: 1 * 2 = 2
2023-02-22T03:55:55,063 WARN [runner.ScriptBindingsManager]: The running total is 1 and the current item from the range is 2
2023-02-22T03:55:55,063 WARN [runner.ScriptBindingsManager]: 2 * 3 = 6
2023-02-22T03:55:55,063 WARN [runner.ScriptBindingsManager]: The running total is 2 and the current item from the range is 3
2023-02-22T03:55:55,063 

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

Introduction

So you or your organization have decided to purchase, or at least trial, ScriptRunner. Now what?

If you’re going to learn how to use ScriptRunner, I would very strongly advise that you set it up in a test environment.  This allows you to learn to use the tool, and to not have to worry about making mistakes or deleting production data.

In order to use ScriptRunner, you’ll need to be an admin on the Jira system in question.   As well, I’ll do my best to explain the Groovy code that we’re using, but it would be of great benefit for you to read some primary learning materials on the subject.   Some of what I’ll say will assume a basic knowledge of object-oriented programming principles.   Please note that Groovy is case-sensitive, but is agnostic to whitespace.

All of the testing and learning we’re going to be doing is done in the ScriptRunner Script Console.   You can think of this as sort of a window into your Jira system, in which Groovy code can be run.  The Script Console is accessed by going to your Jira System Setings > Manage Apps > ScriptRunner > Script Console.  ScriptRunner has many