Using Soy templates to create a BitBucket plugin configuration page

plugin-sdk
bitbucket-server

#1

Hi,

For the last few days I’ve been reading up on using Soy templates as a way of allowing a plugin to be configured by a BitBucket user/admin and have managed to render a webpage with a couple of text boxes and buttons on them.

{namespace bitbucket.practice-hook}

/**
 * @param repository Repository object
 */
{template .repositorySettings}
<html>
<head>
    <meta name="decorator" content="bitbucket.repository.settings">
    <meta name="projectKey" content="{$repository.project.key}">
    <meta name="repositorySlug" content="{$repository.slug}">
    <meta name="activeTab" content="repository-settings-plugin-tab">
    <title>{$repository.slug} / Example Tab</title>
</head>
<body>
    <h3>Repository: {$repository.slug}</h3>
    <p>Hello World!</p>
	
	<form class="aui">
		<div class="field-group">
			<label for="comment-name">Name
				<span class="aui-icon icon-required">(required)</span></label>
			<input class="text medium-field" type="text"
				   id="comment-name" name="comment-name" placeholder="Full Name">
		<div class="buttons-container">
			<div class="buttons">
				<input class="button submit" type="submit" value="Save" id="comment-save-button">
				<a class="cancel" href="#">Cancel</a>
			</div>
		</div>
	</form>


</body>
</html>
{/template}

I’m now trying to work out how to connect this webpage to the plugin and allow the data that’s entered to be stored and used by the plugin. During my reading, I found out about the PDE (plugin data editor) which is where I think the plugin data is stored but cannot get the page to push the data there.

I’ve found a few tutorials which are either for just Jira/Confluence or appearing to be using older methods for it. Is there a clear tutorial or set of instructions for setting up a Soy template and connecting it in to a plugin?


#2

What you are asking is fairly broad, and you haven’t provided many details about your plugin’s setup. So hopefully this information will help, but if not feel free to reply and ask for more specifics.


Your first question was around getting the data from the client to the server.
There are generally 2 ways I’d recommend doing this, but it depends on your front-end setup as to which one is better for you:

  1. HttpServlet (from looking at your code snippet, this is probably the one you want), or
  2. RestResource

The HttpServlet has a doPost method which your servlet can override. A form submission from your soy will call that method with the details of your form and from there you can save your data (we’ll get to that soon).
Documentation
Example (from bitbucket-server-example-plugin)

The RestResource uses javax annotations (e.g. @POST) and you can use javascript to post to this endpoint. Once in the resource, you can read data from the request and save it.
Documentation
Example (from bitbucket-code-coverage-plugin)


Your second question was around storing the data. There are a few ways to do this, and it will depend on what data you are trying to store.

  1. Plugin settings
  2. Active objects

Plugin settings is to store configuration for your plugin. It is not for storing user settings, project settings, repo settings or anything else that is scoped to something more specific than your global plugin settings.
Documentation
Example (from stash-auto-unapprove-plugin)

Active objects allows you to create tables in the database to store your data. While its a little harder to get started with active objects than it is with plugin settings, it is definitely a much better option for the data that most plugins store.
Documentation
Example (from bitbucket-code-coverage-plugin)

Hopefully that was helpful!
I didn’t go into too much details because I’m not sure which option is best for you. Let me know if you’d like me to expand on anything.

Regards,
Kristy


How to store plugin settings at repo + project level?
#3

Hi Kristy,

Thank you for replying, this has been really helpful as I wasn’t sure if it was all the same thing or separate methods.

I’ve followed the tutorials, read the documentation and have tried to get it working which it partly does. I’ve found that the doPost method is not being called when I try to save the data entry from the web page, it’ll store the key creation from the doGet method but not the data that was actually entered. The servlet code I’m working on is below:

package com.atlassian.branches.servlet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.atlassian.soy.renderer.SoyTemplateRenderer;
import com.atlassian.soy.renderer.SoyException;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.RepositoryService;
import com.atlassian.sal.api.pluginsettings.PluginSettings;
import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;

import com.google.common.collect.ImmutableMap;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.inject.Named;
import javax.inject.Inject;
import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component("RepoServlet")
@Named("RepoServlet")
public class RepoServlet extends HttpServlet {
	
	private static final Logger log = LoggerFactory.getLogger(RepoServlet.class);
	private static final String PLUGIN_KEY = "com.atlassian.branches.servlet.templates";
	
	@ComponentImport
	private SoyTemplateRenderer soyTemplateRenderer;

	@ComponentImport
	private RepositoryService repositoryService;

	@ComponentImport
	private PluginSettingsFactory pluginSettingsFactory;

	@Autowired
	public RepoServlet(@ComponentImport SoyTemplateRenderer soyTemplateRenderer,
			@ComponentImport RepositoryService repositoryService,
			@ComponentImport PluginSettingsFactory pluginSettingsFactory) {
		this.soyTemplateRenderer = soyTemplateRenderer;
		this.repositoryService = repositoryService;
		this.pluginSettingsFactory = pluginSettingsFactory;
	}

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String pathInfo = req.getPathInfo();
		String[] components = pathInfo.split("/");

		if (components.length < 3) {
			resp.sendError(HttpServletResponse.SC_NOT_FOUND);
			return;
		}

		Repository repository = repositoryService.getBySlug(components[1], components[2]);

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

		PluginSettings pluginSettings = pluginSettingsFactory.createGlobalSettings();

		if (pluginSettings.get(PLUGIN_KEY + ".name") == null) {
			String noName = "Enter a name here.";
			pluginSettings.put(PLUGIN_KEY + ".name", noName);
		}

		resp.setContentType("text/html;charset=UTF-8");
		soyTemplateRenderer.render(resp.getWriter(), "com.atlassian.branches.practice-hook:config-soy",
				"bitbucket.branches.repositorySettings", ImmutableMap.<String, Object>of("repository", repository));
		}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		PluginSettings pluginSettings = pluginSettingsFactory.createGlobalSettings();
		pluginSettings.put(PLUGIN_KEY + ".name", req.getParameter("name"));
	}
}

It could be that I’m working on this in the completely wrong way but from the existing documentation, this is what I’ve come up with.


#4

Hi @EEJobson,
I’m glad my response helped. Unfortunately, its a bit difficult to debug code over a forum, but I can try anyway!

However, in order to do that I’ll need a few more details. Can you be more specific about what you mean by “the doPost method is not being called”?
Are you getting an error on the client side that means it doesn’t send the request? Is the client sending the request but it gets lost somewhere along the way and doesn’t reach the doPost method? Does the request reach the doPost method but the request isn’t what you had expected? Is the request what you expected but saving to the plugin settings itself isn’t working?

Narrowing down the cause of the problem is probably what needs to happen before I can be more helpful.


#5

Try adding method = “post” in your form declaration.


#6

And in your doPost() method, you should have req.getParameter(“comment-name”)