YouTrack Standalone 2017.2 Help

JavaScript Workflow Quick Start Guide

This Quick Start Guide explains all of the basic concepts you need to start working with workflows in JavaScript.

  • If you want to skip the warm-up, you can dive right into the API.

  • If you have never worked with workflows in YouTrack, read the Workflows for more background information.

Workflows let you customize and automate the lifecycle of issues in your project. With workflows, you can easily notify teams about events, enforce policies, execute periodic tasks, and support business processes.

The previous implementation of this feature used a domain-specific language and an external editor. The new (experimental) version is based on JavaScript and is supported by an in-browser editor.

Part 1. How Do I Write My First Workflow?

To give our new workflow API and in-browser editor a try, you have to enable the New Workflow Editor feature for your instance. You can choose to enable this feature for a specific group of users, or select the All Users group and open the feature for general use. For instructions, see Enable Experimental Features.

Once the feature is enabled in YouTrack, you can create and edit your workflows without leaving YouTrack.

To create a workflow and add your first rule:

  1. From the Administration menu, select Workflows.

  2. Click the Create workflow button.
    • The New Workflow dialog opens.

    new workflow dialog js
  3. In the New Workflow dialog, enter a name and an optional title. The workflow name follows the standard naming conventions for npm packages. For more information, see https://docs.npmjs.com/files/package.json#name.

    For this guide, we used subsystem-assignee as the name.

  4. Click the Save button.
    • The new workflow is added to the list.

  5. Select the new workflow from the list. If the sidebar is hidden, click the Show Details button.
  6. In the sidebar, click the + icon.
    • The New rule dialog opens

  7. In the Name input field, enter navigator, then click the Save button.
    • The rule is added to the workflow.

    • The name of the rule is displayed as a link.

  8. Click the link to open the rule in the workflow editor.
    workflow open rule
    • Now, you can add rules to the workflow using our templates.

  9. Locate and expand the subsystem-assignee workflow in the sidebar to the left of the editor. If you don't see the sidebar, click the control in the upper-left corner of the editor to expand it.

    workflow sidebar

  10. Click the + icon next to the name of the workflow, then select On-change rule.
    • The New rule dialog opens.

  11. In the New rule dialog, enter update-assignee-from-subsystem, then click the Save button.
    • The new rule is added to the workflow.

    • The name of the rule is displayed as a link.

  12. Click the name of the rule.
    • The rule is displayed in the workflow editor.

    workflow delete rule
  13. Click the trash icon next to the navigator rule and delete it. You only needed it to access the Workflows page for the first time. To access this page again, select a workflow from the list that has one or more rules that were written in JavaScript and click the name of any rule.

Now you're ready to create your first simple rule. The best way is to go from one TODO comment in a template to the next, updating the corresponding code and removing unnecessary comments. The rule in this example changes the value of the Assignee field to the subsystem owner when the value of the Subsystem field is set. If the selected value for the Subsystem field does not have an owner, the Assignee field is left unchanged.

To speed things up, click the Copy control in the upper-right corner of the code block below. You can paste the code directly into the workflow editor - the rule is just a plain script in JavaScript.

// Each script starts with a list of `require` statements. The following line // means that everything that is exported in the `entities` module in our core API // is accessible here with the `entities` variable. var entities = require('@jetbrains/youtrack-scripting-api/entities'); // Each script may export exactly one rule to the `export.rule` property. // This is an on-change rule. This type of rule is triggered when a change // is applied to an issue. exports.rule = entities.Issue.onChange({ // A rule may have a human-readable title, which is optional. title: 'Change Assignee when Subsystem is changed', // `action` defines what exactly should be done in response to an issue change. // It is a function that accepts a context as an argument. // The context contains an issue that is changed, entities, defined as requirements, // and some other objects (see further sections for details). action: function(ctx) { var issueFields = ctx.issue.fields; // Here we check to see if the Subsystem field was set to a non-null value // that has a non-null owner. If so, we set the value of the Assignee field // to the owner of the selected subsystem. if (issueFields.isChanged(ctx.Subsystem) && issueFields.Subsystem && issueFields.Subsystem.owner) { issueFields.Assignee = issueFields.Subsystem.owner; } }, // For this rule to be safe, we ensure that both Subsystem and Assignee fields // with the correct types are attached to the same project we attach our rule to. // If either fields are absent, an error is shown in the Workflows list. // The rule cannot be activated until the required fields are attached. requirements: { Subsystem: { type: entities.OwnedField.fieldType }, Assignee: { type: entities.User.fieldType } } });

Click the Save button to store your changes. Now that you have a valid workflow rule stored in YouTrack, you have a few options:

  • To apply this rule, you need to attach it to one or more projects. For more information, see Attach Workflows to Projects.

  • You can export this rule for editing in an IDE that supports JavaScript, like WebStorm. The export option also helps you write and test workflows in a test environment before you import them to your production server. You can also import scripts that were created in an external editor. For more information, see Import and Export Workflows.

Part 2. What Types of Rules Can I Write?

As with the previous implementation of the workflows feature, we have different types of rules. The rule type defines the general conditions that cause the rule to be executed.

Each rule has an optional title property and a requirements property. For a detailed description of the requirements property, see Requirements.

On-change Rules

The previous section shows you an example of an on-change rule. These rules were formerly called stateless rules. For a detailed description, see Stateless Rules.

On-schedule Rules

On-schedule rules define operations that are performed on a set schedule. They are described at Scheduled Rules.

var entities = require('@jetbrains/youtrack-scripting-api/entities'); exports.rule = entities.Issue.onSchedule({ // Optional human-readable title. title: 'Notify an Assignee when Due Date is expired', // Search defines which issues are processed by this rule. // It may be a string, representing a usual YT search expression // (see https://www.jetbrains.com/help/youtrack/standalone/2017.2/Search-and-Command-Attributes.html) // or a function that recalculates a search string every time a rule is triggered. // When using a function, reference it by name: `search: getSearchExpression`. // It is strongly recommended to make a search expression as concrete // as possible (instead of adding conditions inside the action). search: '#Unresolved has: Assignee has: {Due Date}', // Cron defines a schedule and follows java cron expression syntax // (see http://www.quartz-scheduler.org/documentation/quartz-2.x/tutorials/crontrigger.html) // This expression means that a rule is triggered every day at 10:00. cron: '0 0 10 * * ?', // Action defines what exactly should be done with each issue in a project // which match the search condition. This action is triggered separately // for each issue. The action is performed by the Workflow User. // (see https://www.jetbrains.com/help/youtrack/standalone/2017.2/Workflow-Rules.html#Scheduled) action: function(ctx) { var issue = ctx.issue; if (issue.fields.DD < Date.now()) { issue.fields.Assignee.notify('[YouTrack] Overdue', 'Issue ' + issue.id + ' is overdue.'); } }, // Requirements define which entities are necessary for this rule to work properly. // In this case, we only need fields that store the Assignee and a Due Date. requirements: { Assignee: { type: entities.User.fieldType }, DD: { type: entities.Field.dateType, name: 'Due Date' } } });

Action Rules

Action rules are introduced in this release. Read the comments in the code for this rule to learn more about how it works.

var entities = require('@jetbrains/youtrack-scripting-api/entities'); exports.rule = entities.Issue.action({ // Human-readable title. The title is used as the label for the command in the Apply Command dialog and // the item in Command Dialog list in the issue toolbar. If no title is set, an entry is not added to the menu. title: 'Take this issue!', // Custom command. When you execute this command in a command window // (or click on menu entry, which leads to command execution), // an action below is executed. Commands are defined server-wide, // so that you cannot have two action rules with the same command // even if these rules are attached to different projects. command: 'take', // The guard condition determines when the action rule is enabled. // If the guard condition is not met, the custom command cannot be applied to an issue. // In this case, the command of the action rule is not suggested in the Apply Command dialog // and its title is not visible in the issue toolbar. guard: function(ctx) { return ctx.issue.isReported; }, // Action defines what exactly should be done with each issue which is selected // for command execution. This action is executed separately for each issue. action: function(ctx) { ctx.issue.fields.Assignee = ctx.currentUser; }, // Requirements define which entities are necessary for this rule to work properly. // In this case, we only need a field that stores the Assignee. requirements: { Assignee: { type: entities.User.fieldType } } });

State-machine Rules

State-machines let you control the transitions between values for a specified custom field. For a detailed description, see State-machine Rules.

var entities = require('@jetbrains/youtrack-scripting-api/entities'); // 2 days in ms exports.TWO_DAYS = 2 * 24 * 60 * 60 * 1000; exports.rule = entities.Issue.stateMachine({ // Optional human-readable title. title: 'Status state-machine', // The name of the custom field we attach our state-machine to. fieldName: 'Status', // The list of field values and transitions between them. states: { Open: { // Exactly one value must be defined as initial. The initial value is set // when an issue is created. initial: true, // Transitions define the values we can switch to from the current value. transitions: { // The name of the transition (here `start`) is used to set this value in a command // and is also shown in the list of values for a custom field. start: { targetState: 'In progress' }, // This transition is performed automatically after the interval, set in ms // (`after` property), and its name is not used anywhere. reminder: { targetState: 'Open', after: exports.TWO_DAYS, // Action is an optional property for any transition, which behaves // similar to actions in other types of rules. action: function(ctx) { var issue = ctx.issue; issue.project.leader.notify('Reminder', 'Check out an issue ' + issue.id); } } } }, 'In progress': { // This function is called when the corresponding value is assigned to an issue. // It behaves similar to actions in other types of rules. onEnter: function(ctx) { ctx.issue.fields.Assignee = ctx.currentUser; }, // This function is called when a field value is changed from the current value // to another value. onExit: function(ctx) { // This function is optional. Here, we choose to do nothing. }, transitions: { fix: { targetState: 'Fixed' }, reopen: { targetState: 'Open' } } }, Fixed: { transitions: { // It possible to define no transitions for a given value. // This means that this value is the final possible value for this field. } } }, // Requirements define which entities are necessary for this rule to work properly. // In this case we only need a field that stores the Assignee. // Note that the Status field and its values are derived from the `states` property and // do not have to be defined in requirements. requirements: { Assignee: { type: entities.User.fieldType } } });

Custom Scripts

Custom scripts let you organize and reuse blocks of code. They don't contain any specific type of rule. Instead, they contain functions and objects that can be used in other scripts.

// 'math.js' // In any script, we can define whichever objects and functions // we want to use in other scripts by assigning them as properties // of the `exports` object. // Here we define a simple function to use in other rules. exports.f = function(x) { return x * x - 6 * x + 13; }; // And these are a couple of constants we need. exports.lower = 0; exports.upper = 9;
// 'chart.js' var entities = require('@jetbrains/youtrack-scripting-api/entities'); // The `math` object now has all those properties that // we've defined for the `exports` object in our custom script. // This means that you can access a function as `math.f`, and so on. var math = require('./math'); exports.rule = entities.Issue.action({ title: 'Draw a chart', command: 'draw', action: function(ctx) { var issue = ctx.issue; var chart = ''; for (var x = math.lower; x <= math.upper; x++) { var fx = math.f(x); var line = x + ' | '; for (var i = 0; i < fx; i++) { line = line + "#"; } chart = chart + line + "\n"; } issue.addComment(chart); } });

Part 3. What Can I Do with the Workflow API?

Requirements

Requirements contain a set of entities that should be present on a given YouTrack instance for the rule to work as expected. Whenever one or more rule requirements are not met, corresponding errors are shown in the workflow administration UI. The rule is not executed until all of the problems are fixed. This is the safety-net function of requirements.

A second function is a reference one: each required object is plugged into the context object, so that you can reference entities from inside your context-dependent functions (like an action function). There are two types of requirements: project-wide and system-wide. Project-wide requirements contain a list of custom fields to be attached to each project which uses the rule as well as some values from fields value sets. System-wide requirements contain a list of other entities that should be present on a given instance: users and user groups, projects and issues, tags and saved searches.

See detailed examples below.

requirements: { // Each requirement has an alias (a key). A corresponding entity can be referenced // in a context by this alias. For example, a custom field defined below // can be referenced as `ctx.P`, while an issue custom field can be referenced as // `issue.fields.P` as well as `issue.fields.Priority`. P: { // Type is a required property. Here is the complete list of types: // build : entities.Build.fieldType // enum : entities.EnumField.fieldType // group : entities.UserGroup.fieldType // ownedField : entities.OwnedField.fieldType // state : entities.State.fieldType // user : entities.User.fieldType // version : entities.ProjectVersion.fieldType // date : entities.Field.dateType // float : entities.Field.floatType // integer : entities.Field.integerType // string : entities.Field.stringType // period : entities.Field.periodType type: entities.EnumField.fieldType, // Name is an optional property. If a name is not set, // it is considered to be equal to an alias. name: 'Priority', // Everything except for known properties are considered // to be value requirements. A value `name` property is also // optional. These values can be referenced as `ctx.P.M` and // `ctx.P.Normal`. For example, `issue.fields.P = ctx.P.M;` M: { name: 'Major' }, Normal: {} }, // System-wide entities are defined by their names, except for // User (defined by login) and Issue (defined by id). ImportantPerson: { type: entities.User, login: 'superadmin' }, OurTeam: { type: entities.UserGroup, name: 'integration-team' }, Int: { type: entities.Project, name: 'Integration' }, Ref: { type: entities.Issue, id: 'INT-483' }, ToBeReleased: { type: entities.IssueTag, name: 'To be released' }, Untested: { type: entities.SavedQuery, name: 'Not tested yet' } }

Context

Context contains a number of entities which may be useful during a rule execution. Context is passed as an argument to action, onEnter and onExit functions. It contains:

  • An issue, which is an object of a current rule. Its origin varies depending on rule type (see a table below). If several issues become objects at the same time, rules are executed for each issue separately in no specific order.

  • A currentUser, who is a subject of a current rule. It also varies depending on rule type (see a table below). Note that the change initiator is inherited. If the execution of a scheduled rule triggers an on-change rule, the currentUser for the on-change rule is inherited from the scheduled rule.

  • All entities that are defined in Requirements.

Rule type

Issue

User

On-change

The issue which is changed

The user who initiated this change

Scheduled

The issue which matches the search criteria

A dedicated Workflow User (a system user with full set of permissions)

Action

The issue on which a corresponding command is executed

The user who executed a command

State machine - instant actions

The issue where the controlled field is changed

The user who changed the value of the controlled field

State machine - 'after' actions

The issue where the value of the controlled field is equal to the value specified for the action

A dedicated Workflow User (a system user with full set of permissions)

Properties, Custom Fields, and Links

// 1. Accessing properties: predefined issue fields like // 'summary', 'description', 'reporter', and so on. var summary = issue.summary; issue.summary = "Just a bug"; // 2. Accessing custom fields. For example, 'State', 'Assignee'. // 2a. Single-value fields - `issue.fields["Field Name"]` is a value: var state = issue.fields.State; issue.fields.State = ctx.State.Open; if (issue.fields.State.name === ctx.State.Fixed.name) { // Do stuff } // 2b. Multi-value fields - `issue.fields["Field Name"]` is a Set // (see details in next section): var versions = issue.fields["Fix versions"]; versions.forEach(function(v) { subtask.fields["Fix versions"].add(v); }); // 2c. If you set an alias for a given field in requirements, // you can use it for value access as well, // e.g. if "Fix versions" is required as FV: issue.fields.FV.forEach(function(v) { // Do stuff }); // 3. Accessing links. For example, 'relates to', 'parent for'. // Links are accessed by name, `issue.links["Link Name"]` is always a Set // (see details in next section): var parent = issue.links["subtask of"].first(); parent.links["parent for"].add(issue);

Set API

For a detailed description of this object, see Set.

In short, there are several groups of operations you can do with multiple values (returned as Set<value type>):

  • Access them directly (first(), last(), get(index)) or by iterator (entries(), values()).

  • Traverse over all values in Set with forEach(visitor).

  • Look for values with find(predicate) and check if a value is in Set with has(value).

  • Check size with isEmpty(), isNotEmpty() and size property.

  • Modify content with add(element), remove(element) and clear().

  • Get the current changes for a Set object with the added, removed and isChanged properties.

Calling Methods

// Call entity method: var stateCF = issue.project.findFieldByName('State'); // Call static method: var p = entities.Project.findByKey('INT'); // Call issue constructor: var newIssue = new entities.Issue(ctx.currentUser, issue.project, "Subtask");

Finding Specific Entities

There are two ways to find a specific entity, like an issue or a user, and use it in a workflow script:

  1. Add it in the Requirements and reference it in the Context. Use this approach as often as you can, as it is the most reliable. If the specified entity is not found in your database, the script is not executed.

  2. If the first option is not applicable for whatever reason, use the findBy* and find*By* API:

// findBy* methods - use to find a single occurrence of a specific entity: var issue = entities.Issue.findById('MP-23'); // an entities.Issue or null var projectByName = entities.Project.findByName('Music Production'); // an entities.Project or null var projectByKey = entities.Project.findByKey('MP'); // an entities.Project or null var user = entities.User.findByLogin('jane.smith'); // an entities.User or null var userGroup = entities.UserGroup.findByName('MP team'); // an entities.UserGroup or null var agiles = entities.Agile.findByName('MP Scrum'); // a Set of entities.Agile var tags = entities.IssueTag.findByName('production'); // a Set od entities.IssueTag var queries = entities.SavedQuery.findByName('MP Backlog'); // a Set of entities.SavedQuery // find*By* methods - use to find child entities: var sprint = agiles.first().findSprintByName('Sprint 23'); // an entities.Sprint or null var priorityField = projectByKey.findFieldByName('Priority'); // an entities.ProjectCustomField or null var major = field.findValueByName('Major'); // an entities.Field or null var critical = field.findValueByOrdinal(1); // an entities.Field or null var assigneeField = projectByKey.findFieldByName('Assignee'); var jane = assigneeField.findValueByLogin('jane.smith'); // an entities.User or null var groupField = projectByKey.findFieldByName('Requestors'); var newBand = groupField.findValueByName('New Band'); // an entities.UserGroup or null

Finding Several Issues

Sometimes you might want to find a set of issues that match certain criteria and process them in the scope of a single rule. For example, you can compile a list of issues and sent it as an email message. In these situations, the Search API can help. Here's a simple example:

var entities = require('@jetbrains/youtrack-scripting-api/entities'); var search = require('@jetbrains/youtrack-scripting-api/search'); var workflow = require('@jetbrains/youtrack-scripting-api/workflow'); exports.rule = entities.Issue.onChange({ title: 'Do not allow developers to have more than 1 issue in progress per project', action: function(ctx) { var issue = ctx.issue; if (issue.isReported && (issue.fields.becomes(ctx.State, ctx.State['In Progress']) || issue.fields.isChanged(ctx.Assignee)) && (issue.fields.Assignee || {}).login === ctx.currentUser.login) { // First, we build a search query that checks the project that the issue belongs to and returns all of the issues that are assigned to current user except for this issue. var query = 'for: me State: {In Progress} issue id: -' + issue.id; var inProgress = search.search(issue.project, query, ctx.currentUser); // If any issues are found, we get the first one and warn the user. if (inProgress.isNotEmpty()) { var otherIssue = inProgress.first(); var message = 'Dear ' + ctx.currentUser.login + ', please close <a href="' + otherIssue.url + '">' + otherIssue.id + '</a> first!'; workflow.check(false, message); } } }, requirements: { State: { type: entities.State.fieldType, 'In Progress': {} }, Assignee: { type: entities.User.fieldType } } });

Major Changes from the Previous Version of the API

Here is a short list of major API changes in comparison to the previous API:

  1. The issue reference is never omitted. You can't call an issue method assuming that it is called for an issue from context - you must write it explicitly, as in all examples above.

  2. Property-related methods are spelled differently. Also, these functions are now properties. Get methods are simplified.

    Previous API

    Current API

    issue.becomesResolved(), issue.becomesReported()issue.becomesResolved, issue.becomesReported
    issue.getId(), issue.getUrl()issue.id, issue.url
  3. Fields-related keywords are now methods.

    Previous API

    Current API

    X.requiredissue.fields.required(ctx.X, <message>)
    X.changedissue.fields.isChanged(ctx.X)
    X.becomesissue.fields.becomes(ctx.X, <ctx.X.value>)
    X.oldValueissue.fields.oldValue(ctx.X)
  4. Global keywords and warning statements have changed.

    Previous API

    Current API

    message

    workflow.message(<message>) (see '@jetbrains/youtrack-scripting-api/workflow' module)

    assert

    workflow.check(<condition>, <message>) (see '@jetbrains/youtrack-scripting-api/workflow' module)

    loggedInUserctx.currentUser
    now

    Date.now() (standard JS date API)

  5. Some methods are changed completely:

    Previous API

    Current API

    loggedInUser.createNewIssue(<project name>)new entities.Issue(<user>, <project>, <summary>)

Troubleshooting

Condition

You can't create an action rule. YouTrack returns the error Action rule can't have same event name as another action rule.

Cause

The name of the action rule matches an existing command. The name of the action rule is assigned to the command that runs the rule. Commands must be unique per server.

Solution

Use another name for your action rule.

Condition

The workflow rule throws an error for a missing issue field, but the requires setup flag is not displayed in the workflows list.

Cause

The field is not added to the Requirements section.

Solution

Add the field to the Requirements section.

Cause

The reference to the field in the Requirements section is written in the wrong case.

Solution

While the administrative check for requirements in workflow scripts is case-insensitive, the workflow accesses the issue fields case-sensitively. Compare the name of the field on the Custom Fields page with the reference in the Requirements section of the script. Update the reference in the requirements section to match the case that is used for the name of the custom field.

Condition

Changes have been applied to a custom script but the behavior is not changed.

Cause

Custom scripts are only reloaded when other scripts that reference them are updated or at application start.

Solution

Make a minor change in a script that references the custom script (for example, add new line) and save it.

Notes

  1. You can find the complete API reference right here in the documentation.

  2. The API is also described in the @jetbrains/youtrack-scripting-api package in the built-in workflow Editor.

  3. You can debug workflow rules in the editor with the console.log(<message>) method. These messages are displayed in the Console pane in the workflow editor and printed to the workflow.log file in your instance logs folder.

Last modified: 7 March 2019