How do I display a custom message on Pull Request page using ScriptRunner?

I'm evaluating ScriptRunner for Stash and want to create a custom message like the example here: https://scriptrunner.adaptavist.com/stash/latest/docs/customising_ui/  except I want to display a panel in the UI when some sensitive files have been touched in the Pull Request.  How do I do this in the context of a Pull Request (instead of the repository level)?

1 answer

3 votes

This is now possible to do in 4.3.14 of ScriptRunner for Bitbucket Server. Please see the release notes here. Using pulRequest.pathsMatch("glob:readme") will now compare the changed files in the diff.

Unfortunately Atlasssian have not made it easy to do it within the context of a pull request. If you go to a view pull request page and follow the instructions here you will see there are very few places the pull request is available as the context and certainly not as a banner.

However saying that we have found a solution for your requirement, although it involves a few moving parts unfortunately.

To summarise you will need a script REST endpoint to handle detecting the sensitive files in the pull request and to return the banner. Secondly you need some client side JavaScript which will grab the pull request id and the repository id from the page and call this endpoint so it can lookup the pull request, it will also handle inserting the banner into the page.

First you need to install a web resource by going to: Admin -> ScriptRunner -> Script Fragments -> Install web resource. The configuration should look the screenshot below: 

Screen Shot 2016-07-19 at 16.58.47.png

The context we have specified above ensures this resource is only loaded on a view pull request page.

The content of pull-request-banner.js should be: 

(function ($) {
    $(function () {

        require(["bitbucket/util/state"], function (state) {
            var pullRequestId = state.getPullRequest().id;
            var repoId = state.getRepository().id;

            renderPrBanner(pullRequestId, repoId);
        });

        function renderPrBanner(pullRequestId, repoId) {
            AJS.$.ajax({
                type: "GET",
                dataType: "html",
                url: AJS.contextPath() + '/rest/scriptrunner/latest/custom/showSensitiveFilesBanner?pullRequestId=' + pullRequestId + '&repoId=' + repoId
            }).done(function (data) {
                AJS.$('.aui-page-panel-content').prepend(data);
            }).fail(function (jqXHR, textStatus, errorThrown) {
                console.warn("Failed to execute remote:", errorThrown);
            });
        }
    });
})(AJS.$);

This pull-request-banner.js should be placed in the root of on of your plugin.resource.directories by default this is the scripts directory under your Bitbucket home if not specified.

In the above code you will see requires like:

require(["bitbucket/util/state"]

This is part of the Bitbucket JavaScript API and you can find out more about it here.

You should then add a scripted REST endpoint that contains the following script:

import com.atlassian.bitbucket.content.AbstractChangeCallback
import com.atlassian.bitbucket.content.Change
import com.atlassian.bitbucket.pull.PullRequestChangesRequest
import com.atlassian.bitbucket.pull.PullRequestService
import com.atlassian.sal.api.component.ComponentLocator
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript

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

@BaseScript CustomEndpointDelegate delegate

def pullRequestService = ComponentLocator.getComponent(PullRequestService)

showSensitiveFilesBanner() { MultivaluedMap queryParams ->
    def pullRequestId = queryParams.getFirst("pullRequestId") as String
    def repoId = queryParams.getFirst("repoId") as String

    def pullRequest = pullRequestService.getById(repoId.toInteger(), pullRequestId.toLong())

    def senstiveFilePatterns = ["README.md", "basic_branching/file.txt"]

    def fileChangeFinder = new FileChangeFinder(senstiveFilePatterns)
    pullRequestService.streamChanges(new PullRequestChangesRequest.Builder(pullRequest).build(), fileChangeFinder)

    def banner = ""
    if (fileChangeFinder.fileFound) {
        banner = """
        <div class="aui-message aui-message-warning">
            <p class="title">Caution! Sensitive files detected</p>
            <p>This pull request contains sensitive files which have been modified, added or deleted.
            </p>
        </div>
        """
    }

    Response.ok().type(MediaType.TEXT_HTML).entity(banner).build()
}

class FileChangeFinder extends AbstractChangeCallback {

    private boolean fileFound = false
    private List<String> files

    FileChangeFinder(List<String> files) {
        this.files = files
    }

    @Override
    boolean onChange(@Nonnull Change change) throws IOException {

        if (change.path.toString() in files) {
            fileFound = true
        }

        return true
    }
}

You will need to change the contents of the sensitiveFilePatterns in the above code to match your sensitive file patterns. This uses pathsMatch and you can find out more about the syntax here.

After that when you go to view a pull request which has sensitive files you should see a banner like:

Screen Shot 2016-07-20 at 10.16.55.png 

If you follow along, you should shortly have this working. Feel free to comment if you come across any hurdles along the way.

Hope this helps,
Adam 

Thanks for the detailed answer Adam, but it looks like these options are not available to us since we are still running Stash 3.11.4.  Are there options for our version of the product?

In the meantime, I have created a post-receive hook to send email if any of these sensitive files have been touched using the following condition:

pathsMatch('glob:path/to/sensitive.txt')

using this email template:

${EmailUtil.displayAllCommits()}

I find that the emails are very large and contain many unrelated files in the output.  Perhaps these are from previous merges?

How can I limit this output to only a listing of the sensitive files or maybe a link to a pull request?

Michael

 

 

Hi Michael,

Sorry to disappoint but we are only developing new features for Bitbucket, we dropped support for Stash when Atlasssian done a major rebrand to Bitbucket Server and renamed all the classes forcing plugin vendors to put out new versions of their plugins. So the script fragments is not available in that version and there is no easy way of achieving your requirement with that version. Do you have any plans to upgrade from Stash to Bitbucket in the near future?

Display all commits only outputs the changes that were present in the push. Perhaps a user rebased or merged a branch and then pushed as you suggested

The pathsMatch doesn't give you the list of sensitive files unfortunately, it just tells you if any sensitive files were present. Theres currently not a straightforward way of doing this in the template unfortunately. We are looking at making this more flexible in the future.

Thanks,
Adam

Hi Adam,

We were finally able to upgrade to Bitbucket Server and I was able to get your suggested solution working.  However I am finding that the banner shows on pull requests which have no explicit changes to the sensitive files, just like I saw with the email hook.  What I would like to see is the banner display when one or more sensitive files are found in the diff between this pull request branch and its intended target branch. Any suggestions would be much appreciated.

Thanks,

Michael

I tested the server-side part of Adam's code (because if there's a problem that's where it must be), and I didn't have any problems.

A couple of notes though.

pathsMatch will return true regardless of the change types, so even if sensitive files were deleted it would be true. Even if they were added and then deleted in separate commits it would return true. This is important because even if the sensitive files are not present in the HEAD of the PR, they would still be getting in to the main branch.

The other thing is that pathsMatch uses a function that gets the commit contents, and it caches its value using as keys the pull request ID and version ID. When you push new files to a PR the version ID will get bumped. I don't see that this would cause the issue.

Feel free to open a support ticket at https://productsupport.adaptavist.com/servicedesk/customer/portal/3 including the "git log" from the branch point to the tip of the PR, and your source code for the rest endpoint.

 

Hi Jamie,

I don't see this issue when a fresh branch is created, only when I merge the master branch back into that new branch and that merge contains a sensitive file. I have an example of this. So the server-side code seems to be picking up all the changes, not just the diff in the pull request.

Michael

Oh, so you're seeing the banner before the merge is done, but not after the merge?

  1. If I create a new branch and pull request which does not contain a change to a sensitive file, I do not see the banner (expected).
  2. With the pull request from step 1 still open, if I merge master which contains a change to a sensitive file, I see the banner even though that change is not in the pull request diff (not expected).

 

Hi Michael,

I'm having some trouble trying to reproduce the issue you explained to Jamie. Here are the steps I took to reproduce which sounds like what you did.

  • Open a pull request with a change to a non-sensitive file on branch "non-sensitive-branch" and destination "master" (banner not displayed)
  • With the pull request still open I make a change to a sensitive file on master and push this change to master. 

  • I then checkout branch "non-sensitive-branch" and do:

    git merge master
    git push -f
  • Bitbucket then has that sensitive file change from master on the "non-sensitive-branch" and it isn't displayed as a new commit or in the diff. (banner not displayed)

It sounds like at the last step the banner is being displayed for you.

Can you list the steps I can take to reproduce, maybe you are doing it in a slightly different way?
Feel free to open a support ticket at https://productsupport.adaptavist.com/servicedesk/customer/portal/3 with steps to reproduce and then we can discuss in more detail.

Thanks,
Adam 

Hi Adam,

I have created a support ticket.  Please let me know if you have any questions.

Michael

Hi Michael,

I was able to reproduce this as you described. Unfortunately this is a bug in the pathsMatch method as it considers files in all commits for the pull request, but I don't have a quick fix I can provide you with at the moment. Good news is that we will look at fixing this in the next sprint in just under 2 weeks time.

Please track SRBITB-153 to see when the fix will be released.

Thanks,
Adam

Hi Michael,

I have updated the REST endpoint code in my answer to now stream the changes and it will look for specific paths. This should only check the files displayed in the pull request diff. 

We'll probably add another user friendly method in the near future on the pull request to make this sort of thing easier and give you the ability to specify patterns like in pathsMatch.

Thanks,
Adam 

Hi Adam,

Thank you, this seems to work fine for the pull request pages.  

Is there a way to adapt this for an event handler for a PullRequestOpenedEvent?  When I use pathsMatch, I see the same issue and the email contains many files.  Or should I wait for the fix for pathsMatch?  We want to send an email when a pull request is opened which contains one or more of the same sensitive files.

Michael 

What do you want to do when you have the PullRequestOpenedEvent and after you check for sensitive files? Perhaps you could create a new question with your requirements on there with the tag: com.onresolve.stash.groovy.groovyrunner

Then we will go from there. As this feels like a new question.

 

Can you attach Screen Shot 2016-07-19 at 16.58.47.png again? I am not able to view it.

Screen Shot 2016-07-20 at 10.16.55.pngScreen Shot 2016-07-19 at 16.58.47.pngI've attached both screenshots again. Hope it helps.

Thanks for replying Adam. I tried couple of times and was able to figure out with hit and trial method :) 

Suggest an answer

Log in or Sign up to answer
Community showcase
Published Nov 29, 2018 in Marketplace Apps

How to set up an incident workflow from the VP of Engineering at Sentry

Hey Atlassian community, I help lead engineering at Sentry, an open-source error-tracking and monitoring tool that integrates with Jira. We started using Jira Software Cloud internally last year, a...

1,157 views 0 8
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