Where do I place A ScriptRunner script?

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

0 votes
Accepted answer

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.

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.

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. ;)

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

Out of curiosity - what code editor do you use?

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.

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

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.

 

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

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

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.

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

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)

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.

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

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

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

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"

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.

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.

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.

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.

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.

Suggest an answer

Log in or Sign up to answer
Community showcase
Published Oct 31, 2018 in Marketplace Apps

Marketplace Spotlight: Zephyr

Hello Atlassian Community! Each month, we run a series of Spotlights to highlight Marketplace vendors and apps that our team thinks this Community would find valuable. In last month's Spotlig...

412 views 0 3
Read article

Atlassian User Groups

Connect with like-minded Atlassian users at free events near you!

Find a group

Connect with like-minded Atlassian users at free events near you!

Find my local user group

Unfortunately there are no AUG chapters near you at the moment.

Start an AUG

You're one step closer to meeting fellow Atlassian users at your local meet up. Learn more about AUGs

Groups near you