YouTrack Standalone 2017.1 Help

Workflow Language Quick Reference

Overview

The main workflow rules types are the stateless rule, the scheduled rule and the state-machine rule.

CodeDescription
rule Up priority when State.becomes({Open}) || Fix versions.changed { Priority = {Critical}; }
The rule raises 'Priority' when the 'State' field is changed to the 'Open'.
Every field has the .becomes() method and the .changed property:
  • field .becomes({Value}) is true if the current change is setting the value to {Value}
  • field .change is true, if the field's value is just changing in the current transaction.
    You can also refer for more detailed description here
schedule rule Project admin report daily at 10:00:00 [issue.Priority == {Show-stopper}] { project.leader.notify("Urgent!", "Look at the " + getId()); }
Each day at 10:00, this rule sends emails to a project's administrator about all 'Show-stoppers'.
The scheduled rule is triggered by a timer that can be set to go off every minute, hourly, daily, weekly, monthly, yearly or by an arbitrary cron expression.
You can also refer for more detailed description here
statemachine state machine for field State { initial state Open { on fix[always] do { Fix versions.required("Please set the 'Fix versions'"); } transit to Fixed exit { message("You're leaving the state 'Open'"); } } state Fixed { enter { message("You're entering into the state 'Fixed'"); } on reopen[always] do {<define statements>} transit to Open in 1 day[Priority == {Critical}] do {<define statements>} transit to Open } }
A simple state machine that defines allowed transitions between issue states.

State machine is based on states provided by the state machine's specified field and by the transitions between these states. More detailed description is available here.
In particular, there are :
  • initial state <State> — it's the start state
  • on 'event name' .do {<statements>} transit to <State> — define the custom transition
  • enter {} — executed on entering to the state
  • exit {} — executed on exiting from the state
  • in time [condition] do {<statements>} transit to <State> — in the time period, if the condition is satisfied, do something and transit to the target state

As any respective programming language, the workflow language has keywords, variables, collections and iterators, and standard statements. Also there are specific to the workflow language statements, controls, methods and customs working with dates, strings and more.
All rules are worked on context of issue. Workflow cannot handle bundles changes, user registration/modification, report building, tag or saved search modifications and so on.

Back to top>>

Keywords

There are five main keywords: loggedInUser, now, null, true, false.

CodeDescription
issue.Assignee = loggedInUser; The loggedInUser references to the current logged in user, the current 'change' executor.
Due date = now; The current moment in workflow is now.
var number = null; var isHandled = true; var hasChildren = false; Standard expressions true, false, null, which function as expected.

Back to top>>

Variables

Variable is declared with a keyword var followed by variable name. Optionally, initial value can be specified after '=' sign, like in JavaScript language.

CodeDescription
var state = State; The simple variable assignment.
var versions; versions = Fix versions; The simple split variable assignment.
var oldAssignee = Assignee.oldValue; Any field has the oldValue parameter, which is the reference to the previous value of the field; the value that the field had prior the current change.

Back to top>>

Iterators and Collections

Workflow language supports two main iterators for each and while and the following predefined collections: issues, comments, tags, users, issue links, enum elements, versions, builds, ownedFields, groups, states, bundle static elements, strings.

CodeDescription
for each version in Fix versions { if (version.releaseDate < now) { project.leader.notify("Overdue version!", "The issue " + getId()+ " has overdue fix version."); } }
Send notification to a project leader about the overdue 'Fix version'.
description = description + tags.first.name; summary = comments.first.text; var isTodo = tags.contains({tag: todo}); relates to.clear; Any collection has the fields first, last, isEmpty, isNotEmpty and the following methods:
  • added — a collection of items that were added during the current transaction
  • removed — a collection of items that were removed during the current transaction
  • contains(<element>) — check whether a collection contains the specified element or not
    The tags, enum[], *state[], *version[], *builds[], *groups[], *user[]* have also the add() and clear() methods.
var n = 0; var pi = 0; while (n < 100) { if (n % 2 == 0) { pi = pi + 1 / (2 * n + 1); } else { pi = pi - 1 / (2 * n + 1); } n++; } pi = pi * 4;
The while loop has the standard behaviour of iteration by the condition.

The example provided here shows how you can calculate the Pi number within workflow rule.

Back to top>>

Operators

The workflow language provides several specific operators: assert, message and, of cause, the 'branch' operator.

CodeDescription
assert Assignee != loggedInUser: "Oops! Only the Assignee can make changes."; The assert statement stops the rule execution if condition is false and roll-backs all changes to the initial issue(s) state.

Workflows can be invoked by the chain. For example, one workflow rule changes issue attributes in such way that the issue starts to match another workflow rule conditions, and so on. In such case all changes made by these workflows will be roll-backed.
if (now > created + 3 days && Assignee == null) { message("This issue has been created 3 days ago but still unassigned!"); }
The if operator has the standard branch operator behavior.
if (Type == {Feature}) { project.leader.notify("Voted feature", "The feature " + getId() + " has " + votes + " votes!"); } else if (Type == {Bug}) { project.leader.notify("Duplicated bug", "There are " + duplicatesNumber + " duplicates of the bug " + getId()); } else { project.leader.notify("Another type", "The " + Type.name + getId() + " just need your attention."); }
Also there are additional else { } and else if { } operators.

Back to top>>

Controls

The are specific to the workflow language operators required, message and also family of logging operators: debug, info, warn, error, fatal.

CodeDescription
Subsystem.required("Please choose a subsystem.") Every field has the required (<message>) control. If the field value is null it is highlighted and the provided message is shown up.
message("Hello! You are logged in as " + loggedInUser.fullName); The message statement shows up the blue pane on the screen top with the provided message text.
debug("The number: "+ number); info("March 8? - " + (now != 2013-03-08)); warn("Logged in user is developer: " + loggedInUser.isInGroup("Developers")); error("Now: " + now.format(fullDate)); fatal((issue.created == null) + "");
All these methods log the messages into the log files worklfow.log, youtrack.log. The errors and fatal also logs messages into the errors.log.

Back to top>>

Issue-specific Methods

There are more interesting and important issue-specific methods in the workflow language: applyCommand(), addComment(), addTag(), removeTag(), clearAttachment(), isReported(), hasTag().

CodeDescription
applyCommand("for me Critical"); The method applyCommand (<command>) applies the specified by text command to the source issue.
addComment("+1!!!"); The method addComment (<comment>) adds the new comment provided by the text parameter.
addTag("todo"); The method addTag (<tag>) adds the tag provided by the text parameter.
clearAttachments(); The method clearAttachments() deletes all issue attachments.
removeTag("waiting for reply"); The method removeTag (<tag>) removes the tag provided by the text parameter.
if (!isReported() && description == null) { description = "Welcome to YouTrack! \nPlease describe reproduce steps below and attach screenshots if possible."; }
isReported() is true for existing issues. The draft context is defined by !isReported() method.
var amIWatchingResolved = hasTag("Star") && isResolved(); The method hasTag (<tag>) answer whether issue has specified tag or not.
The isResolved () method is true, if all state-type fields are resolved. It there is at least one state-field in unresolved state, this method returns false.
sendMail(Reporter email, "[YouTrack, Commented]", "New comment was added: " + comments.added.first.text)); The method sendMail (<email>, <subject>, <body>) sends the letter by the specified email.

Back to top>>

Working with Dates

YouTrack workflow language provides the built-in DSL for dates, which allows you to format, compare, sum, and subtract dates.

CodeDescription
var today = now.format(mediumDate); The format (<format_string>) method gets the string representation of a date.
The following format options are available:
  • custom format — complete '#' and set the suite of date variables, e.g. "y M d";
  • shortDate, shortDateTime, shortTime, mediumDate, mediumDateTime, mediumTime, fullDate, fullDateTime, fullTime, longDate, longDateTime, longTime
if (created < 2012-12-31 && !isResolved()) { tags.add(project.getUser("root").getSharedTag("obsolete?")); }
Date constants are presented in the language by the pattern year-month-day.
if (updated + 10 months < now) { project.leader.watchIssue(issue); }
Dates can be summed/subtracted with a period-type constant. The following period constants are supported:
  • millisecond(s)
  • second(s)
  • minute(s)
  • hour(s)
  • day(s)
  • week(s)
  • month(s)
  • year(s)
Spent time = now - Start time; The result of the dates subtraction is period.
var duration = now - Timer time; var seconds = (duration.millis - duration.millis % 1000) / 1000; There is possibility to get 'milliseconds' value for a time period.

Back to top>>

Static References

It's allowing to reference some entity statically by the construction {category: static_entity_name}, {group: group_name}, {issue: issueID}, {project: project_shortName}, {savedSearch: savedSearchName}, {tag: tagName}, {user: username}.

CodeDescription
{group: QA Engineers}.notifyAllUsers("Test failed!", "Please look at the failed test " + getId()) All member of the group 'QA Engineers' will be notified.
assert {issue: IDEA-99999}.Type == {Feature}: "It should be the cool feature about the new look and feel!"; The issue 'IDEA-99999' must have the type "Feature".
for each currency in {project: Business Trips}.valuesFor(Currency) { if (currency.name == {Dollar}) { assert currency.colorIndex == 17: "Dollar should be green!"; } }
The 'Currency' field values are taken for the project "Business Trips".
for each notMyIssue in loggedInUser.getIssues({savedSearch: Reported by me}, "for: -me") { notMyIssue.Assignee = loggedInUser; }
The loop by 'reported by current user but not assigned for it' issues.
rule Regression when State.becomes({Reopened}) { tags.add({tag: regression}); }
The tag 'regression' is added onto the reopening issue.
var newIssue = {user: admin}.createNewIssue({project: Administration}.shortName); newIssue.summary = {issue: TMPL-1}.summary; newIssue.tags.add({tag: urgent});
Different static entities are using.

Back to top>>

User-related Methods

The workflow user entity references a YouTrack user. It allows getting user's login, full name, and email attributes and supports a number of methods.

CodeDescription
var myIssues = loggedInUser.getIssues(Everything, "created: Today for: " + loggedInUser.login); Get issues by the query.
  • getIssues(<saved search>, <query>);
var user = project.getUser("user"); if (user.canVoteIssue(issue)) { user.voteIssue(issue); }
Check if user can vote for the issue and do it if can.
  • canVoteIssue(<issue>);
  • voteIssue(<issue>);
  • unvoteIssue(<issue>);
  • canUnvoteIssue(<issue>);
var createIfNotExist = loggedInUser.getSharedTag("iOS") == null; var iOSTag = loggedInUser.getTag("iOS", createIfNotExist);
Get shared and private issue tags.
  • getSharedTag(<tag_name>);
  • getTag(<tag_name>, <create_new>);
var todoIssue = loggedInUser.createNewIssue(project.shortName); todoIssue.summary = "iPhone :" + issue.summary; todoIssue.tags.add(iOSTag); tags.add(iOSTag); Creates new issue. Note, in the same workflow it's need to set the issue *summary *because issue can be created with empty summary.
  • createNewIssue (<projectName>);
    Also there additional methods:
  • getVisibleName();
if (loggedInUser.isInGroup("developers") && loggedInUser.hasRole("Developer")) { loggedInUser.watchIssue(issue); } else { loggedInUser.unwatchIssue(issue); } Check if user has appropriate permissions.
  • watchssue(<issue>);
  • unwatchIssue(<issue>);
  • hasRole(<role_name>);
  • isInGroup(<gropp_name>);
var projectLeader = project.leader; projectLeader.notify("Attention","Please pay attention to " + getId()); projectLeader.sendJabber("Please look at " + getId()); Send letter to the project leader.
  • notify(<title>, <content>, <ignoreNotifyOnOwnChangesSetting>) (and its alias sendMail(<subject>, <body>)).
  • sendJabber(message);

Back to top>>

String Methods

There are a lot of methods to working with strings, e.g. startsWith(), substring(), indexOf() etc. Look details on the Apache StringUtils reference.

CodeDescription
var operators = "+-*/%"; var expr = "a * b + c"; var variable = ""; for each term in expr.split(" ", opts) { if (!operators.contains(term, opts)) { variable = term; } }

This piece of code parses the expression to terms.

var extractedUsername = comments.added.first.text.substringBetween("@", " "); project.getUser(extractedUsername).notify("[Youtrack, Comment notice]", "You were mentioned in the comment of issue " + "", true);

This rule extracts mentioned in comment user login from the "@username" substing and sends the notification to it.

Back to top>>

Field-specific Methods

There are number of methods which are specific to entity types project, comment, tag, group, enum filed, state, owned field, version, build, integer, period.

One can access the project properties shortName, leader, name, issues, fields, description, createdBy. The methods getUser(<name>) and valuesFor(<field>) are available as well.

CodeDescription
var newIssue = project.getUser("admin").createNewIssue(project.shortName); newIssue.Assignee = project.leader; newIssue.summary = "Issue is created in the project " + project.name;

The simple way to create new issue on behalf of given user in the selected project.

for each version in project.valuesFor(Fix versions) { if (version.releaseDate > now && Fix versions.contains(version)) { message("On of the 'Fix versions' is overdue."); } }

The rule shows up the message if at least one of 'Fix versions' is overdue.

var allProjectIssues = project.issues;

The way to get all project issues.

Back to top>>

Comment

The comment entity has the properties author, created, issue, permittedGroup, text, updated, updatedBy and the method getUrl().

CodeDescription
Assignee = comments.added.first.author;

The 'Assignee' is set to the just added comment reporter.

Due Date = comments.last.created;

The 'Due date' is set to the last existing comment created date.

if (comments.added.first.text.contains("fix:", opts)) { State = {Fixed}; }

If the word "fix:" is met on the adding new comment than the 'State' becomes 'Fixed'.

assert comments.added.first.permittedGroup == issue.permittedGroup: "Comment visibility group have to match issue visibilit group.";

Prohibit to restrict adding a new  comment by the group not equal to the issue visibility group.

Back to top>>

Tags

The tag entity has two properties: name and owner.

CodeDescription
assert tags.added.last.owner == loggedInUser: "You aren't the owner of the tag " + tags.added.last.name;

Check if user is trying to add not own tag.

Back to top>>

Other Fields

Groups

The group has properties addNewUser, allUsersGroup, description, name and methods getUsers(), notifyAllUsers().

CodeDescription
var users = Assignees group.getUsers();

Gets all the group 'Assignees group' users.

permittedGroup.oldValue.notifyAllUsers("Visibity has been changed", "The visibility group for the issue " + "<a href=\"" + issue.getUrl() + "\">" + issue.getId() + "</a> has been changed to " + permittedGroup.name);

The rule notifies all the members of the issue permitted group before its changing.

Back to top>>

Enums

The standard fields 'Priority', 'Type' have the type 'enum'. Enum fields has the properties name, description, ordinal, colorIndex and method getPresentation().

CodeDescription
if (Priority.colorIndex == 19) { var priorityPresentation = "The priority '" + Priority.name + "' (" + Priority.description + ") is red."; }

The rule constructs the 'Property' field presentation text.

if (!Type.name.eq(Type.getPresentation(), opts)) { message("The " + Type.getPresentation() + " has been changed."); }

Show the message if getPresentation() is differed from the name. It seems it shouldn't appears ever :)

Back to top>>

States

State entity has isResolved, description, name, ordinal and getPresentation() method.

CodeDescription
for each state in project.valuesFor(State) { if (State.isResolved && state.ordinal > State.ordinal) { State = state; } }

This code calculates the highest state along the 'State' values.

Back to top>>

Owned Field

The standard 'Subsystem' field has the type 'ownedField'. It has the properties owner, description, name, ordinal and getPresentation() method.

CodeDescription
issue.Assignee = issue.Subsystem.owner;

Set current 'Subsystem' owner to the 'Assignee'.

Back to top>>

Version

The field of type 'Version' has the properties archived, released, releasedDate, description, name, ordinal and getSprint(), getPresentation() methods.

The sprint cab be accessed by the version.getSprint(<project>) and has two properties start and finish.

CodeDescription
var version = Fix versions.added.first; if (!version.archived && !version.released && version.releaseDate > now) { message("Current sprint start date: " + version.getSprint(project).start + "; finish date: " + version.getSprint(project).finish); }

This rule shows up the current sprint start and finish dates.

Back to top>>

Build

The field of type 'Build' has the properties assembleDate, description, name, ordinal and getPresentation() method.

CodeDescription
Due Date = Fixed in build.assembleDate;

The build assemble date is set to the 'Due date' field.

Back to top>>

Integer

A field of the 'Integer' type inherits all standard properties as any other issue field but also behaves as the primitive type, and thus supports the following standard operations: +, -, &, /, %, comparison operations: <, <=, >, >=+, increment/decrement i++, i--, ++i, --i.

CodeDescription
var increaseNumber = Internal number + 1;

Increases the 'Internal number' by 1.

Back to top>>

Period

The 'period' type time field is often used in combination with the date fields.

CodeDescription
Spent time = now - Due Date;

The 'Spent time' period is a result of subtracting the given time stamp 'Due date' from the current time stamp.

Back to top>>

Localization

Generally, we assume that users will use text strings (notifications, messages, etc.) in one same language. That is, we assume that users create not multi-lingual workflows.
If you create a workflow rule without specific tag for localization, then text strings in such rules will be shown in the same language and will not be auto-translated with the change of the system language. For example, let's say you use Spanish localization, and you create a workflow with texts in Spanish. In this case, if you choose to switch the system language back to English, your custom workflow will still have texts in Spanish.

However, there is a way to create a multi-lingual workflow that will be auto-translated along with the UI texts and default workflows when you switch to another system language.
To implement such workflow, you should use l10n construction for your texts. Please see the example below.

CodeDescription
Due Date.required("S'il vous plaît préciser la date");

This workflow shows the French text independently of the chosen system language.

l10n ( The work item automatically added by the timer. )

There is a way to localize you own workflow on different languages:

  1. Copy any l10n( text ) entity from any of the default workflows and past it wherever needed.
  2. Standing on the l10n open the 'Inspector panel' in the right bottom corner of Workflow Editor (Alt+2)
  3. Create new unique id for the string and replace the existing one.
  4. Add this key with the text string to all of the translation files that you need. For more details, please refer to the reference

Back to top>>

Project-based Reports

In YouTrack, generally, all workflow rules are applied in the context of an issue. However, in some cases it would have been useful to apply rules in a more global context - in the context of a project, which would provide us opportunity to implement, for example, project-based reports. Luckily, we have a trick allowing us to invoke stateless/schedule rule in the project context.

The example below shows how you can implement a weekly report about unassigned issues in a project. Such report is sent on Mondays, at noon to the project's Leader.

CodeDescription
schedule rule weekly report weekly on Monday at 12:00:00 [issue == {issue: A-1}] { var msg = ""; var myIssues = project.leader.getIssues({savedSearch: Unassigned in A}, ""); for each anIssue in myIssues { if (msg.isNotEmpty) { msg = msg + ", "; } msg = msg + "<a href=\"" + anIssue.getUrl() + "\">" + anIssue.getId() + "</a>"; } msg = msg + "<p style=\"color: gray;font-size: 12px;margin-top: 1em;border-top: 1px solid #D4D5D6\">Sincerely yours, YouTrack</p>"; debug(msg); project.leader.notify("Report", msg, true); }

A-1 — is a random issue that virtually implement rule's context and is not actually used in the rule. This workflow sends the weekly report containing list of all unassigned issues in the project A.

Back to top>>

Last modified: 18 April 2017