The Jira Cloud Migration Assistant tool (JCMA) will only migrate some types of custom fields.   The custom fields that it cannot migrate must be recreated on the Cloud side, or otherwise mitigated in some way.

I wrote a small tool that proactively identifies any custom field in a Jira instance that JCMA will not be able to migrate.

It’s important to be proactive, especially when it comes to migrations. Every unexpected error or issue results in lost time, and a delayed migration.

Here’s the code:

 import com.atlassian.jira.component.ComponentAccessor
 
def migrateableFieldTypes = [
"datepicker",
"datetime",
"textfield",
"textarea",
"grouppicker",
"labels",
"multicheckboxes",
"radiobuttons",
"multigrouppicker",
"multiuserpicker",
"float",
"select",
"multiselect",
"cascadingselect",
"userpicker",
"url",
"project",
"version",
"multiversion"
]
 
def sb = []
 
def customFields = ComponentAccessor.getCustomFieldManager().getCustomFieldObjects()
//Get all of the custom fields in the system
 
customFields.each{ customField -> 
//For each custom field
 
if(!migrateableFieldTypes.contains(customField.properties.genericValue.customfieldtypekey.split(":")[1])){
//If the custom field type does not match one of the items in the array of migrateable field types
 
sb.add(customField.getFieldName() + " | " + customField.properties.genericValue.customfieldtypekey.split(":")[1] + "<br>")
//Note that custom field
 
    }
}
 
return sb.toString().replace(",", "") 

When migrating data from Jira Server/DC to Jira Cloud, JCMA does not like tickets that have no assignee, or which have tickets with an assignee that has an inactive user status.

This script checks a list of issues, and replaces any that have a missing or inactive assignee.

The script comments should pretty well explain what’s happening with the script. By default it’s set up to check all of the issues in a given project. However, by commenting out one line and uncommenting another, a specific list of issues across any number of projects can be fed to the script.

 import com.atlassian.jira.component.ComponentAccessor
def issueManager = ComponentAccessor.getIssueManager()
def issueService = ComponentAccessor.getIssueService()
def userManager = ComponentAccessor.getUserManager()
def projectManager = ComponentAccessor.getProjectManager()

final replacementUser = "<username>"
//Define the username that will be used to replace the current assignee

final projName = "<project name>"
//Define the project to be checked

def project = projectManager.getProjectObjByName(projName).id
//Declare a project ID object, using the name of the project

def issues = ComponentAccessor.issueManager.getIssueIdsForProject(project)
//Get all the issues in the project

//final issues = ["<issues>"]
//Uncomment this line and comment out the previous one to feed the script a specific list of issues

issues.each {
  targetIssue ->
    //For each issue 

Overview

Please note: this solution was originally posted by Peter-Dave Sheehan on the Atlassian Forums. I’m just explaining how I use it.

 

Sometimes when I’m trying to solve a problem with Jira, the internal Java libraries just aren’t sufficient. They’re often not documented, or they’re opaque. 

It’s often far easier to turn to the REST API to get work done, but that’s a little more tricky on Jira DC or Server than it is on Cloud.  On Jira Cloud, a REST call could be as simple as:

 def result = get("/rest/api/2/issue/<issue key>")
.header('Content-Type', 'application/json')
.asObject(Map)

result.body.fields.comment.comments.body.each{field->
       return field
    }
}
 

 

However this won’t work on Server/DC.  Instead we need a REST framework upon which to build our script.

The Framework

This piece of code uses the currently logged in user to authenticate against the Jira REST API.  It then makes a GET call to the designated API endpoint URL.

This code can easily be changed to a POST or a PUT simply by uncommenting the payload statement and the setRequestBody statement, then changing the MethodType from GET to POST/PUT
 

The script returns a JSON blob. With point notation, we can then easily access its individual attributes, and

The Problem

One of the challenges that I encountered this week was the need to include Advanced Roadmaps plans in a Jira DC to Cloud migration.  As you may be aware, JCMA gives you the option to either migrate ALL plans, or none of them.  There is no facility for selectively adding plans.  This is a problem because the client instance has 1200 Roadmaps plans, and trying to add that many plans to a migration causes JCMA to crash.

I set out this week to build the foundations of what I’m calling the Roadmaps Insight Tool.  The first version was intended to simply list every Roadmaps plan in an instance, and list each of its data sources (project, board, or filter). 

The resulting dataset is useful in a number of ways. First, it gives transparency to a part of Jira that is otherwise quite opaque.

Second, it indicates which data sources on each plan are invalid; typically this is because the referenced data source no longer exists.  A Jira administrator wanting to do a cleanup of the Roadmaps Plans could easily base that cleanup on this information.

Third, in the case of this particular client it allows us to

You can easily remove all permissions from a Confluence DC Space, or a Confluence Cloud Space.  Confluence Server, though? You’re out of luck.

Imagine you migrated from Confluence Cloud to Confluence Server, and you wanted to remove all permissions on a Space (except  for maybe “View Space”).  That’s a whole lot of manually clicking, unless you script it.  You’re going to need ScriptRunner for this.

The script below takes two inputs: a Space key, and a username.  It needs the username of someone on the Space with Admin access, because Confluence will not let you remove EVERYONE  with admin access from the Space.

Someone gets left behind.

 

Okay so it takes those two pieces of information as variables.  It then makes use of two arrays. The first array is a prescribed selection of the permissions you’d like removed from the Space. Want to let everyone keep the View Space permission type? Take it out of the List!
The second array is generated by the script. It’s a list of every username and group name with some kind of permission on the Space.

We then nest two loops, and iterate through the permission types and usernames.  For each permission type,

Overview

It’s possible to connect to a Jira instance using Python, and it’s possible to connect to AWS Comprehend using Python. Therefor, it is possible to marry the two, and use Python to assess the sentiment of Jira issues. There are two caveats when it comes to using this script:

  1. The script assumes you can authenticate against Jira with Basic Web Authentication. If your organization uses Single Sign On, this script would need to be amended. 
  2. The script assumes you’re working with Jira Server or Datacenter.  If you’re using Jira Cloud the approach would be different, but I’m planning to do a post about that in the near future.

The authentication method below is not mine. I have linked to the Stack Overflow page where I found it, in the script comments.

 

The Script

The script starts with three imports. We need the Jira library, logging, and the AWS library (boto3).  You’ll likely need to a PIP install of Jira and boto3, if you’ve not used them before.

After the imports we’re defining client, which we use to interact with the AWS API.  Remember to change your region to whichever region is appropriate for you, in addition

As part of my grad school course work, I had half a dozen XML files with content that needed to be analyzed for sentiment.   AWS Comprehend is a service that analyzes text in a number of ways, and one of those is sentiment analysis.

My options were to either cut and paste the content of 400 comments from these XML files, or come up with a programmatic solution.  Naturally, I chose the latter.

The XML file is formatted like so:

 

         <posts>
          <post id="123456">
            <parent>0</parent>
            <userid>user id</userid>
            <created>timestamp</created>
            <modified>timestamp</modified>
            <mailed>1</mailed>
            <subject>Post title</subject>
            <message> Message content </message> 

 

What I needed to get at was the message element of each post, as well as the post id.

The script imports BeautifulSoup to work with the XML, and boto3, to work with AWS.    We next define a string buffer, because we need to store the results of the analysis somehow.

Next we define the client, which tells AWS everything it needs to know.  Tell it the service you’re after, the AWS region, and the tokens you’d use to authenticate against AWS.

After that we provide a list of XML files that the script needs to parse, and tell it to

I found myself needing to examine issues that came through our Jira Service Desk workflow, to determine if they had a form attached. If they didn’t have a form attached, i.e. someone created the issue manually instead of through the Service Desk Portal, the workflow would be instructed to handle them in a certain way.

This turned out to be surprisingly difficult.  There’s no easily accessed attribute associated with issues in Jira that indicates whether or not they have a form attached.

In the end, I determined that it was possible to examine issues in this way by applying some JQL to them.

 import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.bc.issue.search.SearchService

def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def issueManager = ComponentAccessor.getIssueManager()
def searchService = ComponentLocator.getComponent(SearchService)
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()

def issue = issueManager.getIssueObject("<issue key>")
//Define an issue against which the script will be run

def queryParser = ComponentAccessor.getComponent(JqlQueryParser)
def query = queryParser.parseQuery('issueFormsVersion > 1 and key = '+ issue.key)
//Define the query parameters

def search = searchService.search(user, query, PagerFilter.getUnlimitedFilter())
//This gives us a list of issues that match the query

if(search.results.size() > 0){
    //Do something here
    //If any results were found, it means that the issue had a form attached

Overview

I found myself with an interesting Jira problem today.  We had a dashboard that wasn’t showing all issues relevant to the JQL query in the filter.   The issues were fully present in the system, but would only appear if we went in after creation and tweaked them in some way.
Essentially we had issues that weren’t being picked up by the filter because the system didn’t see them as “complete”.

Here’s the sanitized JQL:

 project = <project> AND status = Queued AND "<Custom Field>" = "<value>" 

It was picking up some of the issues, remember. And the issues were all created in the same way: they came in through the Service Desk as Service Requests. So the JQL wasn’t the issue.

Trying Different Solutions

I already had a listener running as part of the process, so I tried adding an issueUpdate statement to it:

 issueManager.updateIssue(user, issue, EventDispatchOption.ISSUE_UPDATED, false) 

This did not resolve the issue.   I next tried updating the issue summary to be itself as part of the Workflow Transition process:

 issue.setSummary(issue.summary + " - " + issue.reporter.displayName) 

This also did not resolve the issue.

Resolving the Issue

In the end I solved the issue by introducing a re-index

Introduction

Once upon a time I spent a week writing a script to automate some functionality for Jira.  The script was supposed to assign issues based on certain criteria, and also update a custom field that contained the name of the team that was currently responsible for the ticket.

At the end of the week I had the script working according to the specifications.   We tested it on Friday afternoon, and all was well.
On Monday morning the project manager sent me an email, notifying me that the script now had to work a completely different way.  It also had to be finished by the end of that day, as Tuesday was the day they were using the script as part of a training session.

Updating the Script

The good news was that for the most part, the changes I had to make to the script were reductive. It had to do fewer things.   However, I had initially included the Custom Field update as part of the transition:

    def issueInputParameters = issueService.newIssueInputParameters()
    issueInputParameters.addCustomFieldValue(customFieldID, customFieldValue)
    //The request is for a new workstation, so set Next to Act to Tier 1 - IDIR Services
    issueInputParameters.setSummary(issue.summary + " - " + issue.getReporterUser().displayName)
    //Set