Last updated Apr 3, 2024

Decorating the user account

Goals

This tutorial will take you through the steps to enhance your plugin to render an additional tab in the user's account 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 account using Soy to render its content

Steps

Generate a Bitbucket Data Center 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 Data Center 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 account 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 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.UserManager

Our Atlassian plugin can now import these interfaces from Bitbucket Data Center, 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
<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 account tab, you will first need to create a Java class that implements the HttpServlet interface. Let's call it AccountServlet.

Execute atlas-create-bitbucket-plugin-module and choose the option for Servlet, name it AccountServlet 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 /account/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 AccountServlet.java and locate the doGet() method. This is executed when the servlet url pattern is matched, namely /plugins/servlet/account/users/*. Let's delete the default implementation, and replace it with the following code:

1
2
// 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
private final SoyTemplateRenderer soyTemplateRenderer;
private final UserService userService;

public AccountServlet(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 account.soy inside of it.

1
2
{namespace plugin.account}

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

The <meta> tags are important - they instruct Bitbucket Data Center 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.account` decorator which provides the scaffolding around the user account page
userSlugThe 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 account page. We retrieve this value from the web item which is defined below

Add an account tab item

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

1
2
    <web-item key="account-plugin-tab" name="Account navigation tab" section="bitbucket.user.account.nav" weight="20">
        <label key="account.plugin.tab">My Plugin</label>
        <link>/plugins/servlet/account/users/${accountUser.name}</link>
        <tooltip key="account.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 $accountUser 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
<client-resource key="account-soy" name="Account 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 AccountServlet 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
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:account-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
2
render(resp, "plugin.account.accountTab", ImmutableMap.<String, Object>of("user", user));

That is, render the plugin.account.accountTab template (as defined in account.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 user account page. You should see a tab provided by your new plugin!

Congratulations!

Resources

You can view the source of this plugin on Bitbucket.

Rate this page: