The plugin created in this tutorial makes use of the workflow-condition modules. The plugin prevents a user from closing a review until at least one reviewer completes the review.
In this tutorial you will:
WorkflowCondition
SPI classThis tutorial teaches you how to:
WorkflowCondition
SPI classFor general information about the plugin development, see Fisheye and Crucible Plugin Guide - Atlassian Developers
We encourage you to work through this tutorial. If you want to skip ahead or check your work when you are done, you can find the plugin source code on Atlassian Bitbucket. Bitbucket serves as a public Git repository containing the tutorial's code. To clone the repository, run:
1 2git clone https://bitbucket.org/atlassian_tutorial/crucible-workflow-condition-tutorial.git
The Workflow Conditions API was introduced to Fisheye/Crucible in version 4.2 as an experimental API and had changed in 4.3 release. From version 4.3, Fisheye/Crucible is considered as final and there are no planned changes towards it anymore.
Follow the steps below to learn how to create your plugin structure and run Fisheye/Crucible.
For more details on the initial setup of the SDK, and the first steps below, see Developing with the Atlassian Plugin SDK.
a) Create your plugin skeleton using the Atlassian Plugin SDK:
1 2$ atlas-create-fecru-plugin ... Define value for groupId: : com.example.ampstutorial Define value for artifactId: : fecrutwitter Define value for version: 1.0-SNAPSHOT: : # just accept the default Define value for package: com.example.ampstutorial: : # again, just press enter for the default
b) (optional) If atlas-create-fecru-plugin creates a plugin skeleton with a different version of Fisheye/Crucible than you want to use, update the properties in pom.xml
Workflow Conditions API was introduced in a final shape in version 4.3.
1 2<properties> <fecru.version>4.3.0-20170119092650</fecru.version> <fecru.data.version>4.3.0-20170119092650</fecru.data.version> </properties>
c) Run Fisheye/Crucible with the skeleton plugin:
1 2$ cd workflowcondition $ mvn fecru:debug
d) Go to http://localhost:3990/fecru/
e) Authenticate with the following credentials: username: admin password:* admin *
In this tutorial you're going to implement the condition which prevents a user from closing a review when none of the reviewers have completed the review.
Implement the com.atlassian.crucible.workflow.WorkflowCondition
SPI class.
1 2@Named public class CompletedReviewerWorkflowCondition implements WorkflowCondition { public ValidationResult validateTransition(WorkflowTransitionContext transitionContext) { return ValidationResult.ok(); } }
The condition in the current state doesn't do anything useful yet. To check for some conditions during the review transition, you must implement the validateTransition
method. This method is called every time a review is being transitioned. It's up to you to verify that it's the transition type the condition should react to.
1 2public ValidationResult validateTransition(WorkflowTransitionContext transitionContext) { if (isCloseReview(transitionContext.getTransitionAction())) { return doValidate(transitionContext.getReviewId()); } return ValidationResult.ok(); } private boolean isCloseReview(String transitionAction) { return ReviewService.Action.Close.getActionString().equals(transitionAction); }
The validateTransition
method should return a ValidationResult
, which can be of one of the following states:
As soon as you defined the programmed condition to react only to closing a review you need to implement the condition check. For the purpose of this tutorial, the check verifies only if there's at least one reviewer who completed the review.
1 2private ValidationResult doValidate(PermId<ReviewData> reviewId) { if (isAtLeastOneReviewerCompleted(reviewId)) { return ValidationResult.ok(); } return ValidationResult.error(CompletedReviewerWorkflowCondition.class.getSimpleName(), new ResultMessage("No one completed the review", "This review can't be closed until at least one reviewer completes it", "This review can't be closed until at least one reviewer completes it")); } private boolean isAtLeastOneReviewerCompleted(PermId<ReviewData> reviewId) { return reviewService.getAllReviewers(reviewId).isEmpty() || !reviewService.getCompletedReviewers(reviewId).isEmpty(); }
A proper ValidationResult
can be created using static factory methods and consists of:
In previous step, you implemented CompletedReviewerWorkflowCondition
, which defines the behaviour of your condition. Now, lis the condition in atlassian-plugin.xml in workflow-condition
module to let Fisheye/Crucible's plugin system know about it.
1 2<workflow-condition key="completed-reviewers-condition" class="com.example.ampstutorial.CompletedReviewerWorkflowCondition"> </workflow-condition
The class is the class of the workflow condition, and the key are unique identifiers required by the plugin system. A single plugin can define multiple workflow-condition
modules. Once the plugin module is configured, a review can't be closed it, until at least one reviewer completes it.
Invoking the transition using REST and JAVA APIs is also prohibited and will fail with an error.
1 2$ curl -u admin:admin -X POST http://localhost:3990/fecru/rest-service/reviews-v1/CR-2/transition.json\?action\=action:closeReview { "reviewId": "CR-2", "message": "Some of workflow validation conditions have failed", "failedConditions": [ { "resultKey": "CompletedReviewerWorkflowCondition", "message": "This review can't be closed until at least one reviewer completes it", "severity":"error" } ] }
CompletedReviewerWorkflowCondition.java
1 2package com.example.ampstutorial; import com.atlassian.crucible.spi.PermId; import com.atlassian.crucible.spi.data.ReviewData; import com.atlassian.crucible.spi.services.ReviewService; import com.atlassian.crucible.workflow.ResultMessage; import com.atlassian.crucible.workflow.ValidationResult; import com.atlassian.crucible.workflow.WorkflowCondition; import com.atlassian.crucible.workflow.WorkflowTransitionContext; import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport; import javax.inject.Inject; import javax.inject.Named; @Named public class CompletedReviewerWorkflowCondition implements WorkflowCondition { private final ReviewService reviewService; @Inject public CompletedReviewerWorkflowCondition(@ComponentImport ReviewService reviewService, @ComponentImport SoyTemplateRenderer renderer) { this.reviewService = reviewService; this.renderer = renderer; } public ValidationResult validateTransition(PermId<ReviewData> reviewId, String transitionAction, @Nullable UserData currentUser) { if (isCloseReview(transitionAction)) { return doValidate(reviewId); } return ValidationResult.ok(); } private boolean isCloseReview(String transitionAction) { return ReviewService.Action.Close.getActionString().equals(transitionAction); } private ValidationResult doValidate(PermId<ReviewData> reviewId) { if (isAtLeastOneReviewerCompleted(reviewId)) { return ValidationResult.ok(); } return ValidationResult.error(CompletedReviewerWorkflowCondition.class.getSimpleName(), new ResultMessage("No one completed the review", "This review can't be closed until at least one reviewer completes it", "This review can't be closed until at least one reviewer completes it")); } private boolean isAtLeastOneReviewerCompleted(PermId<ReviewData> reviewId) { return reviewService.getAllReviewers(reviewId).isEmpty() || !reviewService.getCompletedReviewers(reviewId).isEmpty(); } }
In previous steps, you wrote a workflow condition which presents simple error message to a user but doesn't allow for any user interaction. ResultMessage can also contain actions which allow for such interaction. The starting point for building any kind of action is the com.atlassian.crucible.workflow.ResultAction
interface. Crucible allows for two types of actions:
The URL action represents a simple HTML link. It can be constructed with the buildUrlAction
method. For the purpose of this tutorial, let's include the link to a Users page in a failed condition message.
1 2private ValidationResult doValidate(PermId<ReviewData> reviewId) { if (isAtLeastOneReviewerCompleted(reviewId)) { return ValidationResult.ok(); } return ValidationResult.error(CompletedReviewerWorkflowCondition.class.getSimpleName(), new ResultMessage("No one completed the review", "This review can't be closed until at least one reviewer completes it", "This review can't be closed until at least one reviewer completes it", Lists.newArrayList( ResultAction.buildUrlAction("See users", applicationProperties.getBaseUrl() + "/users") ))); }
As a result of such ValidationResult, Crucible will include the link to a Users page as a part of the failure message.
The Javascript action represents a link and the Javascript handler to that link. To write such action, you'll need the following:
In this tutorial, we'll write an action using the AJAX request which sends a mail reminder to users who didn't complete a review. To achieve that goal, we'll need a REST resource which accepts HTTP requests with the review id, and sends the mail messages to every reviewer who didn't complete their review. Writing such resource isn't a part of this tutorial, so we'll use a ready component, which is accessible in the tutorial repository. To start writing the Javascript action, we'll create Javascript with the AMD module.
1 2define('FECRU/workflow-condition-tutorial/remind-handler', ['require'], function (require) { 'use strict'; var $ = require('jquery'); var fecru = require('global-ns/fecru'); var onClickFunc = function (workflowConditionContext) { var url = fecru.pageContext + '/rest/workflowcondition-tutorial/latest/' + workflowConditionContext.reviewId; $.ajax({ type: "get", url: url, dataType: "json", success: function() { }, error: function() { } }); }; return { onClick: onClickFunc }; });
Next, we'll create Web Resource which serves that file:
1 2<web-resource key="remind-handler" name="remind-handler JS action"> <resource type="download" name="remind-handler.js" location="/js/remind-handler.js"/> </web-resource>
Once front-end side is configured, your plugin needs to notify the Crucible core that here's an additional Javascript action.
1 2private ValidationResult doValidate(PermId<ReviewData> reviewId) { if (isAtLeastOneReviewerCompleted(reviewId)) { return ValidationResult.ok(); } return ValidationResult.error(CompletedReviewerWorkflowCondition.class.getSimpleName(), new ResultMessage("No one completed the review", "This review can't be closed until at least one reviewer completes it", "This review can't be closed until at least one reviewer completes it", Lists.newArrayList( ResultAction.buildUrlAction("See users", applicationProperties.getBaseUrl() + "/users"), ResultAction.buildJavaScriptAction("Send reminders", "com.example.ampstutorial.workflowcondition:remind-handler", "FECRU/workflow-condition-tutorial/remind-handler") ))); }
Given code will result in the following dialog:
Workflow Condition Context
Crucible passes Workflow Condition Context to Javascript on click handler in order to extend possibilities of plugin developers. Context object exports the following functions:
reviewId
→ returns the review id
hideDialog
→ hides transition dialog
resolveMe
→ resolves current condition on the front-end side, which results in removing it from the dialog. Note: This action will affect only the front-end side. If the problem which triggered the workflow condition is not fixed, the transition will fail again
showError
→ shows error banner in the current dialog, accepts error message
hideError
→ hides error banner
In the next step, we'll extend remind-handler to resolve the condition when AJAX call succeeds and show the error banner in case of an error. To do that, we'll use the context object as a handler.
1 2define('FECRU/workflow-condition-tutorial/remind-handler', ['require'], function (require) { 'use strict'; var $ = require('jquery'); var fecru = require('global-ns/fecru'); var onClickFunc = function (workflowConditionContext) { var url = fecru.pageContext + '/rest/workflowcondition-tutorial/latest/' + workflowConditionContext.reviewId; $.ajax({ type: "get", url: url, dataType: "json", success: function() { workflowConditionContext.resolveMe(); }, error: function() { workflowConditionContext.showError(); } }); }; return { onClick: onClickFunc }; });
With such handler, if the request succeeds, Crucible will remove given message from the list. In this particular case there was only one error message so the user is allowed to proceed.
Crucible core can't validate if the problem which triggered the condition failure was indeed solved. Because of that it'll just remove the item from the list. However, once user tries to proceed with the transition, Crucible will validate it once again. If the problem isn't solved (Workflow Condition fails again), the error message will simply reappear.
workflowConditionContext.showError();
shows the following error banner:
Rate this page: