Best Practices How-To's Tips & Tricks

Make It Workflow — Part 2: Preventing Unwanted Updates

The previous article in our Make it Workflow series showed you how to set up complex visibility restrictions for issues. We limited the ability to read issues to specific groups of people and restricted read access for different sets of users for issues in a single project.

blog_3@2x

In this post, we describe how to block unwanted changes to fields in an issue. We’ll continue to support the business case that we described in the previous article. We’re working with an accounting department whose members process payment requests. These users belong to a group of Executors. Payment requests are submitted by other members of the organization as issues in YouTrack. These users are our Requesters group.

Building on the previous business case, let’s assume that you want to limit who can update the values in specific custom fields. Some of the fields store information that is provided by the Requesters. For example, the purpose of the payment. Other fields store information that is entered by the Executors. For example, the transaction ID from the bank. Requesters should not be able to modify the values in these fields.

Workflow-free Mode

In this case, private custom fields come to the rescue. Custom fields in YouTrack have a privacy setting. An administrator can update the properties for any custom field and make it private. The key icon in the list of custom fields indicates that the field is private.

private-fields

For private fields, an extra set of permissions determines whether a user can read or update the field. If the field is private, users must have permission to Read Issue Private Fields and Update Issue Private Fields in the project, respectively. For public fields, users only need to have Read Issue and Update Issue permissions. Reporters only need to have the Create Issue permission to read public fields in the issues that they report themselves.

In the setup from the previous article, Requesters are granted the default Reporter role, which does not contain permission to Read Issue Private Fields or Update Issue Private Fields. As soon as you make a field private, Requesters are no longer able to view this field in any issue — even the issues that they created. Only Viewers and Executors can see and update its value. (For a complete description of these groups and their roles, refer to the previous article).

If your requirements allow it, you might choose to let your Requesters see the values that are stored in private fields but not update them. In this case, add the Read Issue Private Fields permission to the role that is assigned to the Requesters group in your project. This lets Requesters monitor the progress of their requests without letting them change the issue state, priority, or other private fields.

The Workflow-enhanced Approach

As in our previous article, you can solve simple business cases without scripting. However, this solution has a few limitations. Users can either read and/or update all private custom fields in a project or none of them. There is only black and white, no grey.

What happens when your company doesn’t want the accounting team to accept payment requests without manager approval? To support this restriction, you need two custom fields:

  • Authorization status – this field stores two enumerated values: Required (the default value) and Authorized.
  • Authorizer – this field stores a user type. It contains a list of managers who can approve payment requests.

You want to apply a restriction to the Authorization status field so that it can only be changed to Authorized by the user who is selected as the Authorizer. This means that:

  • Authorizer must have permission to read the issue.
  • Authorizer must have permission to update the Authorization status field.
  • No other user should be able to update the value for the Authorization status field.

To support the first and the second points, use the workflow-enhanced approach that was described in the previous article together with the rule that adds the Authorizer to the visibility list. You should also perform the following setup:

  • Create a separate Authorizers group and add all of the managers as members.
  • Grant the same level of access that is available to members of the Requesters group.
  • Set this group as the source for values in the Authorizer field.

The third point requires an additional workflow rule. This rule is triggered when someone tries to set the Authorization status to Authorized. It verifies that the current user is the same person who is set as the Authorizer.

var entities = require('@jetbrains/youtrack-scripting-api/entities');
var workflow = require('@jetbrains/youtrack-scripting-api/workflow');

exports.rule = entities.Issue.onChange({
  title: 'Only Authorizers can update value for Authorization status field',
  guard: function(ctx) {
    return ctx.issue.fields.becomes(ctx.AuthStatus,     
                                    ctx.AuthStatus.Authorized);
  },
  action: function(ctx) {
    var authBy = ctx.issue.fields.AuthBy;
    workflow.check(authBy && authBy.login === ctx.currentUser.login,
                  'Only Authorizers can update this field!');
  },
  requirements: {
    AuthBy: {
      type: entities.User.fieldType,
      name: 'Authorizer'
    },
    AuthStatus: {
      type: entities.EnumField.fieldType,
      name: 'Authorization status',
      Authorized: {}
    }
  }
});

This limitation is very strict. However, you can write the rule differently to make the policy more lenient. The rule that follows lets members of the accounting team choose another value for the Authorizer field, even after the Authorization status field has been set to Authorized. When a new authorizer is selected, the value of the Authorization status field is switched back to Required. This lets you handle cases where, for example, an authorizer leaves the company or is no longer the manager for the person who submitted the payment request.

var entities = require('@jetbrains/youtrack-scripting-api/entities');
var workflow = require('@jetbrains/youtrack-scripting-api/workflow');

exports.rule = entities.Issue.onChange({
  title: 'Only Executors can change Authorizer in authorized requests',
  guard: function(ctx) {
    var fs = ctx.issue.fields;
    return fs.isChanged(ctx.AuthBy) &&
           fs.AuthStatus.name === ctx.AuthStatus.Authorized.name;
  },
  action: function(ctx) {
    workflow.check(ctx.currentUser.isInGroup(ctx.executors.name),
                   'Only Executors can change the Authorizer ' +
                   'after the request has been authorized!');
    ctx.issue.fields.AuthStatus = ctx.AuthStatus.Required;
  },
  requirements: {
    AuthBy: {
      type: entities.User.fieldType,
      name: 'Authorizer'
    },
    AuthStatus: {
      type: entities.EnumField.fieldType,
      name: 'Authorization status',
      Authorized: {},
      Required: {}
    },
    executors: {
      type: entities.UserGroup
    }
  }
});

The previous restrictions determine which users can update different fields. You can also add restrictions that are based on field and value combinations. For example, here’s a rule that won’t let anyone change the value for the State field to Paid unless the Authorization status is Authorized.

var entities = require('@jetbrains/youtrack-scripting-api/entities');
var workflow = require('@jetbrains/youtrack-scripting-api/workflow');

exports.rule = entities.Issue.onChange({
  title: 'Request must be authorized to be marked as paid',
  guard: function(ctx) {
    return ctx.issue.fields.becomes(ctx.State, ctx.State.Paid);
  },
  action: function(ctx) {
    var fs = ctx.issue.fields;
    workflow.check(fs.AuthStatus && 
                   fs.AuthStatus.name === ctx.AuthStatus.Authorized.name,
                   'Only authorized requests can be marked as paid!');
  },
  requirements: {
    AuthStatus: {
      type: entities.EnumField.fieldType,
      name: 'Authorization status',
      Authorized: {},
    },
    State: {
      type: entities.State.fieldType,
      Paid: {}
    }
  }
});

Here’s another variant. When the State has been set to Paid, the value that is stored in the Amount field cannot be modified.

var entities = require('@jetbrains/youtrack-scripting-api/entities');
var workflow = require('@jetbrains/youtrack-scripting-api/workflow');

exports.rule = entities.Issue.onChange({
  title: 'Block changes to Amount for paid requests',
  guard: function(ctx) {
    return ctx.issue.fields.isChanged(ctx.Amount);
  },
  action: function(ctx) {
    var fs = ctx.issue.fields;
    workflow.check(fs.State && fs.State.name !== ctx.State.Paid.name,
                   'You cannot change the Amount for paid requests!');
  },
  requirements: {
    Amount: {
      type: entities.Field.floatType
    },
    State: {
      type: entities.State.fieldType,
      Paid: {}
    }
  }
});

We hope that these examples help you understand the basic concept. You can support a wide variety of update restrictions with the help of workflows. The workflows described in this article are specific to the business case for our accounting team but can be adapted to support any process. Here are a few ways you can extend this scheme:

  • Prohibit anyone but members of the QA team from changing the state for reported bugs from Fixed to Verified.
  • Mark an issue as Fixed only when the Spent time field is not empty.
  • Block users from changing the value for the Assignee field once an issue is resolved.

How and when you apply update restrictions is all up to you and your imagination.

In our next post, we show you how to streamline the issue creation process with workflows. If you’re hungry for more and can’t wait a week, here are a few resources to tide you over: