Available: | Bamboo 3.1 and later |
In this tutorial we explain how to build a User Interface and add validation for user input to a plugin that implements a Bamboo Task.
First download the copy of the latest Plugin SDK and install it on your machine.
Run atlas-create-bamboo-plugin from the command line.
When asked for the groupId, artifactId and package just enter "helloworld" and leave the other options as the defaults.
We covered what a TaskType is and how it works in the Introduction to Tasks tutorial.
Create a new Task named "ExampleTask" that takes a value out of the configuration map using the key "say" and adds a new entry with its value to the build log and returns a successful TaskResult.
Change your class so that it looks like the one below.
1 2package helloworld; import com.atlassian.bamboo.build.logger.BuildLogger; import com.atlassian.bamboo.task.TaskContext; import com.atlassian.bamboo.task.TaskException; import com.atlassian.bamboo.task.TaskResult; import com.atlassian.bamboo.task.TaskResultBuilder; import com.atlassian.bamboo.task.TaskType; import org.jetbrains.annotations.NotNull; public class ExampleTask implements TaskType { @NotNull @java.lang.Override public TaskResult execute(@NotNull final TaskContext taskContext) throws TaskException { final BuildLogger buildLogger = taskContext.getBuildLogger(); final String say = taskContext.getConfigurationMap().get("say"); buildLogger.addBuildLogEntry(say); return TaskResultBuilder.newBuilder(taskContext).success().build(); } }
Do not forget to register your Task with the plugin system by adding the following to "atlassian-plugin.xml" between the atlassian-plugin
elements:
1 2<taskType name="helloworld" class="helloworld.ExampleTask" key="test"> <description>A simple Hello World Task</description> </taskType>
Bamboo uses i18n ("internationalization") property files to provide translations for human-readable texts that are used in the UI.
An i18n property file needs to be registered with the plugin system, by adding it to "atlassian-plugin.xml". By default, "atlas-create-bamboo-plugin" may have already added such a registration. Ensure that "atlassian-plugin.xml" contains a registration like the one below in the "atlassian-plugin" element:
1 2<resource type="i18n" name="helloworld language" location="english"/>
You will need to create the property file. Create a new file named "english.properties" in the "resources" of your project (src/main/resources/english.properties
).
For now, add just one line to that file:
1 2helloworld.say=Say
To render a UI component, Bamboo can use the Freemarker templating language. It has numerous tags available that can be used from within freemarker to map values in the Configurator to elements in the user interface (we will talk more about the Configurator in a moment).
Create a new Freemarker template file named "editExampleTask.ftl" in the "resources" of your project (src/main/resources/editExampleTask.ftl
).
Its content should match this:
1 2[@s.textfield labelKey="helloworld.say" name="say" required='true'/]
This template needs to be added to the registration of your Task with the plugin system, by modifying the registration that you added to "atlassian-plugin.xml" earlier, like this:
1 2<taskType name="helloworld" class="helloworld.ExampleTask" key="test"> <description>A simple Hello World Task</description> <resource type="freemarker" name="edit" location="editExampleTask.ftl"/> </taskType>
This template provides the configuration form for the Task. We use a textField
here to provide input. The labelKey
value helloworld.say
maps to a property defined in the english.properties
file that provides the translation (as described earlier). The name
specifies what the value of the textField
should be in the Configurator. In this case we always want the user to specify a value for the field, so we mark it with the required
attribute with the value set to true
.
So what is a TaskConfigurator? A TaskConfigurator is a class that controls what objects and values are available when rendering the User Interface, how input is persisted and validated.
In our example we will use the AbstractTaskConfigurator class instead of the TaskConfigurator interface. This prevents us from having to implement all of its interface members.
Create a new class named "ExampleTaskConfigurator", and populate an "I18nResolver" field as shown below. This resolver is used later to obtain translatable texts.
1 2package helloworld; import com.atlassian.bamboo.collections.ActionParametersMap; import com.atlassian.bamboo.task.AbstractTaskConfigurator; import com.atlassian.bamboo.task.TaskDefinition; import com.atlassian.bamboo.utils.error.ErrorCollection; import com.atlassian.johnson.util.StringUtils; import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport; import com.atlassian.sal.api.message.I18nResolver; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Map; public class ExampleTaskConfigurator extends AbstractTaskConfigurator { private final I18nResolver i18nResolver; public ExampleTaskConfigurator(@ComponentImport I18nResolver i18nResolver) { this.i18nResolver = i18nResolver; } }
The TaskConfiguration needs to be added to the registration of your Task with the plugin system, by modifying the registration that you added to "atlassian-plugin.xml" earlier, like this:
1 2<taskType name="helloworld" class="helloworld.ExampleTask" key="test"> <description>A simple Hello World Task</description> <resource type="freemarker" name="edit" location="editExampleTask.ftl"/> <configuration class="helloworld.ExampleTaskConfigurator"/> </taskType>
In the remainder of this section, we will further populate this class.
To save your configuration you need to override or implement the generateTaskConfigMap method on your TaskConfigurator:
1 2public @NotNull Map<String, String> generateTaskConfigMap(@NotNull final ActionParametersMap params, @Nullable final TaskDefinition previousTaskDefinition) { final Map<String, String> config = super.generateTaskConfigMap(params, previousTaskDefinition); config.put("say", params.getString("say")); return config; }
When the user saves the Task the generateTaskConfigMap
method is called and a ActionParametersMap and TaskDefinition is provided.
The ActionParametersMap should contain all the form parameter keys and values from the input fields of the editExampleTask.ftl
template file. The TaskDefinition is the saved state of the Task within Bamboo. It contains its user description and its map of configuration key/values. One thing to note is that if the Task is being created the TaskDefinition instance here is null but if it is being edited it contains the previous state of the Task before the user changes it.
Remember the field named "say" in the editExampleTask.ftl
template file? Well the value set by the user for this field is available in the ActionParametersMap. In order to persist the value in a new TaskDefinition we must return a new map that contains this value. For simplicity sake we use the same key for this value as was used as the name of the field.
Before generateTaskConfigMap
is called, the ActionParametersMap
is validated to ensure it contains the correct values needed to create the new TaskDefinition
in generateTaskConfigMap
. Validation is simple. If the field does not pass your Tasks validation rules you simply call addError on the ErrorCollection with the name of the field that did not pass validation and a message about why it failed.
1 2public void validate(@NotNull final ActionParametersMap params, @NotNull final ErrorCollection errorCollection) { super.validate(params, errorCollection); final String sayValue = params.getString("say"); if (StringUtils.isEmpty(sayValue)) { errorCollection.addError("say", i18nResolver.getText("helloworld.say.error")); } }
Note that the error message is obtained from the i18n property file. Add the name of the translation to the file "english.properties" that you created earlier:
1 2helloworld.say=Say helloworld.say.error=Please provide a value!
You can set the default configuration of your Task when it is being created by populating the given context
map with the key of the field and the value you want it to contain in the populateContextForCreate
method, as shown below.
1 2@Override public void populateContextForCreate(@NotNull final Map<String, Object> context) { super.populateContextForCreate(context); context.put("say", "Hello, World!"); }
Once your Task has been created the populateContextForEdit
method can be used to show the configured values of the Task. Simply populate the given context
map with the values stored in the map on the TaskDefinition
(These are the values you set to a new map from the ActionParametersMap
in the generateTaskConfigMap
method). Like populateContextForCreate
, the keys of the context map
should match the field names in your templates.
1 2@Override public void populateContextForEdit(@NotNull final Map<String, Object> context, @NotNull final TaskDefinition taskDefinition) { super.populateContextForEdit(context, taskDefinition); context.put("say", taskDefinition.getConfiguration().get("say")); }
To try the Hello World plugin out run the atlas-run command from the root of the project you generated when you started the tutorial. Once Bamboo is up, create a new Plan with a "None" repository type and configure the Job to contain the "Hello World" Task.
Try removing the default value in the "Say" field and saving. You should see a validation error. That's the validation we have defined in the Configurator.
Change the "Say" field value to be "Hello, World!" then save the task then run your plan. Once the plan has completed running you should see the plugin executing and printing "Hello, World!" in the Jobs logs.
Rate this page: