Here’s a very basic example of a script to review group membership on Jira Server/DC

By first fetching the groups, and then the users in each group, we take the most efficient path toward only fetching the users who are in a group.

On the other hand, we could also tweak this script to show us users who are NOT in a group, or who are in  X or fewer groups.   That might be interesting, too.

 

 import com.atlassian.jira.component.ComponentAccessor

def groupManager = ComponentAccessor.getGroupManager()
def groups = groupManager.getAllGroups()
def sb = []
//Define a string buffer to hold the results

sb.add("<br>Group Name, Active User Count, Inactive User Count, Total User Count")
//Add a header to the buffer
groups.each{ group ->

 def activeUsers = 0
 def inactiveUsers = 0
 Each time we iterate over a new group, the count of active/inactive users gets set back to zero
 def groupMembers = groupManager.getUsersInGroup(group)
 //For each group, fetch the members of the group
    
    groupMembers.each{ member ->
    //Process each member of each group
        
    def memberDetails = ComponentAccessor.getUserManager().getUserByName(member.name)
    //We have to fetch the full user object, using the *name* attribute of the group member
        
        if(memberDetails.isActive()){
            activeUsers += 1 
        }else{
            inactiveUsers += 1
        }
    }//Increment the count of 

There’s a simple way to return a list of field configurations and field configuration schemes in Jira DC/Jira Server.  However, in order to find that information you have to know that Jira once referred to these as field layouts

Using the FieldLayoutManager class, this script returns a list of field layouts:

 import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.fields.layout.field.FieldLayoutManager

def layoutManager = ComponentAccessor.getFieldLayoutManager()
def fieldLayouts = layoutManager.getEditableFieldLayouts()
def sb = []

fieldLayouts.each{ fieldLayout ->

    sb.add("<br> ${fieldLayout.name}")

}

return sb 

 

This script returns the field layout schemes, with a simple change of the method:

 import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.fields.layout.field.FieldLayoutManager

def layoutManager = ComponentAccessor.getFieldLayoutManager()
def layoutSchemes = layoutManager.getFieldLayoutSchemes()
def sb = []

layoutSchemes.each{ layoutScheme ->

    sb.add("<br> ${layoutScheme.name}")

}

return sb 

This simple script fetches all projects, then fetches each issue in the project.  For each issue, it counts the number of attachments and adds it to a running tally for that project.

 import com.atlassian.jira.component.ComponentAccessor

def projectManager = ComponentAccessor.getProjectManager()
def projects = projectManager.getProjectObjects()
def issueManager = ComponentAccessor.getIssueManager()

projects.each{ project ->

  def attachmentsTotal = 0
  def issues = ComponentAccessor.getIssueManager().getIssueIdsForProject(project.id)

  issues.each{ issueID ->

    def issue = ComponentAccessor.getIssueManager().getIssueObject(issueID)
    def attachmentManager = ComponentAccessor.getAttachmentManager().getAttachments(issue).size()
    attachmentsTotal += attachmentManager

  }
  log.warn(project.key + " - " + attachmentsTotal)
} 

Once upon a time, things were simple.  If you wanted to retrieve a page using the Confluence Java API, you simply called getPage().    Fetching Spaces was similarly easy, and intuitive.

Those days are over.  The methods are deprecated. Instead, we now need to use SpaceService and ContentService to manage spaces and content, respectively.  Let’s take a look at some examples of how a task would have been accomplished with the PageManager and SpaceManager, and compare that to how those tasks would be accomplished today.

This the code required to return all page content for all spaces, using PageManager and Spacemanager:
 import com.atlassian.confluence.pages.PageManager
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.confluence.spaces.SpaceManager

def spaceManager = ComponentLocator.getComponent(SpaceManager)
def pageManager = ComponentLocator.getComponent(PageManager)

def spaces = spaceManager.getAllSpaces()

spaces.each { space ->
    def pagesInSpace = pageManager.getPages(space, true)
    
    pagesInSpace.each { page ->
        log.warn(page.getBodyAsString())
    }
}
 

 

Here’s the same code, using the SpaceService and ContentService classes:

 import com.atlassian.confluence.api.model.Expansions
import com.atlassian.confluence.api.model.content.ContentRepresentation
import com.atlassian.confluence.api.model.content.ContentBody
import com.atlassian.confluence.api.model.content.Content
import com.atlassian.confluence.api.model.content.Space
import com.atlassian.confluence.api.model.Expansion
import com.atlassian.confluence.api.model.pagination.PageResponse
import com.atlassian.confluence.api.service.content.SpaceService
import com.atlassian.confluence.api.service.content.ContentService
import com.atlassian.confluence.api.model.content.ContentType
import com.onresolve.scriptrunner.runner.ScriptRunnerImpl
import com.atlassian.confluence.api.model.pagination.SimplePageRequest


def contentService = ScriptRunnerImpl.getPluginComponent(ContentService)
def spaceService = ScriptRunnerImpl.getPluginComponent(SpaceService)
 
SimplePageRequest pageRequest = new SimplePageRequest(0, 10)
 
PageResponse < Space > spaceResults = spaceService.find(new Expansion('name')).fetchMany(new SimplePageRequest(0, 10))
 
List < Space > 

 

This script fetches all of the projects in a Jira Cloud instance. It then fetches all of the project roles for that project, and finally fetches all of the users in that role for that project. In this way, it iterates through the projects and returns information about the users in the project roles.

 

 import groovy.json.JsonSlurper

def sb = []
//Define a string buffer to hold the results

def getUsers = get("/rest/api/2/project")
  .header('Content-Type', 'application/json')
  .asJson()
//Get the list of projects in the instance

def content = getUsers.properties.rawBody
//Get the raw body contents of the HTTP response

def scanner = new java.util.Scanner(content).useDelimiter("\\A")
String rawBody = scanner.hasNext() ? scanner.next() : ""
def json = new JsonSlurper().parseText(rawBody)
//Turn the raw body contents into JSON

json.each{ project ->
//Iterate through the projects
  
  sb.add("$project.name")

  def getRoles = get("/rest/api/2/project/$project.id/role")
    .header('Content-Type', 'application/json')
    .asObject(Map)
//For each project, get the list of roles


  getRoles.body.each{ projectRole ->
  //Iterate through the project roles

      def getRoleMembers = get("$projectRole.value")
      .header('Content-Type', 'application/json')
      .asObject(Map)
      //Return the details about each role

    getRoleMembers.body.actors.each{ roleMember ->
    //Get all the actors (users) in that role

        sb.add("$getRoleMembers.body.name:   $roleMember.displayName")
    }
  }
}

return sb
//Return the results


 

Mitigating CORS Errors With Custom Jira REST API Endpoints

If you dive into the world of REST requests and APIs, you may encounter a CORS error that prevents your request from completing. CORS stands for Cross-Origin Resource Sharing.  Same-origin is a security feature in browsers that prevents requests coming from one place (origin) to access resources in a different domain.  CORS allows web pages to access resources on a different network by providing a standard for safely allowing cross-origin requests.

Let’s talk about the example that I encountered.  I wrote a JavaScript macro for Confluence Server, and I was trying to access a third-party API using that macro.  However, Confluence macros run in the browser when the page loads, rather than running on the back-end Confluence server itself.   Thus, while the Confluence server may be set up to address CORS, your browser almost certainly is not, and the request gets blocked.

We can address this by creating a custom REST API endpoint in Confluence (or Jira).   In this way, we have the server making the request to the third party API, and the macro makes the request to the internal API.

In other words, the custom REST API endpoint acts

Get All Filters in a Jira System

Here’s the truth: getting all of the filters in a Jira DC instance with ScriptRunner is awkward and fussy.  There’s no method that simply returns all of the filters. 

Instead, we need to first return all of the users in the system.  And then we need to examine all of the filters that each of them owns, as each filter must have an owner.  Here’s an example of some code that does that:

 import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.bc.filter.SearchRequestService
import com.atlassian.jira.issue.search.SearchRequest
import com.atlassian.jira.bc.user.search.UserSearchParams
import com.atlassian.jira.bc.user.search.UserSearchService
   
   
SearchRequestService searchRequestService = ComponentAccessor.getComponent(SearchRequestService.class)
UserSearchService userSearchService = ComponentAccessor.getComponent(UserSearchService)
def sb = new StringBuffer()
   
UserSearchParams userSearchParams = new UserSearchParams.Builder()
    .allowEmptyQuery(true)
    .includeInactive(false)
    .ignorePermissionCheck(true)
    .build()
//Define the parameters of the query
  
//Iterate over each user's filters
userSearchService.findUsers("", userSearchParams).each{ApplicationUser filter_owner ->
    try {
        searchRequestService.getOwnedFilters(filter_owner).each{SearchRequest filter->
            String jql = filter.getQuery().toString()
            //for each fiilter, get JQL and check if it contains our string
             
                sb.append("Found: ${filter.name}, ${jql}\n" + "<br>")
             
        }
    } catch (Exception e) {
            //if filter is private
           sb.append("Unable to get filters for ${filter_owner.displayName} due to ${e}")
    }
}
 
return sb 

 

Getting a list of filters on Jira Cloud is much simpler, as there’s a REST API that accomplishes this.  If we

Confluence Meta-Macros and the joy of being organized

Let’s talk about meta-macros.  That is, macros that examine other macros.   I just made up the term, so don’t be concerned if you can’t find other examples on the internet.

If you wanted some insight into which pages in your Confluence Instance were using a specific macro, how would you find that information?

You could certainly check each page manually, but that sounds dreadful.

One option to get Macro information is this ScriptRunner script that I wrote, which examines the latest version of each page in each Space for references to the specified macro:

 import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.pages.Page
def pageManager = ComponentLocator.getComponent(PageManager)
def spaceManager = ComponentLocator.getComponent(SpaceManager)
def spaces = spaceManager.getAllSpaces()
  
def macroRef = 'ac:name="info"'
  
spaces.each {
  spaceObj ->
    def pages = pageManager.getPages(spaceObj, false)
  pages.each {
    page ->
      if (page.getBodyContent().properties.toString().contains(macroRef) && page.version == page.latestVersion.version) {
        log.warn("'${page.title}' (version ${page.version}) is the latest version of the page, and contains the target macro")
      }
  }
}
 

 

But what if you wanted MORE information?  What if you wanted to know every macro running on every page in the system, and you didn’t have ScriptRunner to do it for you?  In that

Overview

I spoke to someone recently on the subject of learning to use ScriptRunner and Groovy.  One of the questions he had was around the number of results that were returned when he called the API.  That is, he had set maxResults to 500, but only 100 results were returned.   Why?
It’s true that you have some agency over the number of results that are returned by the Jira (and Confluence) REST API.  You can set the maxResults value as part of the request, and receive more than the 50 items that are returned by default.   However, there are limits to this parameter.  The API has a built-in limit, and no matter what you set maxResults to, you cannot exceed that limit.

Let’s look at an example.

If I call /rest/api/3/search?jql=project is not EMPTY, it’ll return every issue in the instance from a project that isn’t empty.  That’s potentially a lot of issues. 
However if we look at the results that are returned, there are some meta attributes in addition to the issues themselves.  In our example, the JSON that is returned starts with this:
 {
"expand": "schema,names",
"startAt": 0,
"maxResults": 50,
"total": 35,
 
From this, we see that

This script searches for Jira filters by name, then by filter ID, and then updates the permissions set applied to them.

It’s useful for updating a bulk list of filter names, but really it’s an exercise in working with filters and the search service.

The script iterates through an array of filter names. For each filter name, it uses the searchRequestManager to find that filter object.  It then uses the filter ID of the object to return a filter object.  This seems redundant, but the object returned by the searchRequestManager isn’t actually a filter. It’s information about the filter. 

When we search for the filter by name using searchRequestManager , it will return any filter that has that name. For that reason, we must treat the results like an array, and iterate through them.  For each filter ID that is returned by the name search, we use the searchRequestManager to search for the filter object.

Once we’ve got a filter object, we apply the new permissions to it and commit the change with updateFilter().

 

 

 import com.atlassian.jira.bc.JiraServiceContextImpl
import com.atlassian.jira.bc.filter.SearchRequestService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.sharing.SharePermissionImpl
import com.atlassian.jira.sharing.SharedEntity
import com.atlassian.jira.sharing.type.ShareType
import com.atlassian.jira.issue.search.*

def searchRequestService = ComponentAccessor.getComponent(SearchRequestService)
def currentUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def searchRequestManager