Where do I place A ScriptRunner script?

Eric Dierkens May 26, 2017

I've installed ScriptRunner, where do I place my script? Do I have to create a plugin to use ScriptRunner?

I want ti use ScriptRunner to populate a custome field with a checkbox list of Confluence pages. If I need to crate a plugin do do this, why would I need ScriptRunner.

Also, is there a graphical IDE editor, not commad line, for creating JIRA/Confluence plugins?

Can I develop plugins for JIRA/Confluece in C#?

8 answers

1 accepted

1 vote
Answer accepted
Jonny Carter
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 30, 2017

So, I'm a bit late to the party on this, but I think ScriptRunner can totally do something for your use case (that is, display or modify a checkbox custom field that lists a bunch of Confluence spaces). No need to write another plugin.

It sounds like your core goal is that when someone's editing an issue, you want them to be able to select a few different Confluence spaces. Is that right?

If so, there are a few ways you might approach the problem, but Scripted Fields aren't one of them--as Nic pointed out, those are just calculated fields, not custom fields that a user might modify through the UI.

I think the easiest way would be to make a classic checkbox field, and then use ScriptRunner to keep the options in sync with Confluence. Doing that would require a few parts:

  1. You'd need to create a checkbox list custom field (or multi-select, depending on your preference)
  2. Create a scripted service that periodically calls to Confluence and updates the list of options. For information on making requests to Confluence from JIRA, see our documentation on Interacting with other Atlassian Apps.

I'll follow up with some more detailed code in a bit.

We're sorry it's been so hard for you to get started. To say a word about the docs, believe me, as a member of the ScriptRunner dev team, we're acutely aware of the need to make the docs more approachable. We're working with the Learning and Development team here at Adaptavist to improve them, as well as developing training to make it easier for people to get started with ScriptRunner.

I noticed you were having some trouble finding where to contact support. I haven't been able to find a support ticket from you, but if you can get back to me with your issue key, I can look it up and we can talk privately there if need be.

Eric Dierkens May 30, 2017

Thank you so much for the responce Jonny. This is very close to what I'm looking to do. The idea is to display a list of Confluence pages from a particular space/parent. Each item in the lisy would have a checkbox the user can select. JIRA would need to store the selected items, perferably page id, within JIRA. The text of each checkbox would be a link to the Confluence page.

Coding in JIRA/Confluence is new to me with Java. I'm a web developer that uses C# (MVC) so I'm familer with object oriented programming, html, javascript. I'm still getting use to Velocity and Groovy.

Jonny Carter
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 30, 2017

No problem, Eric! So, here's a quick script I hacked together that you can run from the script console to get all the pages from a Confluence Space and add them as options to an existing Custom Field called "Confluence Pages". You'll want to modify some of the code to match your Confluence space's key and the name of your custom checkbox field.

import com.atlassian.applinks.api.ApplicationLinkResponseHandler
import com.atlassian.applinks.api.ApplicationLinkService
import com.atlassian.applinks.api.application.confluence.ConfluenceApplicationType
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.context.GlobalIssueContext
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.sal.api.net.Response
import com.atlassian.sal.api.net.ResponseException
import groovy.json.JsonSlurper

import static com.atlassian.sal.api.net.Request.MethodType.GET

//Get the field and its options
def customFieldManager = ComponentAccessor.customFieldManager
def optionsManager = ComponentAccessor.optionsManager
def confluencePagesField = customFieldManager.getCustomFieldObjectByName("Confluence Pages")
def fieldConfigSchemeManager = ComponentAccessor.fieldConfigSchemeManager
def globalIssueContext = GlobalIssueContext.getInstance()
def configScheme = fieldConfigSchemeManager.getConfigSchemesForField(confluencePagesField)[0]
def fieldConfig = configScheme?.oneAndOnlyConfig ?: fieldConfigSchemeManager.getRelevantConfig(globalIssueContext, confluencePagesField)
def existingOptions = optionsManager.getOptions(fieldConfig)

def appLinkService = ComponentLocator.getComponent(ApplicationLinkService)
def appLink = appLinkService.getPrimaryApplicationLink(ConfluenceApplicationType)
def applicationLinkRequestFactory = appLink.createAuthenticatedRequestFactory()
def spaceKey = "ASDF"
def request = applicationLinkRequestFactory.createRequest(GET, "/rest/api/content?spaceKey=$spaceKey")
def handler = new ApplicationLinkResponseHandler<Map>() {
    @Override
    Map credentialsRequired(Response response) throws ResponseException {
        return null
    }

    @Override
    Map handle(Response response) throws ResponseException {
        assert response.statusCode == 200
        new JsonSlurper().parseText(response.getResponseBodyAsString()) as Map
    }
}

def pagesFromConfluence = request.execute(handler)["results"]
pagesFromConfluence.eachWithIndex{ pageInformation, int index ->
    def relativePageLink = pageInformation["_links"]["webui"]
    def pageIdAttribute = $/data-page-id="${pageInformation['id']}"/$
    def linkToPage = $/<a href="${appLink.displayUrl}${relativePageLink}" $pageIdAttribute" >${pageInformation['title']}</a>/$
    def option = existingOptions?.find { it.value.contains(pageIdAttribute)}
    if (!option){
        optionsManager.createOption(fieldConfig, null, index + 1, linkToPage)
    }
}

A few important notes:

  • Checkbox options in JIRA only let you set the string display value. The ID is uniquely generated by JIRA and stored there. I've worked around this by adding a data-page-id attribute to the hyperlink in the option's value. That way, even if the title changes, we won't create a redundant option.
    • Notably, I didn't go so far as to cover updating the option's value when a page title changes.
  • This code assumes you've configured an application link between JIRA and Confluence, and that the user running it has admin permissions on both instances. It further assumes that you've authenticated the link as that user, so that JIRA has access to get a token from Confluence.

To run this code as a scheduled service, you'd need to edit it a bit to set the authenticated user first.

import com.atlassian.applinks.api.ApplicationLinkResponseHandler
import com.atlassian.applinks.api.ApplicationLinkService
import com.atlassian.applinks.api.application.confluence.ConfluenceApplicationType
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.context.GlobalIssueContext
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.sal.api.net.Response
import com.atlassian.sal.api.net.ResponseException
import groovy.json.JsonSlurper

import static com.atlassian.sal.api.net.Request.MethodType.GET

def jiraAuthenticationContext = ComponentAccessor.getJiraAuthenticationContext()
def originalUser = jiraAuthenticationContext.getLoggedInUser()

def userManager = ComponentAccessor.getUserManager()
def adminUser = userManager.getUserByName("serviceAccount")
assert adminUser // make sure an admin user called "deployer" exists in all instances

try {
    jiraAuthenticationContext.setLoggedInUser(adminUser)

    // make REST requests here

//Get the field and its options
    def customFieldManager = ComponentAccessor.customFieldManager
    def optionsManager = ComponentAccessor.optionsManager
    def confluencePagesField = customFieldManager.getCustomFieldObjectByName("Confluence Pages")
    def fieldConfigSchemeManager = ComponentAccessor.fieldConfigSchemeManager
    def globalIssueContext = GlobalIssueContext.getInstance()
    def configScheme = fieldConfigSchemeManager.getConfigSchemesForField(confluencePagesField)[0]
    def fieldConfig = configScheme?.oneAndOnlyConfig ?: fieldConfigSchemeManager.getRelevantConfig(globalIssueContext, confluencePagesField)
    def existingOptions = optionsManager.getOptions(fieldConfig)

    def appLinkService = ComponentLocator.getComponent(ApplicationLinkService)
    def appLink = appLinkService.getPrimaryApplicationLink(ConfluenceApplicationType)
    def applicationLinkRequestFactory = appLink.createAuthenticatedRequestFactory()
    def spaceKey = "ASDF"
    def request = applicationLinkRequestFactory.createRequest(GET, "/rest/api/content?spaceKey=$spaceKey")
    def handler = new ApplicationLinkResponseHandler<Map>() {
        @Override
        Map credentialsRequired(Response response) throws ResponseException {
            return null
        }

        @Override
        Map handle(Response response) throws ResponseException {
            assert response.statusCode == 200
            new JsonSlurper().parseText(response.getResponseBodyAsString()) as Map
        }
    }

    def pagesFromConfluence = request.execute(handler)["results"]
    pagesFromConfluence.eachWithIndex { pageInformation, int index ->
        def relativePageLink = pageInformation["_links"]["webui"]
        def pageIdAttribute = $/data-page-id="${pageInformation['id']}"/$
        def linkToPage = $/<a href="${appLink.displayUrl}${relativePageLink}" $pageIdAttribute" >${pageInformation['title']}</a>/$
        def option = existingOptions?.find { it.value.contains(pageIdAttribute) }
        if (!option) {
            optionsManager.createOption(fieldConfig, null, index + 1, linkToPage)
        }
    }
}
finally {
    jiraAuthenticationContext.setLoggedInUser(originalUser)
}

Again, change the username from serviceAccount to match a cross-product admin's username.

If you need further help with it, let us know. Coming from C#, I suspect you'll find Groovy relatively straightforward to learn, especially if you're used to coding with the Linq library; a lot of Groovy's collectors and iterator methods are analagous to those in Linq (though more intuitive IMHO). Warning: you may find that Groovy spoils you a bit. ;)

Eric Dierkens May 30, 2017

Thanks - if there s an award for customer support, you just earned it.

Out of curiosity - what code editor do you use?

Jonny Carter
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 30, 2017

You're welcome! So glad we could help.

As to code editor, I use IntelliJ IDEA. We have some guidance on setting up a development environment using IDEA's community edition if you're interested. https://scriptrunner.adaptavist.com/latest/jira/DevEnvironment.html

Full disclosure: we are also hard at work at improving the code editing experience in the browser, but using an IDE with good Groovy support is currently the best way for a power user to start churning out scripts quickly, and will be for the near future.

Happy scripting, and don't hesitate to hit us up on here with a new question when you have one. Tagging your question with the formal (and rather unwieldy) tag of com.onresolve.jira.groovy.groovyrunner is a good way to make sure we see it.

Eric Dierkens May 30, 2017

Cool - I installed IntelliJ last week, just need to get it configured.

I did run into one compile issue. I chnaged the name of the space key and the name of my SR custome field to match mine. One thing I'm not sure of, how does this know I want checkboxes and a list of them? I'm not seeing any For loops to iterate over the pages and create chechboxes.

Capture.PNG

Jonny Carter
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 30, 2017

So, the static type checker is not everything we wish it was. It's not perfectly aware of all of Groovy's dynamic abilities, so it thinks I'm passing bad data to a method when I'm not. You can ignore that error.

As for the loop, that's right there in this bit:

   pagesFromConfluence.eachWithIndex { pageInformation, int index ->
        //...loop code here
    }

That's using the eachWithIndex method signature to loop. I only used the withIndex version because I needed an index for where the option should be put in the list. You can look at http://mrhaki.blogspot.com/2009/09/groovy-goodness-looping-in-different.html and some other resources for guidance on looping with Groovy. Of course, traditional Java for loops work just fine as well if you prefer that syntax.

 

Eric Dierkens May 30, 2017

thx - I like the eachWithIndex. It's similar to the foreach in C#.

Eric Dierkens May 30, 2017

Jonny - what would I put in the Custom Template section?

Jonny Carter
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 31, 2017

Eric - are you still trying to make a Scripted Field? Remember, the script I wrote is meant to be run as a service that operates on a plain old JIRA Custom Field (a checkbox field, specifically). Putting that code I wrote into Script Field wouldn't work for what you're after. Remember, Script Fields are calculated fields that a user cannot edit. I think their use case is different from what you're trying to do.

If you're writing a Script Field for some other case, I'd suggest you take a look at the docs to start. https://scriptrunner.adaptavist.com/latest/jira/scripted-fields.html 

You'll only need a custom template if one of the built-in templates (Free text field, HTML, Date Time, etc.) doesn't work for your script. If none the built-in templates work to render the data returned by your script field, then you can write a velocity template as described in the docs on custom templates.

Again, I don't think you need a Script Field for this use case. If you're trying to do something else, I'd suggest starting a new question thread so this doesn't get too confusing.

JamieA
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
June 1, 2017

Hi Eric - 

As Jonny seems to be helping you through the issue, please would you consider modifying your review? https://marketplace.atlassian.com/plugins/com.onresolve.jira.groovy.groovyrunner/cloud/reviews

Regards, Jamie

0 votes
Edward Greathouse October 22, 2018

Maybe I missed something when I read through all the answers, but I don't think I actually saw an answer to the question "Where do I place a ScriptRunner script?". I have been using in-line scripts for behaviors, scripted fields, post-functions, etc. However, I have to do a lot of copy-paste that can be resolved by using files. Unfortunately, I don't know what file path to put for referencing files. 

By default, JIRA will try to install itself on C:/Program Files/Atlassian. Is this where ScriptRunner will begin searching for scripts? Should I be creating directories under the "Atlassian" folder called "behaviors", "post-functions", etc. under the "Atlassian" directory to organize my scripts? 

The title of this question seems to match what I'm asking.

Nic Brough -Adaptavist-
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
October 22, 2018

I usually stick them in <jira home>/scripts, and then have a directory structure that represents a convenient storage pattern (usually directories for behaviours, conditions, post-functions and so-on, but sometimes we do it by project or specific block of work).  

I tend to put them under source control as well, so I don't lose them.

Edward Greathouse October 22, 2018

Hi all,

For those who are struggling to find the folder to place your scripts, I found this link from another post that helped me:

https://scriptrunner.adaptavist.com/5.3.6/jira/#_script_roots

According to that documentation, a directory called "scripts" was created under your JIRA home directory.

Edward Greathouse October 22, 2018
0 votes
Eric Dierkens May 30, 2017

---

0 votes
Eric Dierkens May 28, 2017

I'm working in JIRA. I'm trying to create a user control that will display a checkbox list of Confluence pages that the user can select.

0 votes
Eric Dierkens May 27, 2017

Ran into my first script issue. The inlline script does not recognize the line "import com.atlassian.confluence.spaces.SpaceManager" (without quote of cource).

Nic Brough -Adaptavist-
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
May 27, 2017

Could you clarify where you're working here?  You appear to be trying to import Confluence libraries, but you talk about scripted fields, which are a JIRA thing.  Which application are you in?

By the way, I see your point about the docs for Script Runner, it's something I will pick up with my boss next week.  They're utterly dreadful.  The content is there, but it's in totally the wrong structure.  I can't justify that the scripted field page is even vaguely useful given that it says "A scripted field does X, now lets ramble about caching for the 1% of people who need to talk about it, and not mention how to start configuring or writing scripts for them"

Nic Brough -Adaptavist-
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
May 28, 2017

Ok, you can't import Confluence libraries into JIRA - they're part of Confluence.

A scripted field won't do this either.  They are read-only data, so there's no "checkbox a user can select from"

I suspect what you need here is a full add-on, as your code will need to work out what links to display and put them on-screen for selection, and then write any selections somewhere into the issue when the user commits the change.  SR could sort of do the first two, but you need a full custom field to do the third.

0 votes
Eric Dierkens May 27, 2017

I finally found part of the answer. I found it in all places this link from Arsenal Dataplane. This is the type of information that should be include in the Script Runner documentation. A page with screenshot and maybe an exapmle would be nice.

http://www.arsenalesystems.com/docs/display/dataplane/Reporting+on+Calculated+Values+with+ScriptRunner

*************************************************

Writing a Script for the Scripted Field
To define the functionality of the newly added Scripted Field:

Navigate to Toolgear » Add-ons and select Script Fields on the left-hand navigation menu
Scroll down to find your field, click the edit link, and write the script for your field

*************************************************

0 votes
Eric Dierkens May 27, 2017

When I view the custom field based on scriptrunner, there isn't any place to enter a script. The only choice I have from the gear menu are Configure and Edit and neither of those have places to enter a script. When I look at the scriptrunner documentation, it doesn't have any description or visual to guide a new person like myself.

I'm trying to write a custom field that will get all the Coinfluence pages for a given space/parent and then create a multislect checkbox list where the user can select multiple Confluence pages.

0 votes
Nic Brough -Adaptavist-
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
May 27, 2017

Scriptrunner is mostly used to avoid having to write add-ons.

Scripts can live in two places, either inside JIRA itself, in the places where you use scriptrunner, or in files on the file system (or a mix).  When you define where a script is used, you'll see the options - most of the scripting screens have three fields "Script description, script file location OR actual script"

Try going to edit a workflow, adding a "post function" and select the "write your own script" option, you'll see what I mean there.

That's also the answer for your "populate custom field" - you'll be able to write a script in there that can put the data into your custom field.

There is no GUI for ScriptRunner itself.  The in-line script box has a basic error checker, but that's it.  The reason is that it doesn't need one.  You should use your favourite fully featured IDE for editing scripts.  No need to re-invent the wheel.  See https://scriptrunner.adaptavist.com/4.3.4/jira/DevEnvironment.html

Finally, although you could write add-ons in C#, it would be a right pain.  The Atlassian applications are written in Java and variants, so it's a lot easier to write in Java, rather than have to implement a whole new framework.  (Although if your code is external, like parts of Connect add-ons or things that talk to JIRA over REST, then it's fine, of course, because you're not inside the service and using its frameworks)

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events