Best Practices How-To's Tips & Tricks

Make It Workflow — Part 3: Streamlining Issue Reporting

In previous installments, we discussed how to restrict issue visibility and prevent unwanted updates. We still haven’t covered what is possibly the most important aspect of the issue life cycle — its birth. In this article, we describe how you, your team, and your customers can create issues that contain the most accurate and detailed information from day one.

blog_4@2x

Workflow-free Mode

As with other issue-related operations, you can set up a basic issue reporting process without workflows. It all starts with the configuration of the custom fields in your projects. For most custom fields, you can choose whether the field can or cannot be empty. So, how does this affect issue reporting?

If the field can be empty, you can report an issue without setting any value in this field. In the default YouTrack project template, fields like Assignee and Fix versions can be empty. If you don’t specify a value for the field, the empty value (like Unassigned or Unscheduled) is used instead. You can set the value or clear it at any time.

If the field is cannot be empty, you must assign a default value in the configuration. This value is automatically assigned to each issue that is reported in the project. Moreover, you cannot clear the value of the field, only replace it with another value. In the default YouTrack project template, fields like Priority and State cannot be empty and have default values of Normal and Submitted, respectively.

By defining whether fields can or cannot be empty and setting default values accordingly, you can effectively set the starting point for new issues in your project.

The Workflow-enhanced Approach

While field configuration options are useful, they have some limitations. There are a number of use cases that are not addressed by empty values and default values:

  • You want to require a value in the Due date field. Fields that store simple values (like string, integer, float, date, or period) cannot store a default value. In new issues, these fields are always empty.
  • You want reporters to set the value in a specific field without suggesting a default. For example, you want to assign each task to a Subsystem, but there isn’t a logical value that you would suggest as a default.
  • You want to set default values dynamically. For example, you always want the Due date to be two weeks from today.
  • You want to provide your customers with a template for reporting issues. The template contains predefined values for custom fields and a default description.
  • As an extension of the previous case, you want to provide your customers with a collection of templates for issues in the same project. Each template varies based on the value that is selected for a custom field value (like Type) or can be applied to a draft using menu actions.

Support for these use cases can be implemented with various workflow rules, some of which are already available as default workflows in YouTrack.

Mandatory fields

The first case is about making simple fields mandatory for new issues. This case is addressed by the issue.fields.required(field, message) method. This method checks if the field is empty. If it is, the current changes are rolled back and a message is shown to the reporter. Check out the ‘Require due dates for submitted issues’ rule from the ‘Due Date’ 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('Require due dates for submitted issues'),
  guard: function(ctx) {
    return ctx.issue.becomesReported;
  },
  action: function(ctx) {
    ctx.issue.fields.required(ctx.DueDate, 
      workflow.i18n('You must set the Due date!'));
  },
  requirements: {
    DueDate: {
      type: entities.Field.dateType,
      name: "Due Date"
    }
  }
});

Note the guard in this rule. This guard ensures that the check is only initiated once, after the reporter has filled in the issue content and attempts to create the issue.

If you want to make it so the value for this field cannot be removed for submitted issues, update the guard as follows:

  guard: function(ctx) {
    return ctx.issue.isReported;
  }

Several of the workflows described in this article use the workflow.i18n(...) method. This method is used to localize default workflows. When you write your own workflows, you always know the language of your YouTrack installation, so you don’t need to use this method. If you want to edit the messages in default workflows, remove this method and replace its usage with a plain string of message text.

The second case listed above is solved with the same kind of rule. Simply take a custom field that can be empty and check whether it is empty or not when a new issue is reported. The issue.fields.required(field, message) method works the same for all types of custom fields.

Dynamic defaults

You can use workflows to calculate the default value for a field automatically. For example, the following rule sets the value for the Due date field to a date that is two weeks from the current date:

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

var WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000;

exports.rule = entities.Issue.onChange({
  title: 'Set Due Date on issue submitting',
  guard: function(ctx) {
    return ctx.issue.becomesReported && !ctx.issue.fields.DueDate;
  },
  action: function(ctx) {
    ctx.issue.fields.DueDate = Date.now() + 2 * WEEK_IN_MS;
  },
  requirements: {
    DueDate: {
      type: entities.Field.dateType,
      name: 'Due Date'
    }
  }
});

Issue templates

Another popular case that is supported by one of the default workflows adds a default description to new issues. Here is how it is implemented in our ‘Default Description’ 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('Insert default description template for external users'),
  guard: function(ctx) {
    var issue = ctx.issue;
    return !issue.isReported && !issue.becomesReported && !issue.description;
  },
  action: function(ctx) {
    ctx.issue.description = 
       workflow.i18n('What steps will reproduce the problem?') +
      "\n1.\n2.\n3.\n\n" +
      workflow.i18n("What is the expected result?") +
      "\n\n" +
      workflow.i18n("What happens instead?") +
      "\n\n" +
      workflow.i18n("Please provide any additional information below.") +
      "\n" +
      workflow.i18n("Attach a screenshot if possible") +
      "\n";
  },
  requirements: {}
});

This workflow rule sets the default description for each new issue. That this rule is triggered as soon as the reporter edits any value in the new issue, for example, typing a summary or changing the project. Due to technical limitations, this rule can’t be applied at the moment when the New Issue page opens.

You can extend this workflow to let reporters choose between different templates without filling in a lot of data manually. For example, you can use different templates for feature requests and bug reports:

  • For bug reports, you require the steps to reproduce (STR) in the description.
  • For features, you require the suggestion and motivation (what and why). You also want to set the value for the Fix versions field to Backlog.

For this case, the easiest approach consists of two parts:

  • Make the Type field empty by default.
  • Force the reporter to select the Type at the very beginning of creating an issue:
var entities = require('@jetbrains/youtrack-scripting-api/entities');

exports.rule = entities.Issue.onChange({
  title: 'Choose Type when new issue is created',
  guard: function(ctx) {
    var issue = ctx.issue;
    return !issue.isReported && !issue.becomesReported &&
      !issue.fields.Type && !issue.isChanged('project');
  },
  action: function(ctx) {
    ctx.issue.fields.required(ctx.Type, 'Please choose issue Type before editing!');
  },
  requirements: {
    Type: {
      type: entities.EnumField.fieldType
    }
  }
});
  • Add a rule which sets the values based on the selection in the Type field:
var entities = require('@jetbrains/youtrack-scripting-api/entities');

exports.rule = entities.Issue.onChange({
  title: 'Set issue template based on Type',
  guard: function(ctx) {
    var issue = ctx.issue;
    return !issue.isReported && !issue.becomesReported && !issue.description &&
      issue.fields.isChanged(ctx.Type) && !issue.fields.oldValue(ctx.Type);
  },
  action: function(ctx) {
    var issue = ctx.issue;

    var typeIs = function(value) {
      return issue.fields.Type.name === value.name;
    };

    if (typeIs(ctx.Type.Bug)) {
      issue.description =
         'What steps will reproduce the problem?' +
         '\n1.\n2.\n3.\n\n' +
         'What is the expected result?\n\n' +
         'What happens instead?\n';
    } else if (typeIs(ctx.Type.Feature)) {
      issue.description =
         'What should be implemented?\n\n' +
         'Why is this functionality required?\n';
      issue.fields.FixVersions.add(ctx.FixVersions.Backlog);
    }
  },
  requirements: {
    Type: {
      type: entities.EnumField.fieldType,
      Bug: {},
      Feature: {}
    },
    FixVersions: {
      type: entities.ProjectVersion.fieldType,
      multi: true,
      name: 'Fix versions',
      Backlog: {}
    }
  }
});

Another way to support this use case is to provide a list of templates in the form of actions. Action rules are a special type of workflow rule. Each action rule is associated with a command and an item in the list of actions that are shown in the Command Dialog drop-down menu. These rules are triggered when the user executes the command or clicks the corresponding item. For details, refer to the YouTrack documentation.

Here’s a sample action rule for the feature request template:

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

exports.rule = entities.Issue.action({
  title: 'Feature template',
  command: 'feature-template',
  guard: function(ctx) {
    var issue = ctx.issue;
    return !issue.isReported && !issue.becomesReported && !issue.description;
  },
  action: function(ctx) {
    var issue = ctx.issue;
    issue.description =
      'What should be implemented?\n\n' +
      'Why is this functionality required?\n';
    issue.fields.Type = ctx.Type.Feature;
    issue.fields.FixVersions.add(ctx.FixVersions.Backlog);
  },
  requirements: {
    Type: {
      type: entities.EnumField.fieldType,
      Feature: {}
    },
    FixVersions: {
      type: entities.ProjectVersion.fieldType,
      multi: true,
      name: 'Fix versions',
      Backlog: {}
    }
  }
});

To make reporters select a template, first check if the description is empty. If so, suggest that they choose a template from the menu.

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

exports.rule = entities.Issue.onChange({
  title: 'Choose a template when new issue is created',
  guard: function(ctx) {
    var issue = ctx.issue;
    return !issue.isReported && !issue.becomesReported && !issue.description;
  },
  action: function(ctx) {
    workflow.check(false, 'Please, choose one of templates in action menu!');
  },
  requirements: {
    Type: {
      type: entities.EnumField.fieldType
    }
  }
});

The field-based approach is better for simple cases like the one we’ve described here. When you have complex templates that are defined by a combination of field values, use the action-based approach.

We hope that this article helps you to understand how you can streamline issue reporting for every member of your team. In the next article, we describe how you can take the reporter out of the equation and completely automate issue creation. Stay tuned!

The guidelines in this series are just the tip of the workflow iceberg. There’s a lot more out there if you’re eager to learn more: