YouTrack InCloud 2019.3 Help

State-machine Rules

A state-machine rule regulates the transitions from one value to another for a custom field. YouTrack provides templates for two types of state-machine rules.

  • A basic state-machine rule that regulates the transitions from one value to another for a custom field. To learn more about this type of state-machine, see Basic State-machines.

  • A state-machine rule that imposes separate sets of state transitions based on the current value in a specified field. To learn how to script this type of state-machine, see State-machine Rules State-machines per Issue Type.

Both of these state-machine types are built using a similar collection of parameters.

You can apply a state-machine rule to any custom field. However, the most common use case for a state-machine rule is to regulate transitions between values for the State field or another custom field that stores a state type.

When a state-machine rule is applied to a project, the options that are shown in the drop-down list for the field are restricted to the transitions that are defined in the state-machine rule for the current state. Custom fields that are regulated by a state-machine rule are marked with a Workflow-driven field icon. The action that is defined for each value in the state-machine rule is displayed in the drop-down list. The actual values are shown to the right. The following image illustrates how the list of allowed transitions for the State field changes based on the current value.

Statemachine field values

Basic State-machines

A basic state-machine regulates the transitions from one value to another for a custom field for all issues in any project that uses the workflow. For each transition, you can add one or more actions. Additional properties for each transition determine how these actions are applied.

  • Transitions that use onEnter and onExit properties perform an action when the value for the field is set. These actions are executed by the user who changes the value of the field.

  • Transitions that use the after property perform an action after the specified time frame. These actions are executed by the workflow user account.

This example that follows shows a simplified state-machine rule. This rule defines the transitions that are allowed for the State field.

The following transitions are defined in this rule:

  • From the initial Open state, the value can change to In Progress. An additional transition with the name reminder includes an after property that reminds the project owner if an issue remains in the Open state for more than two days.

  • When the issue transitions from Open to In Progress, the onEnter property for the In progress state automatically assigns the issue to the user who changes the value of the State field.

  • From In progress, the value can change to Fixed or Open. When an issue transitions from In Progress back to Open an automation for this transition asks the user to unassign the issue, otherwise the transition is blocked.

  • The final state is Fixed. There are no additional transitions that are defined for this value. Once this value is set, it cannot be changed.

The following diagram shows an abstract representation of this rule.

A diagram that illustrates the supported transitions between values for the State field.

The workflow rule that supports this state-machine is written as follows:

var entities = require('@jetbrains/youtrack-scripting-api/entities'); var workflow = require('@jetbrains/youtrack-scripting-api/workflow'); exports.TWO_DAYS = 2 * 24 * 60 * 60 * 1000; exports.rule = entities.Issue.stateMachine({ title: 'Basic state-machine', fieldName: 'State', states: { Open: { initial: true, transitions: { start: { targetState: 'In progress' }, reminder: { targetState: 'Open', after: exports.TWO_DAYS, action: function(ctx) { var issue = ctx.issue; issue.project.leader.notify('Reminder', 'Check out an issue ' + issue.id); } } } }, 'In progress': { onEnter: function(ctx) { ctx.issue.fields.Assignee = ctx.currentUser; }, onExit: function(ctx) { }, transitions: { fix: { targetState: 'Fixed' }, reopen: { guard: function(ctx) { if (ctx.issue.fields.Assignee) { workflow.message('You cannot reopen an issue unless you unassign it first'); return false; } return true; }, targetState: 'Open' } } }, Fixed: { transitions: {} } }, requirements: { Assignee: { type: entities.User.fieldType } } });

The components that define this state-machine rule are as follows:

  • The require statements reference the entities module and the workflow module in the workflow API.

  • The exports.TWO_DAYS property sets a local variable that is used to send the reminder for issues that have the Open status. This variable is assigned a value that equals 2 days in milliseconds.

  • The exports.rule property uses the Issue.stateMachine method to export the script that follows the declaration as a state-machine rule.

  • The body of the rule itself contains definitions for the following properties:

    Property

    Description

    title

    An optional human-readable title.

    fieldName

    The name of the custom field that is managed by the state-machine.

    states

    The list of field values and definitions for the transitions between them. Values that contain spaces and special characters are set in single quotes.

    requirements

    The list of entities that are required for the rule to execute without errors. This property ensures that rules can be attached to projects safely.

    In this example, we only require that there is an Assignee field that stores a user type in the projects to which the rule is attached. If this field is absent, an error is shown in the Workflows list. The rule cannot be enabled until the required field is attached.

    For a state-machine rule, you do not need to add the field that is managed by the state-machine to the requirements. This field and its values are derived from the states property.

  • Each of the values that are defined in the states property contain definitions for the following additional properties:

    Property

    Description

    initial

    A Boolean property that determines which of the values in the list is set when an issue is created. Exactly one value must set this property to true. For other values that are not set as the initial value, this property is optional and can be omitted.

    onEnter

    An optional function declaration that is called when the corresponding value is assigned to an issue. These functions behave similar to actions in other types of rules.

    onExit

    An optional function declaration that is called when the field value is changed from the current value to another value. These functions behave similar to actions in other types of rules.

    transitions

    The list of possible target values that can be set for each value in the list.

    In this example, we only require that there is an Assignee field that stores a user type in the projects to which the rule is attached. If this field is absent, an error is shown in the Workflows list. The rule cannot be enabled until the required field is attached.

    For a state-machine rule, you do not need to list the values that are used by fields that are managed by the state-machine in the requirements. This field and its values are derived from the states property.

  • Each of the values that are defined in the transitions property contain a name property. The name is used to set this value in a command and is also shown in the list of values for a custom field. Each transition name contains definitions for the following additional properties:

    Property

    Description

    targetState

    The actual name of the value that is set when the command specified in the name property is applied.

    after

    An optional property that sets an interval for performing an action. The action itself is specified in the action property.

    action

    An optional property for any transition that behaves similar to actions in other types of rules..

    guard

    An optional condition that determines when the transition is allowed. If the guard condition is not met, the value for the custom field cannot change to the value that is defined for this transition.

    In this example, for an issue to transition from In progress back to an Open state, the issue must be unassigned. The guard contains a function that uses the message method from the workflow module to provide feedback to the user who attempts to reopen an issue without unassigning it first.

State-machines per Issue Type

A state-machine per issue type adds an additional dimension to a basic state-machine. Instead of regulating a single set of transitions for values in a custom field, the rule provides separate state-machines for each of the possible values in a separate field.

Just like you can apply a state-machine rule to any custom field, not just the default State field, you can make type-specific transitions depend upon the value in any enumerated field, not just the default Type field. The most common use case is to regulate transitions between values for the State field based on the current value for the issue Type.

A state-machine per issue type is written similarly to a basic state-machine, with the following changes:

  • The fieldName property is replaced with an alias, stateFieldName in the template for a new state-machine rule. The value for this property determines which field is managed by the state-machine rule.

  • A separate property for the typeFieldName lets you specify the field that dictates which state-machine applies to the state field. The presence of this property is the main difference between a basic state-machine (which doesn't use it) and a state-machine per issue type.

  • The states property is replaced with an alias, defaultMachine. This property describes the default set of transitions for values in the managed field. This state-machine applies to issues that are not assigned a value for which an alternative state-machine has been defined.

  • The alternativeMachines property stores a collection of state-machine transitions for designated values in the custom field that is set for the typeFieldName property.

When you specify a value for the typeFieldName, configure the defaultMachine, and define at least one state-machine for a value in the alternativeMachines section of the rule, you end up with a state-machine per issue type. The field that you specify for the typeFieldName property functions as a switch between the default state-machine and the collection of alternative state-machines.

The following example shows how to build a state-machine per issue type. Each of the tabs below describes the state-machine that corresponds to one of the possible values for the Type field.

A diagram that shows the default transitions for the State field in a state-machine per issue type.

First, we take the logic that is applied in the basic state-machine rule above and apply it as a default to any type of issue in a project. As a result, the values for the State field in issues that are assigned the Task type are regulated according to the model shown above. This state-machine also applies to issues that are assigned any type that isn't assigned an alternative state-machine.

A diagram that shows the transitions for the State field in issues that are assigned the Bug type.

Next, we'll add an alternative state-machine just for issues that are assigned the Bug type.

  • For this issue type, we make it possible to assign the value Can't Reproduce to the State field.

  • We'll make it a little more flexible than the default state-machine, allowing a transition from Fixed back to Open. This means that we can reopen issues when a regression occurs instead of having to report the same problem more than once.

  • We'll also make it possible to transition from Can't Reproduce back to Open.

We add some automation to specific states and transitions.

  • When an issue moves from Open to In Progress, the issue is assigned to the user who changes the issue state. This is the same definition for the onEnter property that is used for the In progress state in the basic state-machine.

  • For the transition from In Progress to Can't Reproduce we add an action. This displays an alert that asks the user to describe what steps they took to try to reproduce the bug.

  • From Fixed the issue can only transition back to the Open state. As a result, we can add an automation for the onExit property to the definition for the Fixed field. This automation sends a notification to the assignee to inform them that the issue was reopened.

A diagram that shows the transitions for the State field in issues that are classified as feature requests

We'll add one more alternative state-machine and apply this to issues that are assigned the Feature type.

  • For these types of issues, we add the possibility to move an issue directly from an Open state to Rejected. If anybody suggests a feature that the team decides doesn't fit with their vision of the product, they can throw it out.

  • Just to be open to the possibility for a change in direction, we'll allow issues to transition from Rejected back to Open.

  • All of the other transitions are similar to the state-machine for bugs.

To keep it simple, we haven't added any automations to this state-machine. That doesn't mean that there aren't any opportunities to automate or enhance the process for this issue type. Possible enhancements include:

  • Using an after event to the set of transitions for the Open state to tag issues that remain in this state for over six months. You can then review all of the tagged issues in your next backlog grooming session.

  • Adding an onEnter event to the In Progress state, prompting the user to assign the feature to a specific milestone or pipeline in the roadmap.

  • Adding an action event to the transition from Rejected to Open, prompting the user to justify why they would reopen the feature request for future consideration.

We can take the sample code for the basic state-machine rule and quickly convert it into a state-machine per issue type with just a few changes.

  • While it's not required, we have replaced the name of the fieldName property with its alias, stateFieldName. As with the sample for the basic state-machine, we're managing transitions between values for a field called State. We don't change the value for this property.

  • We've added the property typeFieldName and specified 'Type' as its value. This means that the Type field can now be used as a switch to determine whether to apply the default state-machine or an alternative state-machine that is assigned to one of the possible values for this field.

  • We've applied another change just for cosmetic purposes, replacing the name of the states property with its alias, defaultMachine. We don't change the value for this property. Instead, we reuse the basic state-machine rule, which will apply to any issue that isn't assigned one of the types that are defined in the alternativeMachines section of the rule.

  • We've added a new section for alternativeMachines. Here, we specify state-machines for issues that are assigned the values Bug and Feature in the Type custom field.

Here's the modified state-machine rule:

var entities = require('@jetbrains/youtrack-scripting-api/entities'); var workflow = require('@jetbrains/youtrack-scripting-api/workflow'); exports.TWO_DAYS = 2 * 24 * 60 * 60 * 1000; exports.ONE_WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000; exports.rule = entities.Issue.stateMachine({ title: 'State-machine per Issue Type', stateFieldName: 'State', typeFieldName: 'Type', defaultMachine: { Open: { initial: true, transitions: { start: { targetState: 'In progress' }, reminder: { targetState: 'Open', after: exports.TWO_DAYS, action: function(ctx) { var issue = ctx.issue; issue.project.leader.notify('Reminder', 'Check out an issue ' + issue.id); } } } }, 'In progress': { onEnter: function(ctx) { ctx.issue.fields.Assignee = ctx.currentUser; }, onExit: function(ctx) { }, transitions: { fix: { targetState: 'Fixed' }, reopen: { guard: function(ctx) { if (ctx.issue.fields.Assignee) { workflow.message('You cannot reopen an issue unless you unassign it first'); return false; } return true; }, targetState: 'Open' } } }, Fixed: { transitions: {} } }, alternativeMachines: { Bug: { Open: { initial: true, transitions: { 'in progress': { targetState: 'In Progress', action: function(ctx) { if (!ctx.issue.fields.Assignee) { ctx.issue.fields.Assignee = ctx.currentUser; } } }, 'can\'t reproduce': { targetState: 'Can\'t Reproduce' } } }, 'In Progress': { transitions: { 'reopen': { targetState: 'Open' }, 'fix': { targetState: 'Fixed' }, 'can\'t reproduce': { targetState: 'Can\'t Reproduce' }, 'remind': { after: exports.ONE_WEEK_IN_MS, targetState: 'In Progress', action: function(ctx) { if (ctx.issue.fields.Assignee) { var subj = '[YouTrack] Issue is in progress'; var body = 'Issue ' + ctx.issue.id + ' is in progress for a week.'; ctx.issue.fields.Assignee.notify(subj, body); } } } } }, Fixed: { transitions: { 'reopen': { targetState: 'Open' } }, onExit: function(ctx) { if (ctx.issue.fields.Assignee) { var subj = '[YouTrack] Issue is reopened'; var body = 'Issue ' + ctx.issue.id + ' requires your attention.'; ctx.issue.fields.Assignee.notify(subj, body); } } }, 'Can\'t Reproduce': { onEnter: function( /*ctx*/ ) { workflow.message('Please leave a comment explaining what you tried to do.'); }, transitions: { 'reopen': { targetState: 'Open' } } } }, Feature: { Open: { initial: true, transitions: { 'in progress': { targetState: 'In Progress', }, reject: { targetState: 'Rejected' } } }, 'In Progress': { transitions: { reopen: { targetState: 'Open' }, fix: { targetState: 'Fixed' } } }, Fixed: { transitions: { reopen: { targetState: 'Open' } } }, Rejected: { transitions: { reopen: { targetState: 'Open' } } } } }, requirements: { Assignee: { type: entities.User.fieldType } } });

The components that define this state-machine rule are as follows:

  • The require statements reference the entities and workflow modules in the workflow API.

  • The exports.TWO_DAYS property sets a local variable that is used to send the reminder for issues that have the Open status. This variable is assigned a value that equals 2 days in milliseconds.

  • The exports.ONE_WEEK_IN_MS property sets a local variable that is used to send the reminder for bugs that have been In Progress for more than one week. This variable is assigned a value that equals one week in milliseconds.

  • The exports.rule property uses the Issue.stateMachine method to export the script that follows the declaration as a state-machine rule.

  • The body of the rule itself contains definitions for the following properties:

    Property

    Description

    title

    An optional human-readable title.

    stateFieldName

    The name of the custom field that is managed by the state-machine.

    typeFieldName

    The name of the custom field that regulates which state-machine is applied to the managed field.

    defaultMachine

    The list of field values and definitions for the transitions between them for the default state-machine. The default state-machine applies to any issue that isn't assigned one of the types that are defined in the alternativeMachines section of the rule.

    Values that contain spaces and special characters are set in single quotes.

    alternativeMachines

    Stores a collection of state-machine transitions for designated values in the custom field that is set for the typeFieldName property.

    requirements

    The list of entities that are required for the rule to execute without errors. This property ensures that rules can be attached to projects safely.

    In this example, we only require that there is an Assignee field that stores a user type in the projects to which the rule is attached. If this field is absent, an error is shown in the Workflows list. The rule cannot be enabled until the required field is attached.

    For state-machines by issue type, you don't need to add any of the fields that are managed by the state-machine to the requirements. This field and its values are derived from the defaultMachine property and the collection of fields that are defined for each value in the alternativeMachines section of the rule. The field that is set for the typeFieldName property is also implicitly added to the requirements for the workflow rule.

  • Each of the values that are defined in the defaultMachine property and each of the fields that are defined for each value in the alternativeMachines section of the rule contain definitions for the following additional properties:

    Property

    Description

    initial

    A Boolean property that determines which of the values in the list is set when an issue is created. Exactly one value must set this property to true. For other values that are not set as the initial value, this property is optional and can be omitted.

    onEnter

    An optional function declaration that is called when the corresponding value is assigned to an issue. These functions behave similar to actions in other types of rules.

    onExit

    An optional function declaration that is called when the field value is changed from the current value to another value. These functions behave similar to actions in other types of rules.

    transitions

    The list of possible target values that can be set for each value in the list.

    In this example, we only require that there is an Assignee field that stores a user type in the projects to which the rule is attached. If this field is absent, an error is shown in the Workflows list. The rule cannot be enabled until the required field is attached.

    For a state-machine rule, you do not need to list the values that are used by fields that are managed by the state-machine in the requirements. This field and its values are derived from the states property.

  • Each of the values that are defined in the transitions property contain a name property. The name is used to set this value in a command and is also shown in the list of values for a custom field. Each transition name contains definitions for the following additional properties:

    Property

    Description

    targetState

    The actual name of the value that is set when the command specified in the name property is applied.

    after

    An optional property that sets an interval for performing an action. The action itself is specified in the action property.

    action

    An optional property for any transition that behaves similar to actions in other types of rules..

    guard

    An optional condition that determines when the transition is allowed. If the guard condition is not met, the value for the custom field cannot change to the value that is defined for this transition.

    In this example, for an issue to transition from In progress back to an Open state, the issue must be unassigned. The guard contains a function that uses the message method from the workflow module to provide feedback to the user who attempts to reopen an issue without unassigning it first.

  • The alternativeMachines property stores a set of values from the field that is referenced in the typeFieldName property. Each of the values that are assigned dedicated state-machines are simply referenced by name.

Last modified: 12 December 2019