A web-item is a button or link that will appear at your chosen location.

What happens when the link is clicked is up to you - some examples might be:

Let’s start with a simple example: We would like to add a link to Google on the top navigation bar. Fill out the form as follows:

link to google

Click Add to register the fragment. Refresh the page, and you should see your newly minted web item in the top navigation bar.

top item
You can understand what the values of the section attribute mean by reading the Atlassian documentation on Web UI modules , or you use the fragment locator tool.

Clicking the link should take you to Google. Experiment with the Weight field. With a weight of 1, your link is now the left-most one. A larger weight will move it further to the right.

So far, so easy, but not very useful.

Next we’ll try to add a link to Bamboo build results, which will do a Google search on the current build’s key.

Create a new web item or change the values to the following:

page link

You should now be able to see this on the results of a build:

tools menu link

Note that the URL is processed with velocity before rendering. The variables you can use depend on the context of the Web Item.

  • plan - a Plan object

If you don’t change the key, the module from the first part of the tutorial will be overwritten.

Acting on the current plan

Let’s say we want to run some code that operates on the current page, and returns a message to the user. For instance, deleting old artifacts for a given plan. First we will define a REST Endpoint script, with one endpoint, named cleanupArtifacts:

import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.json.JsonOutput
import groovy.transform.BaseScript

import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

@BaseScript CustomEndpointDelegate delegate

cleanupArtifacts(httpMethod: "GET") { MultivaluedMap queryParams ->

    /* The details deleting artifacts are omitted for brevity */
    def planKey = queryParams.getFirst("planKey") as String // use the plan key to retrieve the plan object

    def flag = [
        type : 'success',
        title: "Plan reviewed",
        close: 'auto',
        body : "Deleted outdated artifacts from $planKey"
    ]

    Response.ok(JsonOutput.toJson(flag)).build()
}

Verify that this works by browsing to: http://<bamboo-url>/rest/scriptrunner/latest/custom/cleanupArtifacts?planKey=TEST-ABC. It should simply respond with some JSON. If you are doing something with the plan be sure to enter a valid plan key.

Now create a new web item or change the values to the following:

delete old artifacts config

Notice that the Do what has changed to Run code and show a flag.

You should now be able to see this, when you select the Actions menu on the plan overview page:

delete old artifacts button

And clicking on Delete old artifacts should result in the following flag being displayed:

delete old artifacts flag

Conditions

The condition is used to define whether the link should be shown or not, and it can use anything available in its context, plus details of the current user.

For example, let’s say we wanted to restrict this web item to plans in a particular project.

context.planKey.startsWith("MYPROJECT-") //replace 'MYPROJECT' with your project's key

Enter this in as the Condition. It can either be entered inline or written to a file underneath a script root.

Conditions must be written defensively. What is available in the context depends on the particular web section, and the page that it’s viewed on.

For instance, a link in the top section (system.top.navigation.bar) will not have access to a plan.

If you are using a section like chainResult, you can assume you will always have the plan context variable defined.

Dialogs (Advanced)

There is some support for showing dialogs, although if you require complex interactions you will need to write some javascript.

To display a dialog when the user clicks a web-item, configure a Web Item and select the Run code and display a dialog option. The endpoint needs to return HTML for an AUI dialog2. The following REST endpoint code displays a dialog when clicked:

import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript

import javax.ws.rs.core.MediaType
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

@BaseScript CustomEndpointDelegate delegate

showDialog() { MultivaluedMap queryParams ->

    def dialog =
        """<section role="dialog" id="sr-dialog" class="aui-layer aui-dialog2 aui-dialog2-medium" aria-hidden="true" data-aui-remove-on-hide="true">
            <header class="aui-dialog2-header">
                <h2 class="aui-dialog2-header-main">Some dialog</h2>
                <a class="aui-dialog2-header-close">
                    <span class="aui-icon aui-icon-small aui-iconfont-close-dialog">Close</span>
                </a>
            </header>
            <div class="aui-dialog2-content">
                <p>This is a dialog...</p>
            </div>
            <footer class="aui-dialog2-footer">
                <div class="aui-dialog2-footer-actions">
                    <button id="dialog-close-button" class="aui-button aui-button-link">Close</button>
                </div>
                <div class="aui-dialog2-footer-hint">Some hint here if you like</div>
            </footer>
        </section>
        """

    Response.ok().type(MediaType.TEXT_HTML).entity(dialog.toString()).build()
}

The button with the ID dialog-close-button will be automatically wired to close when clicked if you use a dialog ID of sr-dialog.

Clicking the link the dialog will display:

show dialog

To add more advanced features to dialog boxes using JavaScript, see the section on Web Resources.

Deep Dive: Adding Static Analysis Tools to Build Results

As a deeper example, let’s imagine we want to raise the profile of our static build analysis tool. We’ve already configured a build artifact, to hold the HTML report of our static analysis job, but it’s getting ignored. No one clicks the artifacts tab, and even if they did there’s a baker’s dozen other artifacts there, so they skim right past it.

We’ve setup an example project to illustrate this case using CodeNarc, but any static analysis tool capable of generating reports should do fine. Note that there’s a Bamboo Spec class in the resources directory to configure the plan for you if you want to try this example on a test host.

Let’s add a static analysis tab to our builds so that the output of a static analysis tool can be displayed in the build results.

To do this, we’ll need to wire up a REST Endpoint to return the HTML file inside of an iframe.

import com.atlassian.bamboo.build.artifact.AbstractArtifactHandler
import com.atlassian.bamboo.build.artifact.ArtifactHandlingUtils
import com.atlassian.bamboo.build.artifact.handlers.ArtifactHandlersService
import com.atlassian.bamboo.chains.ImmutableChainResultsSummary
import com.atlassian.bamboo.plan.PlanKeys
import com.atlassian.bamboo.resultsummary.ResultsSummaryManager
import com.atlassian.plugin.PluginAccessor
import com.atlassian.sal.api.ApplicationProperties
import com.atlassian.sal.api.UrlMode
import com.atlassian.sal.api.component.ComponentLocator
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript

import javax.servlet.http.HttpServletRequest
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

@BaseScript CustomEndpointDelegate customEndpointDelegate

staticAnalysis(httpMethod: "GET") { MultivaluedMap queryParams, String body, HttpServletRequest request ->
    String planKey = queryParams.getFirst("planKey")
    String buildNumber = queryParams.getFirst("buildNumber")
    def resultsSummaryManager = ComponentLocator.getComponent(ResultsSummaryManager)
    def resultKey = PlanKeys.getPlanResultKey(planKey, buildNumber.toInteger())
    def results = resultsSummaryManager.getResultsSummary(resultKey) as ImmutableChainResultsSummary
    def subResults = results.stageResults[0].buildResults (1)
    def artifactLinks = subResults.collectMany { it.producedArtifactLinks }
    def artifact = artifactLinks.find {
        it?.artifact?.label == 'Static analysis' (2)
    }?.artifact
    def pluginAccessor = ComponentLocator.getComponent(PluginAccessor)
    def handler = ArtifactHandlingUtils.getArtifactHandlerForLink(pluginAccessor, artifact.linkType)
    def artifactHandlersService = ComponentLocator.getComponent(ArtifactHandlersService)
    def artifactHandlerConfigProvider = AbstractArtifactHandler.configProvider(artifactHandlersService)
    def dataProvider = handler.getArtifactLinkDataProvider(artifact, artifactHandlerConfigProvider)
    def baseUrl = ComponentLocator.getComponent(ApplicationProperties).getBaseUrl(UrlMode.ABSOLUTE)
    def artifactUrl = dataProvider.listObjects(null).first().getUrl()
    String url = "${baseUrl}${artifactUrl}"
    def html = /<iframe style="width: 100%; height: 1000px;" src="$url"><\/iframe>/.toString()
    return Response.ok(html, MediaType.TEXT_HTML).build()
}
1 We’re assuming that the static code analysis runs in the first stage of the plan, though you can adjust as appropriate
2 Get the right artifact, based on its label of "Static analysis"

You can double-check that the REST Endpoint is working by browsing to <your bamboo base URL>/rest/scriptrunner/latest/custom/staticAnalysis?planKey=PROJ1-NARC&buildNumber=1. If you’re not using our sample project, change the plan key and build number as appropriate. You should get a page with the HTML report from CodeNarc.

Now we can add a Web Item to dynamicaly drop that HTML into our build results.

static analysis web item

Key parts of the configuration:

  • To make sure this shows up as a tab, we need to add the web item to the chainResults.subMenu/chainResults section

  • To make sure this only shows up in our example plan, we’ll add a condition of context["planKey"].toString().contains("NARC")

  • On "Do what", we must choose "Do nothing - you will use javascript to bind the action"

  • The Link field needs to point to our custom REST Endpoint. We can use variables in the context to construct the URL, so /rest/scriptrunner/latest/custom/staticAnalysis?planKey=${planKey}&buildNumber=${buildNumber} should do the trick.

  • You can adjust the weight to determine the order in which the tab appears in the list of tabs.

With that in place, navigate to the build history of our sample plan. You should see a static analysis tab that includes the report from CodeNarc. Notably, we didn’t have to wire up any JavaScript to write this. Bamboo’s own javascript for moving between tabs did all that work for us!

You can use this same basic format to add other tabs to your build results. All you need is a REST Endpoint that returns an HTML snippet and a Script Fragment that adds a Web Item to chainResults.subMenu/chainResults and calls it.

For how-to questions please ask on Atlassian Answers where there is a very active community. Adaptavist staff are also likely to respond there.

Ask a question about ScriptRunner for JIRA, for for Bitbucket Server, or for Confluence.