https://marketplace.atlassian.com/apps/6820/scriptrunner-for-jira?hosting=server&tab=overview
-
Copy a Jira Project with Scriptrunner REST
—
The builtin Script "Copy Project" can be used with REST:
curl -u admin:Welcome1 -H "Content-Type: application/json" -H "X-Atlassian-Token: nocheck" -d @copyProject.json http://jira.server.dk/rest/scriptrunner/latest/canned/com.onresolve.scriptrunner.canned.jira.admin.CopyProject
The Json Payload:
-
Copy a Confluence Space with Scriptrunner REST
—
The builtin Script "Copy Space" can be used with REST:
curl -u admin:Welcome1 -H "Content-Type: application/json" -H "X-Atlassian-Token: nocheck" -d @copySpace.json http://confluence.server.dk/rest/scriptrunner/latest/canned/com.onresolve.scriptrunner.canned.confluence.admin.CopySpace
The Json Payload:
-
Search Jira fields for a Key=Value entry
—
I am always missing a Custom Field that can handle Key=Value entried; combined with multiple entries, so I use a multiline Text Field:
Service Level=AppDrift
CustomerNumber=1043
Othername=Myhost
Lets call the field "VC Tags". Issues with a Key=Value can be found with a JQL like:
-
Reindex a controlled number of issues in Jira
—
This can be achived via the ScriptRunner Console:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.search.SearchException
import com.atlassian.jira.issue.search.SearchResults
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.security.JiraAuthenticationContext
import com.atlassian.jira.bc.issue.search.SearchService.ParseResult
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.util.ImportUtils
JiraAuthenticationContext authenticationContext = ComponentAccessor.getJiraAuthenticationContext()
def authContext = ComponentAccessor.getJiraAuthenticationContext()
def issueIndexingService = ComponentAccessor.getComponent(IssueIndexingService)
def issueManager = ComponentAccessor.getIssueManager()
String jql="project=TEST and status not in (Closed,Resolved)"
Number maxIndexCount = 600
SearchService searchService = ComponentAccessor.getComponentOfType(SearchService .class)
ParseResult parseResult = searchService.parseQuery(authenticationContext.getLoggedInUser(), jql)
int totalIssues = 0
if (parseResult.isValid())
{
SearchResults results = searchService.search(authenticationContext.getLoggedInUser(), parseResult.getQuery(), PagerFilter.getUnlimitedFilter())
final List issues = results?.results
//Loop all issues
issues.each { theIssue ->
if (totalIssues < maxIndexCount)
{
boolean wasIndexing = ImportUtils.isIndexIssues()
ImportUtils.setIndexIssues(true)
issueIndexingService.reIndex(issueManager.getIssueObject(theIssue.id))
ImportUtils.setIndexIssues(wasIndexing)
totalIssues= totalIssues +1
log.warn "Indexed: " + theIssue.getKey()
}
}
}
-
ScriptRunner for JIRA plugin
—
This Plugin gives some extremely nice features, like:
- Using prebuilt-in functions/tasks
- Running scripts in Transitions.
-
Add Organisations to the Issue for JIRA Service Desk (4.x)
—
This script adds all Organisations for a User to a Ticket that is being created in Jira service Desk - Created via JIRA, not the Portal. It uses the Reporter.
Notice, a User can belong to several Organisations, all will be added to the Ticket:
Find v3.x of the script here
-
Add Organisations to the Issue for JIRA Service Desk (3.x)
—
This script adds all Organisations for a User to a Ticket that is being created in Jira service Desk - Created via JIRA, not the Portal. It uses the Reporter.
Notice, a User can belong to several Organisations, all will be added to the Ticket:
Find v4.x of the script here
-
Elevation of Rights in Groovy
—
This sample disables a user - by setting the JIRA Context to a user with higher Level:
This is a potential thread, as its possible for anyone who can create and execure groovy scripts to elevate rights, just by knowing an admin username
-
Howto Change a Project (or multiple projects) outgoing email
—
Via Scriptrunner for all projects:
import com.atlassian.core.ofbiz.util.OFBizPropertyUtils
import com.atlassian.jira.component.ComponentAccessor
def newAddress = "newEmail@example.com"
ComponentAccessor.projectManager.projects.each {
def email = OFBizPropertyUtils.getPropertySet(it.getGenericValue())
email.setString("jira.project.email.sender", newAddress)
}
-
JIRA Event Ids
—
Can be examined from a Listener:
package com.netic.eventlistener
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.link.IssueLink;
import com.atlassian.jira.issue.comments.CommentManager
import com.atlassian.jira.util.JiraUtils;
import com.opensymphony.workflow.WorkflowContext;
import com.atlassian.jira.event.issue.AbstractIssueEventListener
import com.atlassian.jira.event.issue.IssueEvent
class EventListener extends AbstractIssueEventListener {
@Override
void workflowEvent(IssueEvent event) {
String IssueType = event.issue.getIssueType().name
String ProjectName = event.issue.getProjectObject().name
String IssueKey = event.issue.key
String EventId = event.getEventTypeId()
System.out.println("Event: " + EventId)
}
}
-
Deactivate a User via REST
—
As the official API does not have this function (currently), we can accomplish it via Adaptavist Scriptrunner.
Add a REST endpoint and use the code:
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.issuetype.IssueType
import com.atlassian.jira.project.ProjectManager
import com.atlassian.jira.issue.CustomFieldManager
import javax.ws.rs.core.MultivaluedMap
import javax.servlet.http.HttpServletRequest
import javax.ws.rs.core.Response
import com.atlassian.crowd.embedded.api.CrowdService
import com.atlassian.crowd.embedded.api.UserWithAttributes
import com.atlassian.crowd.embedded.impl.ImmutableUser
import com.atlassian.jira.bc.user.UserService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.user.ApplicationUsers
import com.atlassian.jira.user.util.UserUtil
@BaseScript CustomEndpointDelegate delegate
deactivateuser(httpMethod: "GET", groups: ["jira-administrators"]) { MultivaluedMap queryParams ->
def issueManager = ComponentAccessor.getIssueManager()
def userManager = ComponentAccessor.getUserManager()
String userName = queryParams.getFirst("username") as String
def builder = new groovy.json.JsonBuilder()
UserUtil userUtil = ComponentAccessor.userUtil
CrowdService crowdService = ComponentAccessor.crowdService
UserService userService = ComponentAccessor.getComponent(UserService)
ApplicationUser updateUser
UserService.UpdateUserValidationResult updateUserValidationResult
// Get user
UserWithAttributes user = crowdService.getUserWithAttributes(userName)
if (user)
{
updateUser = ApplicationUsers.from(ImmutableUser.newUser(user).active(false).toUser())
updateUserValidationResult = userService.validateUpdateUser(updateUser)
if (updateUserValidationResult.isValid())
{
userService.updateUser(updateUserValidationResult)
builder { result "User Deactivated"}
return Response.status(201).entity(builder.toString()).build()
}
else
{
builder { result "Update of ${user.name} failed: ${updateUserValidationResult.getErrorCollection().getErrors().entrySet().join(',')}"}
return Response.status(400).entity(builder.toString()).build()
}
}
else
{
//No such username
builder { result "User did not exist"}
return Response.status(400).entity(builder.toString()).build()
}
}
-
-
Set or Clear Customer Request Type field
—
To Set it:
import com.atlassian.jira.component.ComponentAccessor
def customFieldManager = ComponentAccessor.customFieldManager
def crtField = customFieldManager.getCustomFieldObjects(issue).find{it.name == "Customer Request Type"}
issue.setCustomFieldValue(crtField, "Desired Request Type")
To Clear it:
-
JIRA Workflow Plugins
—
A brief summary of some Workflow enhancements plugins.
The main focusing is primarily on what they contribute on the Post-Function side of a Transition.
Workflow Enhancer for JIRA
-
Logging PageEvents to Statsd (Datadog)
—
A possible way to Log User- and Page-Access to statsd is via the Event system - using Adaptavist's Scriptrunner for Confluence, se https://scriptrunner.adaptavist.com/latest/confluence/ConfluenceEventHandlers.html#_collecting_stats
Read Access Logging in Confluence for good reasons to log via the Event system.
-
Logging PageEvents to Elasticsearch
—
A possible way to Log User- and Page-Access in Confluence is via the Event system - using Adaptavist's Scriptrunner for Confluence.
This ways has Pros and Cons - read Access Logging in Confluence. On Pro is that the POST to Splunk in in the backend; so we dont need to open for the receiving system in the Firewall
-
PUT'ing with Groovy
—
This is how to make PUTs and POSTs:
def requestMethod = "PUT";
def URLParam = []
def baseURL = "http://myserver.domain.dk/"
def url = new java.net.URL(baseURL);
URLConnection connection = url.openConnection();
connection.setRequestMethod(requestMethod);
connection.doOutput = true
connection.setUseCaches(false);
connection.setRequestProperty("Content-Type", "text/html");
connection.setRequestProperty('Authorization', 'Basic ' + 'username:password'.bytes.encodeBase64().toString());
//PUT Content
OutputStreamWriter out = new OutputStreamWriter(
connection.getOutputStream());
out.write("type=0");
out.write("&format=0");
out.write("&content_id=" + ListId);
out.write("&jira_key=" + IssueKey);
//out.write("options={\"status\":\"" + issueStatus + "\"}");
out.close();
//try
//{
log.info "Script=MakeReport.groovy IssueKey=" + IssueKey + " ScriptRunIdent=" + scriptRunIdent + " URL=" + baseURL
log.info "Script=MakeReport.groovy IssueKey=" + IssueKey + " ScriptRunIdent=" + scriptRunIdent + " Action=GetHttpsConnection"
connection.connect();
try
{
connection.getContent()
}
catch (Exception ex)
{
log.info "Script=MakeReport.groovy IssueKey=" + IssueKey + " ScriptRunIdent=" + scriptRunIdent + " Error=ConnectionError Message="+ ex.getMessage()
}
String Status=connection.getResponseCode()
String Message=connection.getResponseMessage()
-
Logging PageEvents to Splunk
—
A possible way to Log User- and Page-Access in Confluence is via the Event system - using Adaptavist's Scriptrunner for Confluence.
This ways has Pros and Cons - read Access Logging in Confluence. One Pro is that the POST to Splunk in in the backend; so we dont need to open for the receiving system in the Firewall for the world.
-
Scripted field: Last updated in short
—
The goal is to replace the long "Days since Last Comment" with the short "D:HH:MM:
This can be done via a Scripted Field from JIRA Scriprunner:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.IssueManager
import java.text.SimpleDateFormat
// https://docs.atlassian.com/jira/server/com/atlassian/jira/issue/Issue.html
// https://www.tutorialspoint.com/groovy/groovy_operators.htm
long updatedDateTime;
// If No Comments, use:
updatedDateTime = issue.updated.getTime()
// If has Comments, use date of Last Comment:
def comments = ComponentAccessor.commentManager.getComments(issue)
if (comments) {
updatedDateTime = comments.last().getUpdated().getTime()
}
// Find Now
long Now = System.currentTimeMillis()
if (updatedDateTime)
{
// 1s = 1000 ms
// Diff is positive, its now-lastupdated in seconds
int Diff= (Now-updatedDateTime)/1000
// 1d = 86400s
// Get Remains in seconds
int Remains = Diff%86400
int Days = (Diff-Remains)/86400
String sDays = String.valueOf(Days)
// 1h = 3600s
int Remains2 = Remains%3600
int Hours = (Remains-Remains2)/3600
String sHours = "0" + String.valueOf(Hours)
int Mins =Remains2/60
String sMins = "0" + String.valueOf(Mins)
return sDays + ":" + sHours.reverse().take(2).reverse() + ":" + sMins.reverse().take(2).reverse()
}
else
{
return "0:00:00"
}
-
Run groovy script from linux command line
—
curl -u admin:admin --header "X-Atlassian-token: nocheck" -X POST --data "scriptFile=$FilePathRelativeToSourcepath$" http://localhost:8080/jira/rest/scriptrunner/latest/user/exec/
-
Script Runner Listener
—
To Create a Listener, create a directory structure to match below <jira-home>/data/scripts, like:
cd /opt/jira-data/data/script
mkdir com com/firm com/firm/listener
go to the directory and make a class file:
The builtin Script "Copy Project" can be used with REST:
curl -u admin:Welcome1 -H "Content-Type: application/json" -H "X-Atlassian-Token: nocheck" -d @copyProject.json http://jira.server.dk/rest/scriptrunner/latest/canned/com.onresolve.scriptrunner.canned.jira.admin.CopyProject
The Json Payload:
The builtin Script "Copy Space" can be used with REST:
curl -u admin:Welcome1 -H "Content-Type: application/json" -H "X-Atlassian-Token: nocheck" -d @copySpace.json http://confluence.server.dk/rest/scriptrunner/latest/canned/com.onresolve.scriptrunner.canned.confluence.admin.CopySpace
The Json Payload:
I am always missing a Custom Field that can handle Key=Value entried; combined with multiple entries, so I use a multiline Text Field:
Service Level=AppDrift CustomerNumber=1043 Othername=Myhost
Lets call the field "VC Tags". Issues with a Key=Value can be found with a JQL like:
This can be achived via the ScriptRunner Console:
import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.issue.search.SearchException import com.atlassian.jira.issue.search.SearchResults import com.atlassian.jira.issue.search.SearchProvider import com.atlassian.jira.web.bean.PagerFilter import com.atlassian.jira.security.JiraAuthenticationContext import com.atlassian.jira.bc.issue.search.SearchService.ParseResult import com.atlassian.jira.bc.issue.search.SearchService import com.atlassian.jira.issue.index.IssueIndexingService import com.atlassian.jira.util.ImportUtils JiraAuthenticationContext authenticationContext = ComponentAccessor.getJiraAuthenticationContext() def authContext = ComponentAccessor.getJiraAuthenticationContext() def issueIndexingService = ComponentAccessor.getComponent(IssueIndexingService) def issueManager = ComponentAccessor.getIssueManager() String jql="project=TEST and status not in (Closed,Resolved)" Number maxIndexCount = 600 SearchService searchService = ComponentAccessor.getComponentOfType(SearchService .class) ParseResult parseResult = searchService.parseQuery(authenticationContext.getLoggedInUser(), jql) int totalIssues = 0 if (parseResult.isValid()) { SearchResults results = searchService.search(authenticationContext.getLoggedInUser(), parseResult.getQuery(), PagerFilter.getUnlimitedFilter()) final List issues = results?.results //Loop all issues issues.each { theIssue -> if (totalIssues < maxIndexCount) { boolean wasIndexing = ImportUtils.isIndexIssues() ImportUtils.setIndexIssues(true) issueIndexingService.reIndex(issueManager.getIssueObject(theIssue.id)) ImportUtils.setIndexIssues(wasIndexing) totalIssues= totalIssues +1 log.warn "Indexed: " + theIssue.getKey() } } }
This Plugin gives some extremely nice features, like:
- Using prebuilt-in functions/tasks
- Running scripts in Transitions.
This script adds all Organisations for a User to a Ticket that is being created in Jira service Desk - Created via JIRA, not the Portal. It uses the Reporter.
Notice, a User can belong to several Organisations, all will be added to the Ticket:
Find v3.x of the script here
This script adds all Organisations for a User to a Ticket that is being created in Jira service Desk - Created via JIRA, not the Portal. It uses the Reporter.
Notice, a User can belong to several Organisations, all will be added to the Ticket:
Find v4.x of the script here
This sample disables a user - by setting the JIRA Context to a user with higher Level:
This is a potential thread, as its possible for anyone who can create and execure groovy scripts to elevate rights, just by knowing an admin username
Via Scriptrunner for all projects:
import com.atlassian.core.ofbiz.util.OFBizPropertyUtils import com.atlassian.jira.component.ComponentAccessor def newAddress = "newEmail@example.com" ComponentAccessor.projectManager.projects.each { def email = OFBizPropertyUtils.getPropertySet(it.getGenericValue()) email.setString("jira.project.email.sender", newAddress) }
Can be examined from a Listener:
package com.netic.eventlistener import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.issue.link.IssueLink; import com.atlassian.jira.issue.comments.CommentManager import com.atlassian.jira.util.JiraUtils; import com.opensymphony.workflow.WorkflowContext; import com.atlassian.jira.event.issue.AbstractIssueEventListener import com.atlassian.jira.event.issue.IssueEvent class EventListener extends AbstractIssueEventListener { @Override void workflowEvent(IssueEvent event) { String IssueType = event.issue.getIssueType().name String ProjectName = event.issue.getProjectObject().name String IssueKey = event.issue.key String EventId = event.getEventTypeId() System.out.println("Event: " + EventId) } }
As the official API does not have this function (currently), we can accomplish it via Adaptavist Scriptrunner.
Add a REST endpoint and use the code:
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate import groovy.transform.BaseScript import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.issue.Issue import com.atlassian.jira.issue.IssueManager import com.atlassian.jira.issue.issuetype.IssueType import com.atlassian.jira.project.ProjectManager import com.atlassian.jira.issue.CustomFieldManager import javax.ws.rs.core.MultivaluedMap import javax.servlet.http.HttpServletRequest import javax.ws.rs.core.Response import com.atlassian.crowd.embedded.api.CrowdService import com.atlassian.crowd.embedded.api.UserWithAttributes import com.atlassian.crowd.embedded.impl.ImmutableUser import com.atlassian.jira.bc.user.UserService import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.user.ApplicationUser import com.atlassian.jira.user.ApplicationUsers import com.atlassian.jira.user.util.UserUtil @BaseScript CustomEndpointDelegate delegate deactivateuser(httpMethod: "GET", groups: ["jira-administrators"]) { MultivaluedMap queryParams -> def issueManager = ComponentAccessor.getIssueManager() def userManager = ComponentAccessor.getUserManager() String userName = queryParams.getFirst("username") as String def builder = new groovy.json.JsonBuilder() UserUtil userUtil = ComponentAccessor.userUtil CrowdService crowdService = ComponentAccessor.crowdService UserService userService = ComponentAccessor.getComponent(UserService) ApplicationUser updateUser UserService.UpdateUserValidationResult updateUserValidationResult // Get user UserWithAttributes user = crowdService.getUserWithAttributes(userName) if (user) { updateUser = ApplicationUsers.from(ImmutableUser.newUser(user).active(false).toUser()) updateUserValidationResult = userService.validateUpdateUser(updateUser) if (updateUserValidationResult.isValid()) { userService.updateUser(updateUserValidationResult) builder { result "User Deactivated"} return Response.status(201).entity(builder.toString()).build() } else { builder { result "Update of ${user.name} failed: ${updateUserValidationResult.getErrorCollection().getErrors().entrySet().join(',')}"} return Response.status(400).entity(builder.toString()).build() } } else { //No such username builder { result "User did not exist"} return Response.status(400).entity(builder.toString()).build() } }
To Set it:
import com.atlassian.jira.component.ComponentAccessor def customFieldManager = ComponentAccessor.customFieldManager def crtField = customFieldManager.getCustomFieldObjects(issue).find{it.name == "Customer Request Type"} issue.setCustomFieldValue(crtField, "Desired Request Type")
To Clear it:
A brief summary of some Workflow enhancements plugins.
The main focusing is primarily on what they contribute on the Post-Function side of a Transition.
Workflow Enhancer for JIRA
A possible way to Log User- and Page-Access to statsd is via the Event system - using Adaptavist's Scriptrunner for Confluence, se https://scriptrunner.adaptavist.com/latest/confluence/ConfluenceEventHandlers.html#_collecting_stats
Read Access Logging in Confluence for good reasons to log via the Event system.
A possible way to Log User- and Page-Access in Confluence is via the Event system - using Adaptavist's Scriptrunner for Confluence.
This ways has Pros and Cons - read Access Logging in Confluence. On Pro is that the POST to Splunk in in the backend; so we dont need to open for the receiving system in the Firewall
This is how to make PUTs and POSTs:
def requestMethod = "PUT"; def URLParam = [] def baseURL = "http://myserver.domain.dk/" def url = new java.net.URL(baseURL); URLConnection connection = url.openConnection(); connection.setRequestMethod(requestMethod); connection.doOutput = true connection.setUseCaches(false); connection.setRequestProperty("Content-Type", "text/html"); connection.setRequestProperty('Authorization', 'Basic ' + 'username:password'.bytes.encodeBase64().toString()); //PUT Content OutputStreamWriter out = new OutputStreamWriter( connection.getOutputStream()); out.write("type=0"); out.write("&format=0"); out.write("&content_id=" + ListId); out.write("&jira_key=" + IssueKey); //out.write("options={\"status\":\"" + issueStatus + "\"}"); out.close(); //try //{ log.info "Script=MakeReport.groovy IssueKey=" + IssueKey + " ScriptRunIdent=" + scriptRunIdent + " URL=" + baseURL log.info "Script=MakeReport.groovy IssueKey=" + IssueKey + " ScriptRunIdent=" + scriptRunIdent + " Action=GetHttpsConnection" connection.connect(); try { connection.getContent() } catch (Exception ex) { log.info "Script=MakeReport.groovy IssueKey=" + IssueKey + " ScriptRunIdent=" + scriptRunIdent + " Error=ConnectionError Message="+ ex.getMessage() } String Status=connection.getResponseCode() String Message=connection.getResponseMessage()
A possible way to Log User- and Page-Access in Confluence is via the Event system - using Adaptavist's Scriptrunner for Confluence.
This ways has Pros and Cons - read Access Logging in Confluence. One Pro is that the POST to Splunk in in the backend; so we dont need to open for the receiving system in the Firewall for the world.
The goal is to replace the long "Days since Last Comment" with the short "D:HH:MM:
This can be done via a Scripted Field from JIRA Scriprunner:
import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.issue.Issue import com.atlassian.jira.issue.IssueManager import java.text.SimpleDateFormat // https://docs.atlassian.com/jira/server/com/atlassian/jira/issue/Issue.html // https://www.tutorialspoint.com/groovy/groovy_operators.htm long updatedDateTime; // If No Comments, use: updatedDateTime = issue.updated.getTime() // If has Comments, use date of Last Comment: def comments = ComponentAccessor.commentManager.getComments(issue) if (comments) { updatedDateTime = comments.last().getUpdated().getTime() } // Find Now long Now = System.currentTimeMillis() if (updatedDateTime) { // 1s = 1000 ms // Diff is positive, its now-lastupdated in seconds int Diff= (Now-updatedDateTime)/1000 // 1d = 86400s // Get Remains in seconds int Remains = Diff%86400 int Days = (Diff-Remains)/86400 String sDays = String.valueOf(Days) // 1h = 3600s int Remains2 = Remains%3600 int Hours = (Remains-Remains2)/3600 String sHours = "0" + String.valueOf(Hours) int Mins =Remains2/60 String sMins = "0" + String.valueOf(Mins) return sDays + ":" + sHours.reverse().take(2).reverse() + ":" + sMins.reverse().take(2).reverse() } else { return "0:00:00" }
curl -u admin:admin --header "X-Atlassian-token: nocheck" -X POST --data "scriptFile=$FilePathRelativeToSourcepath$" http://localhost:8080/jira/rest/scriptrunner/latest/user/exec/
To Create a Listener, create a directory structure to match below <jira-home>/data/scripts, like:
cd /opt/jira-data/data/script mkdir com com/firm com/firm/listener
go to the directory and make a class file: