The settings or preferences for a given user in Jira Cloud are stored in a number of locations within the system.   The User Properties section contains settings relating to which interface elements the user sees or doesn’t see.  

For example, when you first access ScriptRunner on a Jira instance, you’re presented with a little quiz.  It looks like this:

After you click through this quiz it goes away forever. Someone recently remarked that they’d love to not have their ScriptRunner users be presented with this quiz in the first place.

…Okay, we can make that happen!

 

First we need to query the user properties for a given user with this code:

 def userProps = get("rest/api/2/user/properties") 
.header("Accept", "application/json")
.queryString("accountId", "<user account ID>")
.asJson(); 

return userProps.body 

The results look like something like this:

 {
  "array": {
    "empty": false
  },
  "object": {
    "keys": [
      {
        "self": "  https://some-jira-instance.atlassian.net/rest/api/2/user/properties/navigation_next_ui_state?accountId=12345678910abcdef123456",
        "key": "navigation_next_ui_state"
      },
      {
        "self": "  https://some-jira-instance.atlassian.net/rest/api/2/user/properties/onboarding?accountId=12345678910abcdef123456",
        "key": "onboarding"
      },
      {
        "self": "  https://some-jira-instance.atlassian.net/rest/api/2/user/properties/project.config.dw.create.last.template?accountId=12345678910abcdef123456",
        "key": "project.config.dw.create.last.template"
      },
      {
        "self": "  https://some-jira-instance.atlassian.net/rest/api/2/user/properties/sr-post-install-quiz?accountId=12345678910abcdef123456",
        "key": "sr-post-install-quiz"
      }
    ]
  }
} 

 

The key we’re interested in is the one at the bottom, called sr-post-install-quiz.  A new user isn’t going to have this property; it only gets

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