Last updatedDec 14, 2017
Improve this page

Decorating the user profile

Overview

Decorating the user profile allows a plugin writer to add information to a public user profile page in Bitbucket Server, to provide valuable information to the user which may not be available in a default Bitbucket Server installation.

Some examples of what you might build into Bitbucket Server's user profile page could include:

  • A listing of favorite repositories
  • Handy contact information for the developer, such as where they might sit in your company floor plan

Goals

This tutorial will take you through the steps to enhance your plugin to render an additional tab in the user profile page.

After completing this tutorial you will have learned to do the following:

  • Create a Servlet that extracts a user's profile
  • Add a new tab to the user profile page
  • Decorate the user profile using Soy to render its content

Steps

Generate a Bitbucket Server plugin using the Atlassian SDK

Ensure you have installed the Atlassian SDK on your system as described in this tutorial. Once installed, create a Bitbucket Server plugin project by running atlas-create-bitbucket-plugin and supplying the following values when prompted:

groupIdartifactIdversionpackage
com.mycompany.bitbucketbitbucket-profile-plugin1.0.0-SNAPSHOTcom.mycompany.bitbucket.plugin.profile

Add dependencies

To create a plugin which decorates the User profile with Soy, we need to add a set of dependencies to our plugin. We can do this using the atlas-create-bitbucket-plugin-module which is bundled with the Atlassian SDK.

We need to import the SoyTemplateRenderer and the UserService to render Soy templates and retrieve the ApplicationUser respectively.

Run atlas-create-bitbucket-plugin-module and choose the option for Component Import, using the following interfaces:

  • com.atlassian.soy.renderer.SoyTemplateRenderer
  • com.atlassian.bitbucket.user.UserService

Our Atlassian plugin can now import these interfaces from Bitbucket Server, however we still need to add a dependency to import the SoyTemplateRenderer in the pom.xml file.

Open pom.xml, locate the <dependencies> element and add the following lines:

1
2
3
4
5
<dependency>
    <groupId>com.atlassian.soy</groupId>
    <artifactId>soy-template-renderer-api</artifactId>
    <scope>provided</scope>
</dependency>

Create a servlet

In order to implement a new profile tab, you will first need to create a Java class that implements the HttpServlet interface. Let's call it ProfileServlet.

Execute atlas-create-bitbucket-plugin-module and choose the option for Servlet, name it ProfileServlet and choose the default package name (or change it, if you want).

When prompted to show advanced setup, choose yes. You can use the default options for all if you want – however change the URL Pattern to be /profile/users/*. This is the url that the servlet will respond to - we want to namespace this servlet to listen to the users component with any proceeding path components. We will use this to extract the user from the URL.

Note: if you don't choose advanced setup, you can change the url-pattern for your servlet in atlassian-plugin.xml.

Locate the ApplicationUser

Open ProfileServlet.java and locate the doGet() method. This is executed when the servlet url pattern is matched, namely /plugins/servlet/profile/users/*. Let's delete the default implementation, and replace it with the following code:

1
2
3
4
5
6
7
8
9
10
11
12
// Get userSlug from path
String pathInfo = req.getPathInfo();

String userSlug = pathInfo.substring(1); // Strip leading slash
ApplicationUser user = userService.getUserBySlug(userSlug);

if (user == null) {
    resp.sendError(HttpServletResponse.SC_NOT_FOUND);
    return;
}

// todo: render result

User Slugs

The user slug is a URL safe version of the user's username. It should always be used in preference to the username value.

We send a 404 not found response if either we can't understand the URL, or the user is not found.

In this implementation, we are using the UserService component to retrieve the user. We're also going to use the SoyTemplateRenderer that we imported, so let's go ahead and inject both into the constructor of this Servlet, as follows:

1
2
3
4
5
6
7
private final SoyTemplateRenderer soyTemplateRenderer;
private final UserService userService;

public ProfileServlet(SoyTemplateRenderer soyTemplateRenderer, UserService userService) {
    this.soyTemplateRenderer = soyTemplateRenderer;
    this.userService = userService;
}

Create a Soy Template

In order to render a soy template from our servlet, we need to create it. Navigate to the directory called src/main/resources, create a directory called templates and create profile.soy inside of it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{namespace plugin.profile}

/**
 * @param user ApplicationUser object which is in the context provided by ProfileServlet
 */
{template .profileTab}
<html>
<head>
    <meta name="decorator" content="bitbucket.users.profile">
    <meta name="userSlug" content="{$user.slug}">
    <meta name="activeTab" content="profile-plugin-tab">
    <title>{$user.displayName} / Profile Tab</title>
</head>
<body>
    <h3>Hello, {$user.displayName}</h3>
    <p>Welcome to my plugin tab in the profile page.</p>
</body>
</html>
{/template}

The <meta> tags are important - they instruct Bitbucket Server which decorator to use, and provide important information on how to build the page. Let's break them down:

Meta tagDescription
decoratorUse the `bitbucket.users.profile` decorator which provides the scaffolding around the user profile page
userSlugThe profile user which this page is decorating. This should be passed to your template from your `Servlet` which extracts the user from the URL
activeTabInforms the decorator which navigation tab is active on the profile page. We retrieve this value from the web item which is defined below

Add a profile tab item

In atlassian-plugin.xml, we need to define the location for our navigation item.

1
2
3
4
5
<web-item key="profile-plugin-tab" name="Profile navigation tab" section="bitbucket.user.profile.secondary.tabs" weight="20">
    <label key="profile.plugin.tab">My Plugin</label>
    <link>/plugins/servlet/profile/users/${profileUser.name}</link>
    <tooltip key="profile.plugin.tab.description">Hooray, we have a tab!</tooltip>
</web-item>

Hints:

  • The value of the <meta> tag activeTab which we put into our soy template above needs to match the key value of our web item.
  • The link which is used needs to match the url-pattern of the servlet which we defined at the beginning of this tutorial. The $profileUser variable is available on the context for this web item location - learn more about web fragments.

Render the Template

Add a resource

We need to add our resources to atlassian-plugin.xml.

1
2
3
<client-resource key="profile-soy" name="Profile Tab Soy Templates">
    <directory location="/templates/" />
</client-resource>

This will add a resource which finds all files in the /templates directory.

Render the template

Let's head back to ProfileServlet and wire up the template rendering.

Create a method called render(). It's going to have three arguments: the HttpServletResponse to send data down the wire with, the templateName of the soy template to render, and finally the arguments to the template in the form of a Map.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void render(HttpServletResponse resp, String templateName, Map<String, Object> data) throws IOException, ServletException {
    resp.setContentType("text/html;charset=UTF-8");
    try {
        soyTemplateRenderer.render(resp.getWriter(),
                "com.mycompany.bitbucket.bitbucket-profile-plugin:profile-soy",
                templateName,
                data);
    } catch (SoyException e) {
        Throwable cause = e.getCause();
        if (cause instanceof IOException) {
            throw (IOException) cause;
        }
        throw new ServletException(e);
    }
}

The second argument to soyTemplateRenderer.render() is the fully qualified resource name that we just created. It is made up of group.artifact:resource-name.

Finally, let's hook up the doGet() method to render(), by adding the following line to the bottom:

1
render(resp, "plugin.profile.profileTab", ImmutableMap.<String, Object>of("user", user));

That is, render the plugin.profile.profileTab template (as defined in profile.soy with the user as the only argument)

Try it out!

From the command line run atlas-run. Then you should connect to http://localhost:7990/bitbucket, logging in as admin/admin and visit the profile page. You should see a tab provided by your new plugin!

Congratulations!

Resources

You can view the source of this plugin on Bitbucket.