Client Web Fragment Plugin Modules

Introduction

Client Web Fragment plugin modules allow plugins to define new links and HTML within sections of Bitbucket Server that are rendered dynamically on the client.

Client Web Fragment module types include:

They parallel the functionality of Web Items, Web Panels, and Web Sections. Please see the Web Fragments overview for details applicable to both.

Please see the guide on adding to the pull request overview for an example of how they can be used.

##Understanding Client Web Fragment Fields

Because Client Web Fragments are rendered in the browser, they have a number of differences:

  • They are configured using JavaScript instead of Java classes.
  • Velocity templating is not available, and Soy templates or string-returning JavaScript functions are used instead
  • They can depend on other <web-resource> modules for JS and CSS.

A client web fragment might look as follows:

1
2
3
4
5
6
<client-web-item key="comment-create-issue" section="bitbucket.comments.comment.actions">
    <label>Create Issue</label>
    <link type="js">"/plugins/servlet/comment-issue?create&commentId=" + ctx.commentId</link>
    <client-context-provider>CommentIssueCreator.provideContext</client-context-provider>
    <dependency>com.mycompany.comment.issue:comment-issue-creator</dependency>
</client-web-item>

Compare this to a <web-item> plugin module that you might be used to:

1
2
3
4
5
<web-item key="comment-create-issue" section="bitbucket.comments.comment.actions">
    <label>Create Issue</label>
    <link>/plugins/servlet/comment-issue?create&commentId=${commentId}</link>
    <context-provider class="com.mycompany.comment.issue.CommentIssueContextProvider" />
</web-item>

The first difference is trivial: the name of the root element is prefixed with client-. But there are some more interesting differences as well:

  • The JS expression in the <link> element must explicitly specify the type "js" or will be evaluated as a plaintext string.
  • The <client-context-provider> references a JS function.

Field Types (text, js)

Each sub-element (label, link, and context-provider) contains a value that can be interpreted as either plain text or JavaScript. The default type varies between fields, but any type may be used by adding a type="js" or type="text" attribute to the field. Note that \"text\" might not be useful for all fields. For example:

1
2
3
<client-context-provider type="text">
    Error: This must be a JavaScript function. Using text here will not work.
</client-context-provider>
The "text" type

The contents of a "text" field will be treated as plaintext and the contents will used as the value of that field. For example, the <label> element above is a literal value.

The "js" type

The contents of a "js" field are re-evaluated as a JavaScript expression whenever the web fragment is rendered. If the result of that evaluation is a function, that function is called with the context object as the first parameter. That means, the following two values will both evaluate to "A string literal".

1
<label type="js">"A string " + "literal"</label>

and

1
2
3
4
5
<label type="js">
    function(context) {
        return "A string literal";
    }
<label>

When writing JavaScript fields, you MUST only provide a single expression. You MAY NOT provide a statement:

  • Allowed
    • function(context) {}
    • "string literal"
    • ctx.a + ctx.b + ctx.c ? ctx.x : ctx.y
    • (x = ctx.a, ctx.b) (but not encouraged)
  • Not allowed
    • var a = ctx.a (statement)
    • return ctx.b (statement)
    • "string literal"; (statement)
    • console.log('hi'); 7 (statement, then expression)

Context

Whenever a request is made to render your web fragments, a set of key-value pairs are passed in to give you information about your location. These are collectively referred to as the context. The convention in Bitbucket Server is to include a "project", a "repository", or a "pull-request" variable where appropriate, but when defining a web fragment location, you can include any parameters you think are useful. The context can be accessed from any JavaScript field. Let's look at an excerpt from above:

1
2
3
4
5
6
7
8
<client-context-provider>
    function(ctx) {
        return {
            dataA : 'Hello ' + ctx.subject,
            dataB : 'my other value'
        };
    }
</client-context-provider>

Since the context is passed in as the first argument to your function, you can read the \"subject\" property from it (assuming that this location has provided a "subject" key-value pair).

Security Note

Because Client Web Fragments are only rendered on the client, security and permissions checks must be done early in the process.

Be careful that:

  • You do not add sensitive data to the <client-web-item>, <client-web-panel>, or <client-web-section> XML descriptors - this data will be sent to anyone who requests it.
  • You do not use a <client-condition> element to hide sensitive data from users that shouldn't see it.

Instead, ensure that your sensitive data is protected by using a server-side <condition> to hide the fragment, and a server-side <context-provider> to populate the sensitive data. The data won't be sent to the client if the condition returns false.