Best Practices How-To's Tips & Tricks

Make It Workflow — Part 5: Propagating Values Between Issues

Over the last month, we have investigated various aspects of the issue lifecycle. We described how to set up granular read and write access for existing issues, how to streamline issue creation, and even how to generate issues automatically. If you missed any of our previous installments, you can find all of these articles by filtering for posts with the tag “makeitworkflow“.

The last thing we want to cover in this part of our Make It Workflow series is how to automate issue updates. As always, the purpose of this automation is to make YouTrack do the dirty work for you.

blog_6

First, we’ll have a quick look at a few workflows that update issues based on changes that are applied to the issue itself. Then we’ll show you how changes in one issue can automatically apply updates to related issues.

Apply Changes in Response to Manual Updates

You may have never realized that part of the functionality that you use on a daily basis in YouTrack is implemented with workflows. One of the examples that we discussed in the previous blog post is the ‘Clone Issue’ workflow. This workflow adds the custom clone command to your YouTrack toolkit.

Another example is the ‘Subsystem Assignee’ workflow, which is attached automatically to every new project. This workflow contains one rule – ‘Set subsystem owner as assignee for unassigned issues’ – which… well…  basically does what the title says. The concept is relatively straightforward. Many organizational processes imply that each subsystem in your product has a responsible person or “owner”. This person is the default Assignee for each new issue that is assigned to this Subsystem. When you designate an owner for each of your subsystems, every new issue is assigned accordingly.
Here’s the corresponding workflow rule:

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

exports.rule = entities.Issue.onChange({
  title: workflow.i18n('Set subsystem owner as assignee for unassigned issues'),
  guard: function(ctx) {
    return !ctx.issue.fields.Assignee && ctx.issue.fields.Subsystem;
  },
  action: function(ctx) {
    var issue = ctx.issue;
    if ((issue.isReported && (issue.fields.isChanged(ctx.Subsystem) ||
         issue.isChanged('project'))) || issue.becomesReported) {
      issue.fields.Assignee = issue.fields.Subsystem.owner;
    }
  },
  requirements: {
    Assignee: {
      type: entities.User.fieldType
    },
    Subsystem: {
      type: entities.OwnedField.fieldType
    }
  }
});

This type of update is useful in a wide range of cases. Here are just a few examples:

  1. Set the current user as Assignee when the issue state changes from Open to In Progress
  2. Change the issue State from Wait for reply to Open when a new comment is added.
  3. Mark an issue as Fixed when the Fix versions are set.
  4. Clear the Fix versions field when the issue state changes to Duplicate or Incomplete.

Apply Changes on a Set Schedule

Let’s say that you want to improve your productivity and support the Pomodoro technique in your project (to learn more about the Pomodoro technique, refer to the Cirillo Company’s website). There are plenty of ways to do it: set up a physical timer, use a mobile app, enable a browser extension… or you can simply enable the ‘Pomodoro Timer’ workflow, which is distributed with YouTrack.

This workflow is supported by a set of additional custom fields, including Pomodoro state and Pomodoro countdown. As soon as the Pomodoro state changes to Timer’s running, the Pomodoro countdown is set to 25. A scheduled rule immediately starts decreasing the value in the countdown field by 1 every minute:

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

exports.rule = entities.Issue.onSchedule({
  title: workflow.i18n('Enable Pomodoro countdown'),
  search: 'has: {Pomodoro countdown} AND Pomodoro countdown: -0 AND (Pomodoro state: {Timer’s running} OR Pomodoro state: {On a break})',
  cron: '0 * * * * ?',
  action: function(ctx) {
    var issueFields = ctx.issue.fields;
    issueFields['Pomodoro countdown'] -= 1;
  },
  requirements: {
    'PomodoroCountdown': {
      name: 'Pomodoro countdown',
      type: entities.Field.integerType
    },
    'PomodoroState': {
      name: 'Pomodoro state',
      type: entities.EnumField.fieldType
    }
  }
});

You can find more details about this workflow in the YouTrack documentation

Another example that illustrates this type of automatic update is described in our Workflow Tutorial.

Update Issues in Response to Changes in Related Issues

The next two cases demonstrate how to update issues in response to changes in other issues. We’ll look at issues that are linked with a parent-subtask relationship. However, you can apply this approach to issues with other link types, depending on your process.

The first direction is to apply updates with a top-down approach: when updates are applied to a parent issue, the subtasks are updated accordingly. We call this behavior ‘inheritance’ and have several default workflows in YouTrack that implement this behavior for various fields: ‘Subtask Inherit Assignee’, ‘Subtask Inherit Fix Versions’ and ‘Subtask Inherit Subsystem’. Let’s take a look at the corresponding rule for the Subsystem field:

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

exports.rule = entities.Issue.onChange({
  title: workflow.i18n('Copy subsystem from parent task when issue is linked as a subtask'),
  guard: function(ctx) {
    return ctx.issue.links['parent for'].added.isNotEmpty() &&
      ctx.issue.fields.Subsystem;
  },
  action: function(ctx) {
    var issue = ctx.issue;

    var safeSetSubsystem = function(subtask) {
      if (subtask.project && !subtask.project.isArchived) {
        if (subtask.project.key === issue.project.key ||
            subtask.project.findFieldByName(ctx.Subsystem.name)) {
          if (!subtask.fields.Subsystem) {
            var value = subtask.project.findFieldByName(ctx.Subsystem.name)
              .findValueByName(issue.fields.Subsystem.name);
            if (value) {
              subtask.fields.Subsystem = value;
            }
          }
        }
      }
    };

    issue.links['parent for'].added.forEach(safeSetSubsystem);
  },
  requirements: {
    Subsystem: {
      type: entities.EnumField.fieldType
    },
    SubtaskOf: {
      type: entities.IssueLinkPrototype,
      name: 'Subtask',
      outward: 'parent for',
      inward: 'subtask of'
    }
  }
});

You may notice that this rule is pretty verbose. This verbosity guarantees that the rule won’t fail with an error when one of the subtasks belongs to a project that doesn’t use a Subsystem field. We recommend that you follow this approach every time, as the rule can potentially refer to issues in projects other than the project to which this rule is attached.

The second direction is from the bottom up. This approach is used when subtasks represent the steps for completing a larger task. When all of the subtasks reach a specific state, this means that the parent issue has reached this state as well. The most basic example is to close the parent issue as fixed when all subtasks are fixed. This rule is one of the modules in the default ‘Subtasks’ workflow:

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

exports.rule = entities.Issue.onChange({
  title: workflow.i18n('Fix parent when all subtasks are resolved'),
  guard: function(ctx) {
    return ctx.issue.isReported && ctx.issue.becomesResolved;
  },
  action: function(ctx) {
    var processParent = function(issue) {
      if (issue.links['subtask of'].isEmpty()) {
        return;
      }
      var parent = issue.links['subtask of'].first();
      if (parent && parent.project && !parent.project.isArchived &&
        parent.isReported && !parent.isResolved) {
        var unresolvedSubtask = parent.links['parent for'].find(function(subtask) {
          return subtask.isReported && !subtask.fields.State.isResolved;
        });
        if (!unresolvedSubtask) {
          var field = parent.project.findFieldByName(ctx.State.name);
          if (field) {
            var value = field.findValueByName(ctx.State.Fixed.name);
            if (value) {
              parent.State = value;
              workflow.message(workflow.i18n('Automatically set {0} as Done', parent.id));
              return parent;
            }
          }
        }
      }
    };

    var issue = ctx.issue;
    while (issue) {
      issue = processParent(issue);
    }
  },
  requirements: {
    State: {
      type: entities.State.fieldType,
      Fixed: {}
    }
  }
});

Depending on your process, this ‘reaching-the-target-state’ condition can be very different. For example, you can have a field Documentation provided with the values No(default) and Yes When each subtask is assigned Yesto this field, this means that the entire parent issue is documented.

We hope that your workflow toolkit is now stocked with a range of gizmos that support your issue management processes — even the most tricky ones. This is our last article in the Make It Workflow series for 2017. After the holidays, we’ll return with more specific use cases, including:

  • Extending the capabilities of the time-tracking feature
  • Enhancing helpdesk support
  • Sending newsletters with YouTrack

If you have any ideas about what you would like to read about in this series, you are very welcome to post a comment here or share your thoughts in our YouTrack Workflow Community in Slack.

We wish you a Merry Christmas and a Happy New Year!