Last updated Feb 19, 2024

Fisheye and Crucible Development : Stage 2: Implement Workflow Condition

Definition

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.

How to start

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
2
public 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:

  • OK - condition passes, review transition can be performed
  • WARN - condition fails, warnings can be skipped and the review transition performed
  • ERROR - conditions fails, review can't be transitioned without fixing validation errors

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
2
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();
}

A proper ValidationResult can be created using static factory methods and consists of:

Configuration

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
2
package 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();
    }
}

Rate this page: