h1. Welcome to YouTrack Workflow Repository\!
{section}
{column:width=60%}
{tip:title=To use a ready workflow file in your YouTrack, please:} !screenshot1.png|align=right,thumbnail,border=1!
* Download a workflow zip file.
* If you are a project administrator, then import the workflow to your project in Administration > Projects > _project_ > Workflow tab.
* If you have system administrator permissions, then import the workflow to your YouTrack in Administration \-> Workflow page and attach it to one or multiple projects.
You can also try any of the custom workflows listed below in our [YouTrack InCloud test instance|http://workflows.myjetbrains.com/youtrack/]. All registered users get project admin permissions.
{tip}
{column}
{column:width=40%}
{panel:bgColor=#FFFFFF}(i) *Continue*
- New to workflow in YouTrack? Please take a look at the [workflow guide|http://confluence.jetbrains.net/display/YTD3/Workflow+Guide].
- Ready to create your own workflow rule? Use [YouTrack Workflow Editor|http://www.jetbrains.com/youtrack/download/index.html].
- Want to share your workflow? [Contact us|mailto:youtrack-feedback@jetbrains.com] or [create an issue|http://youtrack.jetbrains.com/dashboard/JT].
{panel}
{column}
{section}
h2. Custom Workflow Examples
This table contains various use cases and programmed workflow rules ready to be used in your YouTrack.
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Votes and watchers} Votes and Watchers
{anchor} [(download zip file)|^Votes and Watchers.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Add the project lead to an issue watchers list once the issue collects a certain number of votes.{td}
{td:class=confluenceTd}{code}rule watch if lots of votes
when votes == 10 {
project.leader.watchIssue(issue);
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Default visibility group} Default Visibility Group {anchor}[(download zip file)|^default visibility group.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Set the default visibility group for all new issues in a project.{note}{*}Requirements*: group 'Reporters'{note}{td}
{td:class=confluenceTd}{code}rule set permitted group for new issues
when !isReported() && reporter.isInGroup("Reporters") {
permittedGroup = {developers};
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:no comments added}User Comment(s) Awaiting Reply Notification {anchor} [(download zip file)|^User Comment(s) Awaiting Reply notification.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}If an issue has a recent non-developer comment, send a notification to a specific user (support team member). This check is performed at a given time (daily at 12pm by default). {note}{*}Requirements*: group 'developers-team'{note}{td}
{td:class=confluenceTd}{code}schedule rule Notify on no comments during time period
daily at 12 : 00 : 00 [!issue.State.isResolved] {
if (comments.last != null && !comments.last.author.isInGroup("developers-team")) {
project.leader.notify("[Youtrack, Notifier] Issue " +
getId() + " needs your attention",
"Issue " + summary + " has a recent comment added by a non-developer.");
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:reopenduplicate} Reopen Duplicate {anchor}[(download zip file)|^Reopen if no duplicates.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Reopen an issue if all duplicate links are removed.{td}
{td:class=confluenceTd}{code}rule Reopen issue on removing last duplicates link
when duplicates.changed && duplicates.isEmpty {
State = {Open};
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Clearfixforversion} Clear 'Fix For Version' {anchor}[(download zip file)|^Clear fix for version on reopen.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Clear 'Fix for version' field when an issue is reopened.{td}
{td:class=confluenceTd}{code}rule Clear fix for version on issue reopen
when State.oldValue != null && State.oldValue.isResolved && !State.isResolved {
Fix versions.clear;
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Unblocked state when no blockers}'Unblocked' State {anchor}[(download zip file)|^Unblocked state when no blockers.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Provide an ability to have the issue unblocked (denoted with a "is blocked by" - "blocks" relationship) when the last remaining blocker is resolved and the issue has no remaining blockers.{note}{*}Requirements*: link types 'blocks' - 'is blocked by' {note}{td}
{td:class=confluenceTd}{code}rule unblock on blockers resolving
when issue.State.isResolved && !issue.State.oldValue.isResolved {
for each dependant in issue.blocks {
if (dependant.is blocked by.first == dependant.is blocked by.last) { //e.g. it's source issue
dependant.State = {Unblocked};
}
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Semi-automatic 'Wait for reply' state management} Semi-Automatic 'Wait for Reply' State Management
{anchor} [(download zip file)|^Semi-automatic 'Wait for reply' state management.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}When a comment from an external user is required, internal user sets issue state to 'Wait for reply' manually. When a comment is added, issue is automatically reopened. Assignee gets a notification if no comment is added within 5 days. {note}{*}Requirements*:
* state 'Wait for Reply'
* group 'developers'
{note}{td}
{td:class=confluenceTd}Statemachine with 'Waiting for reply' state: {code}statemachine States for field State {
initial state Submitted {
on wait[always] do {<define statements>} transit to Wait for Reply
...
}
state Open {
on wait[always] do {<define statements>} transit to Wait for Reply
...
}
state Wait for Reply {
on fix[always] do {<define statements>} transit to Fixed
on open[always] do {<define statements>} transit to Open
on in progress[always] do {<define statements>} transit to In Progress
...
}
}{code}
{expand:title=Changes issue state from 'Wait for reply' to 'Open' when comment is added by an external user}
{code}
rule Wait for reply: reopen on answer
when State == {Wait for Reply} && comments.added != null && comments.added.isNotEmpty &&
comments.added.last != null && !comments.added.last.author.isInGroup("developers") {
State = {Open};
}
{code}
{expand}
{expand:title=Notify assignee if no answer is received within 5 days}
{code}
schedule rule Wait for reply reminder
daily at 12 : 05 : 00 [issue.State == {Wait for Reply}] {
var lastComment = comments.last;
if (lastComment != null && lastComment.author.isInGroup("developers")
&& lastComment.created + 5 days < now) {
if (Assignee != null) {
Assignee.notify("[Youtrack, 'Wait for reply' reminder] Unanswered comment within 5 days",
"Hi, " + Assignee.fullName + "! <br><br> Issue <a href=\"" + getUrl() + "\">" + getId()
+ " " + summary + "</a>" + " is in state \"Wait for reply\" more than 5 days."
+ " It has unanswered comment:<br><b>" + lastComment.text
+ "</b><br><br> Please answer or resolved issue.<br><br>"
+ "<p style=\"color: gray;font-size: 12px;margin-top: 1em;border-top: 1px solid #D4D5D6\">"
+ "Sincerely yours, YouTrack" + "</p>");
}
}
}
{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:notifyuser} Notify User Mentioned in Comment
{anchor}[(download zip file)|^Notify Mentioned in Comment User.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Send notification to a user mentioned in a comment.{td}
{td:class=confluenceTd}{code}schedule rule send notification to a user mentioned in comment
daily at 12 : 00 : 00 [<expression>] {
var mentionedUser;
for each comment in comments {
if (comment.text.contains("remind", opts) && comment.created + 1 day > now) {
mentionedUser = comment.text.substringBetween("remind @", " ");
}
}
for each user in permittedGroup.getUsers() {
if (user.login.eq(mentionedUser, opts)) {
user.notify("You were mentioned", "Your name was mentioned in a comment in " + getId());
}
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Managewaitingforreply}'Waiting for Reply' Tag Management {anchor}[(download zip file)|^Manage tag Waiting for reply.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd} Replace 'waiting for reply' tag with 'answered' when a developer adds a comment.{note}{*}Requirements*: group 'developers'{note}{td}
{td:class=confluenceTd}{code}rule Set tag waiting for reply (answered)
when comments.added.isNotEmpty {
if (!comments.added.last.author.isInGroup("developers")) {
removeTag("waiting for reply");
addTag("waiting for reply (answered?)");
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Display the last submitted date for duplicated exceptions}Last Submitted Date for Duplicate Exceptions
{anchor}[(download zip file)|^Display the last submitted date for duplicated exceptions.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd} Show the last submitted date for the main issue when a new 'is duplicated by' link is added to it.{note}{*}Requirements*: field 'Last duplicate' of 'date' type{note}{td}
{td:class=confluenceTd}{code}rule Display last submitted date for duplicated exceptions
when issue.is duplicated by.added.isNotEmpty {
var lastDuplicate = issue.Last duplicate;
for each source in issue.is duplicated by.added {
var addedDuplicate = source.created;
if (lastDuplicate < addedDuplicate) {
lastDuplicate = addedDuplicate;
issue.Last duplicate = lastDuplicate;
}
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Restrictionsonfieldchanging} Restrict Fields Changing {anchor}[(download zip file)|^Restrict Fields Changing.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd} Make some fields editable only by the issue assignee. Display an error message to any other user who attempts to modify the fields. {td}
{td:class=confluenceTd}{code}rule assert assignee can change field
when Priority.changed || Fix versions.changed || Fixed in build.changed {
assert loggedInUser == Assignee: "Only the Issue Assignee can set this field";
}
{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Duplicateissuesvisibilitygroup}Duplicate Issues Only With Same Visibility Groups
{anchor}[(download zip file)|^Duplicate Issues Only With Same Visibility Group.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Disable duplicating issues with different visibility groups.{td}
{td:class=confluenceTd}{code}rule Different duplicates issues visibility
when issue.duplicates.added.isNotEmpty {
for each duplicate in issue.duplicates.added {
assert permittedGroup == duplicate.permittedGroup:
"Issues can only be duplicated if they have the same visibility groups";
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:openassignedissues} 'Open' Assigned Issues {anchor}[(download zip file)|^Set assignee on open issue.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Change an issue state to Open when an assignee is added.{td}
{td:class=confluenceTd}{code}rule Open issue on set Assignee
when Assignee != null && Assignee.changed && Assignee.oldValue == null {
State = {Open};
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Testers process}QA Verification Process
{anchor}[(download zip file)|^QA Verification Process.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}
* Notifies a user when he/she is set to verify an issue
* Requires setting 'Verified in build' value when changing state to 'Verified', and sets current user name for 'Verified by' field value.
* Requires adding a comment when changing state to 'W/O verification'.
{note}{*}Requirements*:
* field 'Verified by' of 'user' type
* field 'Verified in build' of 'build' type
* state 'W/O verification'{note}
{td}
{td:class=confluenceTd}{code}rule Notify on set Verify by user
when isReported() && Verified by.changed {
if (Verified by != null && loggedInUser != Verified by) {
Verified by.notify("[Youtrack, Verify by] You became the 'Verified by' user for issue "
+ issue.getId() + " " + issue.summary, "Hi, " + Verified by.fullName +
"! <br><br> You became the 'Verified by' user for issue <a href=\"" + issue.getUrl()
+ "\">" + issue.getId() + "</a>" + " " + " <a href=\"" + issue.getUrl() + "\">"
+ issue.summary + "</a>" + "<br> Please verify it or close as \"W/O verification\".<br><br>"
+ "<p style=\"color: gray;font-size: 12px;margin-top: 1em;border-top: 1px solid #D4D5D6\">"
+ "Best regards, YouTrack" + "</p>");
}
}{code}
{expand:title=Set 'verified in build' value}
{code}rule Set Verified State on specifying Verified In Build
when Verified in build != null && State != {Verified} {
State = {Verified};
}{code}
{expand}
{expand:title=Set 'verified by' user name when adding 'verified in build' value}
{code}
rule Set verified by user on specifying Verified in build
when Verified in build != null && Verified in build.changed {
Verified by = loggedInUser;
}{code}
{expand}
{expand:title=States machine}
{code}
...
state W/O verification {
enter {
Verified by = loggedInUser;
}
on reopen[always] do {<define statements>} transit to Open
}
state Verified {
on reopen[always] do {<define statements>} transit to Open
enter {
Verified in build.required("Specify 'Verified in build'");
Verified by = loggedInUser;
}
exit {
Verified in build = null;
}
}
...
{code}
{expand}
{expand:title=Require adding a new comment when changing an issue state}
{code}rule Require comment on w/o verification
when State.becomes({W/O verification}) {
assert comments.added.isNotEmpty: "Add a comment about the missing details";
}{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Advanced states machine} Advanced States Machine {anchor}[(download zip file)|^Advanced States Machine.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Manage states transitions.{note}{*}Requirements*:
\\
* field 'Verified by' user of type 'user'
* field 'Verified in build' of type 'build'
* value 'W/O verification in bundle 'State'
* state 'Wait for Reply'
{note}
{td}
{td:class=confluenceTd}
{expand:title=Lifecycle of the State field}
{code}statemachine States for field State {
initial state Submitted {
in 3 days[State == {Submitted}] do {
var user = Assignee;
if (user == null) {
user = project.leader;
}
user.notify("[Youtrack, State reminder] Issue " + issue.getId()
+ " is still Submitted: " + issue.summary, "Hi, " + user.fullName
+ "! <br><br> Issue <a href=\"" + issue.getUrl() + "\">" + issue.getId()
+ "</a>" + " is still in Submitted state:" + " <a href=\"" + issue.getUrl()
+ "\">" + issue.summary + "</a>"
+ "<br> Please start working on it or specify a more accurate state.<br><br>"
+ "<p style=\"color: gray;font-size: 12px;margin-top: 1em;border-top: 1px solid #D4D5D6\">"
+ "Sincerely yours, YouTrack" + "</p>");
}
on fix[always] do {<define statements>} transit to Fixed
on open[always] do {<define statements>} transit to Open
on in progress[always] do {<define statements>} transit to In Progress
on discuss[always] do {<define statements>} transit to To be discussed
on can't reproduce[always] do {<define statements>} transit to Can't Reproduce
on obsolete[always] do {<define statements>} transit to Obsolete
on duplicate[always] do {<define statements>} transit to Duplicate
on as designed[always] do {<define statements>} transit to As designed
on invalidate[always] do {<define statements>} transit to Invalid
}
state Open {
on in progress[always] do {<define statements>} transit to In Progress
on discuss[always] do {<define statements>} transit to To be discussed
on fix[always] do {<define statements>} transit to Fixed
on obsolete[always] do {<define statements>} transit to Obsolete
on duplicate[always] do {<define statements>} transit to Duplicate
on can't reproduce[always] do {<define statements>} transit to Can't Reproduce
on as designed[always] do {<define statements>} transit to As designed
on invalid[always] do {<define statements>} transit to Invalid
on wait[always] do {<define statements>} transit to Wait for Reply
}
state Reopened {
on open[always] do {<define statements>} transit to Open
}
state Obsolete {
on reopen[always] do {<define statements>} transit to Open
}
state Duplicate {
on reopen[always] do {<define statements>} transit to Open
}
state In Progress {
on reopen[always] do {<define statements>} transit to Open
on fix[always] do {<define statements>} transit to Fixed
on can't reproduce[always] do {<define statements>} transit to Can't Reproduce
on obsolete[always] do {<define statements>} transit to Obsolete
on as designed[always] do {<define statements>} transit to As designed
}
state To be discussed {
on in progress[always] do {<define statements>} transit to In Progress
on duplicate[always] do {<define statements>} transit to Duplicate
on obsolete[always] do {<define statements>} transit to Obsolete
}
state Can't Reproduce {
on reopen[always] do {<define statements>} transit to Open
}
state As designed {
on reopen[always] do {<define statements>} transit to Open
}
state Won't fix {
on reopen[always] do {<define statements>} transit to Open
}
state Invalid {
on reopen[always] do {<define statements>} transit to Open
}
state Incomplete {
on reopen[always] do {<define statements>} transit to Open
}
state Fixed {
on reopen[always] do {<define statements>} transit to Open
on verify[always] do {<define statements>} transit to Verified
on can't verify[always] do {<define statements>} transit to W/O verification
}
state W/O verification {
enter {
Verified by = loggedInUser;
}
on reopen[always] do {<define statements>} transit to Open
}
state Verified {
on reopen[always] do {<define statements>} transit to Open
enter {
Verified in build.required("Specify verified in build");
Verified by = loggedInUser;
}
exit {
Verified in build = null;
}
}
state Wait for Reply {
on fix[always] do {<define statements>} transit to Fixed
on open[always] do {<define statements>} transit to Open
on in progress[always] do {<define statements>} transit to In Progress
on discuss[always] do {<define statements>} transit to To be discussed
on can't reproduce[always] do {<define statements>} transit to Can't Reproduce
on obsolete[always] do {<define statements>} transit to Obsolete
on duplicate[always] do {<define statements>} transit to Duplicate
on as designed[always] do {<define statements>} transit to As designed
on invalidate[always] do {<define statements>} transit to Invalid
}
}{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Duty} 'On Duty' Management
{anchor}[(download zip file)|^'On Duty' Management.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Manage duties. Requires a separate project to store duties. Duty is stored in the 'Assignee' field. The chain of duties is implemented as linked by 'is required for' link issues.
{note}{*}Requirements*:
* states: 'On duty', 'Active', 'On a rest'
* new field 'day of week' of type 'integer'
{note}{td}
{td:class=confluenceTd}
{expand:title=Set today's person-on-duty based on 'is required for' link}
{code}schedule rule Set Today's Duty Based on is required for
daily at 10 : 00 : 00 [issue.State == {On duty} && issue.Day of week != 5
&& issue.Day of week != 6] {
if (updated + 10 seconds < now) {
// set new duty
var nextDuty = is required for.first;
var stop = 0;
if (nextDuty == null || nextDuty.Assignee == null) {
project.leader.notify("Duty",
"Duties chain should be implemented via 'is required for' links,
and 'Duty' store field must be non-empty.",
true);
}
while (stop != -1 && stop < 50) {
if (nextDuty.State == {Active}) {
nextDuty.Day of week = Day of week;
nextDuty.State = {On duty};
Assignee.notify("Duty: You are no longer on duty", Assignee.fullName + ", "
+ " you just came off duty.<br><br>The new person on duty is "
+ nextDuty.Assignee.fullName + ".", true);
State = {Active};
debug("Duty changed to: " + nextDuty.Assignee.fullName);
stop = -1;
} else if (nextDuty.State == {On a rest}) {
nextDuty = nextDuty.is required for.first;
stop = stop + 1;
} else {
project.leader.notify("Duty", "States should be only 'Active' or 'On a rest'", true);
stop = -1;
}
}
nextDuty.Assignee.notify("Duty: You are on duty", nextDuty.Assignee.fullName + ", "
+ "you are on duty today.<br><br> Don't forget to process "
+ "<a href=\"http://youtrack.jetbrains.net/issues/JT?"
+ "q=%23Unassigned+%23Unresolved+created%3A+Yesterday%2C+Today+sort+by%3A+Priority+\">"
+ "any new issues in YouTrack." + "</a>" + "<br>"
+ "<p style=\"color: gray;font-size: 12px;margin-top: 1em;border-top: 1px solid #D4D5D6\">"
+ "Sincerely yours, YouTrack" + "</p>", true);
var curIssue = nextDuty;
var preventInfinite = 0;
while (curIssue != issue && preventInfinite < 50) {
if (curIssue.State == {Active}) {
assert curIssue.Assignee != null: "Assignee cannot not be null!";
curIssue.Assignee.notify("Duty: Person on duty has changed", "Today's person on duty is "
+ nextDuty.Assignee.fullName + ".", true);
}
curIssue = curIssue.is required for.first;
preventInfinite = preventInfinite + 1;
}
}
}{code}
{expand}
Manage day of week:
{code}schedule rule Set day of week
daily at 00 : 00 : 00 [issue.State == {On duty}] {
Day of week = (Day of week + 1) % 7;
}{code}
Disable dropping Assignee:
{code}rule Assert Assignee not null
when Assignee.changed {
assert Assignee != null: "Assignee cannot be null!";
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Warn on issue visibility changed}Change Visibility Warning
{anchor}[(download zip file)|^Warn on issue visibility changed.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Warn on change of issue visibility. {note}{*}Requirements*: group 'developers'
{note}{td}
{td:class=confluenceTd}{code}rule issueVisibility
when permittedGroup.changed {
var msg = "To post non-public data, please make the issue public
instead and add protected attachments and/or comments.";
assert loggedInUser.isInGroup("developers"): msg;
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Assignee and multiple subsystems} Assignee and Multiple Subsystems {anchor}[(download zip file)|^Assignee and multiple subsystems.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}When a Subsystem is changed, set issue assignee to the subsystem owner.{note}{*}Requirements*: field 'Subsystems' of type 'ownedField\[*\]'{note}{td}
{td:class=confluenceTd}{code}rule Set assignee from Subsystems
when Subsystems.changed {
var owner = null;
for each subsystem in Subsystems {
if (owner == null) {
owner = subsystem.owner;
} else if (subsystem.owner != owner) {
owner = null;
break;
}
}
if (owner != null && Assignee == null) {
Assignee = owner;
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Feedbackmanagement} Feedback Management {anchor}[(download zip file)|^Feedback management.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Handle feedback getting from mailbox. {note}{*}Requirements*:
* states: 'Answered', 'Unanswered'
* group 'developers'
* type 'Spam'
{note}{td}
{td:class=confluenceTd}Set the state to 'Answered' if a new comment is posted: {code}rule issue is answered if new comment posted
when comments.added.isNotEmpty {
if (comments.last.author.isInGroup("developers")) {
State = {Answered};
} else {
State = {Unanswered};
}
}{code}
{expand:title=Issue becomes answered if a new comment posted}
{code}rule issue is answered if new comment posted
when comments.added.isNotEmpty {
if (comments.last.author.isInGroup("developers")) {
State = {Answered};
} else {
State = {Unanswered};
}
}{code}
{expand}
{expand:title=SPAM issues become answered}
{code}rule resolve on mark as SPAM
when Type.becomes({Spam}) {
State = {Answered};
}{code}
{expand}
{expand:title=Assign an issue to the author of the last 'developer group' comment}
{code}rule set assignee on developer comment
when comments.added.isNotEmpty && comments.added.last.author.isInGroup("developers")
&& comments.added.last.author != Assignee {
Assignee = comments.added.last.author;
}
{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Commentwontfixstate}Comment Won't Fix State {anchor} [(download zip file)|^Comment Won't Fix State.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Require adding a comment when changing state to Won't fix.
{td}
{td:class=confluenceTd|width=65%}
Require adding comment when moving to Won't Fix state
{code}rule Comment when won't fix
when issue.State.becomes({Won't fix}) {
assert comments.added.isNotEmpty: "Please comment!";
}
{code}
{td}
{tr}
{table}
h2. Default Workflows
This table contains workflows included in YouTrack by default.
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:additionalnotifications}Additional Notifications {anchor} [(download zip file) |^jetbrains-youtrack-additionalNotifications.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Notify the previous assignee when issue is reassigned.
{td}
{td:class=confluenceTd|width=65%}
Notify the previous issue assignee
{code}rule Notify on issue reassign
when isReported() && Assignee.changed {
var oldAssignee = Assignee.oldValue;
var newAssignee = Assignee.login;
if (newAssignee == null) {
newAssignee = "nobody";
}
if (oldAssignee != null) {
oldAssignee.notify("[YouTrack, Reassigned] Issue " + getId() + ": " + summary,
"Issue " + "<a href=\"" + issue.getUrl() + "\">" + issue.getId() + "</a>
was reassigned from you to " + newAssignee);
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Nocommentsforverifiedissues} No Comments for Verified Issues {anchor} [(download zip file)|^No Comments for Verified Issues.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Disable adding comments to a verified issue.
{td}
{td:class=confluenceTd|width=65%}
No Comments for Verified Issues
{code}rule No Comments for Verified Issues
when State == {Verified} && !State.becomes({Verified}) {
assert comments.added.isEmpty: "Commenting fixed and verified issues is disabled.";
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:New issue description template}New Issue Description Template {anchor} [(download zip file)|^jetbrains-youtrack-defaultDescription.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Provide external reporters with a description template for New Issue.
{td}
{td:class=confluenceTd|width=65%}
Description template for external users
{code}rule Description template for external users
when !isReported() && description == null {
description = "What are the steps to reproduce the problem?\n" + "1.\n2.\n3.\n\n" +
"What is the expected result?\n\n" + "What happens instead?\n\n" +
"Please provide any additional information below.\n" +
"Attach a screenshot if possible\n";}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Fixing linked issues}{*}Fixing Linked Issues,*{anchor} [(download zip file)|^jetbrains-youtrack-dependencies.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Prohibit moving an issue to Fixed state if it 'depends on' unresolved issue(s).
{td}
{td:class=confluenceTd|width=65%}
Disable fixing issues with unresolved dependencies
{code}rule Do not allow fix issue with unresolved dependencies
when State.becomes({Fixed}) && depends on.isNotEmpty {
for each dep in depends on {
assert dep.State.isResolved: "The issue has unresolved dependencies and cannot be Fixed!";
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Voting for Resolved Issues}Voting for Resolved Issues {anchor} [(download zip file)|^Voting for Resolved Issues.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Disable voting for resolved issues.
{td}
{td:class=confluenceTd|width=65%}
Disable voting for resolved issues
{code}rule jetbrains-youtrack-doNotVoteForResolvedIssue
when isResolved() {
assert !votes.changed: "Voting for resolved issues is disabled.";
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Due Date Management}Due Date Management {anchor} [(download zip file)|^Due Date Management.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Disable submitting an issue without a Due Date set. Notify the issue assignee about overdue issues.
{note}{*}Requirements*: field 'Due date' is attached to project{note}
{td}
{td:class=confluenceTd|width=65%}
Disable submitting an issue without a due date
{code}
rule Don't allow to submit issue without Due Date set
when !issue.isReported() {
Due date.required("Please set the Due Date!");
}{code}
{expand:title=Notify an issue assignee about overdue issues}
{code}
schedule rule Notify assignee about overdue issues
daily at 10 : 00 : 00 [now > issue.Due Date] {
var user;
if (issue.Assignee == null) {
user = issue.project.leader;
} else {
user = issue.Assignee;
}
user.notify("Issue is overdue", "Please see the issue: " + issue.getId());
}
{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Intelligent Duplicates Management}Intelligent Duplicates Management {anchor} [(download zip file)|^jetbrains-youtrack-duplicates.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
* Automatically relink all duplicates directly to the main duplicated issue.
\\
* User can't remove all duplicate links from issue in Duplicate state.
\\
* Set issue state to duplicate when a Duplicate link is added.
\\
* Raise the Priority of a duplicated issue.
\\
* Require adding a duplicate link when changing issue state to Duplicate.
{td}
{td:class=confluenceTd|width=65%}
Users cannot remove all duplicate links from issue in state Duplicate
{code}
rule User can't remove all duplicate links from issue in state Duplicate
when issue.State == {Duplicate} && duplicates.removed.isNotEmpty {
assert duplicates.isNotEmpty: "Issue in Duplicate state should have at least one duplicate.";
}
{code}
{expand:title=Set issue state to Duplicate when duplicate link is added}
{code}
rule When have duplicate link set issue state to Duplicate
when duplicates.added.isNotEmpty && State != {Duplicate} {
if (State.canBeUpdatedBy(loggedInUser)) {
State = {Duplicate};
}
}
{code}
{expand}
{expand:title=Require adding a link to the main issue when issue state is set to Duplicate}
{code}
rule When issue becomes duplicate it must have duplicate link
when State.becomes({Duplicate}) {
duplicates.required("Add link to duplicate issue.");
}
{code}
{expand}
{expand:title=Don't allow duplicates to form a tree structure with height greater than one (target)}
{code}
rule Don't allow duplicates to form a tree structure with height greater than one (target)
when issue.is duplicated by.added.isNotEmpty {
info("Processing duplicate-target issue " + issue.getId());
var target = issue.duplicates.first;
if (target != null) {
// ban several duplicates for one issue
issue.duplicates.clear;
issue.duplicates.add(target);
for each incoming in issue.is duplicated by {
incoming.duplicates.remove(issue);
if (incoming == target) {
info("Target cycle resolved for issue " + target.getId());
} else {
incoming.duplicates.add(target);
}
}
}
}
{code}
{expand}
{expand:title=Don't allow duplicates to form a tree structure with height greater than one (source)}
{code}
rule Don't allow duplicates to form a tree structure with height greater than one (source)
when issue.duplicates.added.isNotEmpty {
info("Processing duplicate-source issue " + issue.getId());
var target = issue.duplicates.added.first;
for each incoming in issue.is duplicated by {
incoming.duplicates.remove(issue);
if (incoming == target) {
info("Source cycle resolved for issue " + target.getId());
} else {
incoming.duplicates.add(target);
}
}
}
{code}
{expand}
{expand:title=When 'is duplicated by' link is added, try to raise priority}
{code}
rule When is duplicated by link is added try to raise priority
when issue.is duplicated by.added.isNotEmpty {
var priorityOrdinal = issue.Priority.ordinal;
for each source in issue.is duplicated by.added {
var sourcePriority = source.Priority;
var sourcePriorityOrdinal = sourcePriority.ordinal;
if (priorityOrdinal > sourcePriorityOrdinal) {
priorityOrdinal = sourcePriorityOrdinal;
issue.Priority = sourcePriority;
}
}
}
{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Deny certain field values combination}Deny Certain Combination of Field Values {anchor} [(download zip file)|^jetbrains-youtrack-issuePropertiesCombinations.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Disable a certain combination of field values.
{td}
{td:class=confluenceTd|width=65%}
Denied Combinations
{code}rule deniedCombinations
when <issue created or updated> {
assert !(Priority == {Show-stopper} && State == {Submitted}):
"Denied fields combination detected (Submitted Show-stopper)";
assert !(State == {Open} && Assignee == null):
"Denied fields combination detected (Open Unassigned)";
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Notify issue reporter to approve the fix}Notify Reporter To Approve Fix {anchor} [(download zip file)|^jetbrains-youtrack-notifyReporterToApproveFix.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
* Send a specific notification to the reporter to approve the fix.
* Introduce State life-cycle including reporter verification.
{td}
{td:class=confluenceTd|width=65%}
Send a notification to the reporter to approve fix
{code}
rule Send specific notification to reporter to approve fix
when State.becomes({Pending verification}) {
reporter.notify("Please approve fix for the issue" + getId(), "You have reported issue"
+ getId() + "Please review the applied fix for your issue and set the appropriate state.");
Assignee = reporter;
}
{code}
{expand:title=State life-cycle with verified by reporter value}
{code}
statemachine State lifecycle with verification by reporter for field State {
initial state Submitted {
on Open[always] do {<define statements>} transit to Open
}
state Open {
on Fix[always] do {<define statements>} transit to Fixed
}
state Fixed {
enter {
Fixed in build.required("Please set the 'Fixed in build' value.");
}
on Send for verification[always] do {<define statements>} transit to Pending verification
}
state Pending verification {
enter {
Assignee = reporter;
reporter.notify("Please approve fix for the issue" + getId(), "You have reported issue"
+ getId() + "Please review the applied fix for the issue and set the appropriate state.");
}
on Approve[always] do {<define statements>} transit to Verified
on Reopen[always] do {<define statements>} transit to Open
}
state Verified {
on Reopen[always] do {<define statements>} transit to Open
}
}
{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Fix Version and Fixed State}Fix Version and Fixed State {anchor} [(download zip file)|^jetbrains-youtrack-setFixVersions.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
* Require Fix Version value for Fixed state.
* Set an issue state to Fixed when Fixed Version is added.
{td}
{td:class=confluenceTd|width=65%}
Require Fix Version value for Fixed state
{code}
rule Assert Fix version is set for Fixed issues
when State.becomes({Fixed}) {
Fix versions.required("Please set the 'Fix versions' field!");
}{code}
{expand:title=Set Fixed state on specifying Fixed in version}
{code}rule Set Fixed state on specifying Fixed in version
when State != {Fixed} && Fix versions.added.isNotEmpty {
State = {Fixed};
}{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Set assignee to subsystem owner}Set Assignee to Subsystem Owner {anchor} [(download zip file)|^jetbrains-youtrack-subsystemAssignee.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Set subsystem owner as assignee for new issues when subsystem is specified.
{td}
{td:class=confluenceTd|width=65%}
Set subsystem owner as assignee for new issues
{code}
rule Set subsystem owner as assignee for new issues
when Assignee == null && (Subsystem.changed || project.changed) {
if (issue.Subsystem != null) {
issue.Assignee = issue.Subsystem.owner;
}
}
{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Time Management }Time Management {anchor} [(download zip file)|^Time Management.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
* Change issue state in time automatically.
* Notify an issue assignee, project lead or subsystem owner when issue life-cycle is violated.
{note}{*}Requirements*:
* subsystem 'Support'
* state 'Overdue'
* state 'Wait for reproduce'
* state 'Approved'{note}
{td}
{td:class=confluenceTd|width=65%}
{expand:title=Time Management for State field}{code}
statemachine Time Management for field State {
initial state Submitted {
enter {
Subsystem = {Support};
Assignee.required("Responsible support engineer is required!");
}
in 1 hour[always] do {<define statements>} transit to Overdue
on reproducing[always] do {<define statements>} transit to Open
on incomplete[always] do {<define statements>} transit to Incomplete
}
state Overdue {
enter {
var user;
if (Assignee != null) {
user = Assignee;
} else if (Subsystem.owner != null) {
user = Subsystem.owner;
} else {
user = project.leader;
}
user.notify("Acknowledgement needed", "Issue" + getId() + "is waiting for acknowledgement.");
}
on incomplete[always] do {<define statements>} transit to Incomplete
on reproducing[always] do {<define statements>} transit to Open
}
state Open {
in 4 hours[always] do {<define statements>} transit to Wait for reproduce
on approved[always] do {<define statements>} transit to Approved
on incomplete[always] do {<define statements>} transit to Incomplete
on can't reproduce[always] do {<define statements>} transit to Can't Reproduce
}
state Wait for reproduce {
in 1 day[always] do {
var user;
if (Subsystem.owner != null) {
user = Subsystem.owner;
} else {
user = project.leader;
}
user.notify("Issue is not reproduced in 1 day",
"Issue " + getId() + " is still waiting for reproduction steps.");
// Notify sales?
}
in 3 days[always] do {
var user;
if (Subsystem.owner != null) {
user = Subsystem.owner;
} else {
user = project.leader;
}
user.notify("Issue is not reproduced in 4 days", "Issue " + getId() +
" is not reproduced. You may want to visit the customer at their site.");
// Notify sales?
}
on approved[always] do {<define statements>} transit to Approved
on can't reproduce[always] do {<define statements>} transit to Can't Reproduce
on incomplete[always] do {<define statements>} transit to Incomplete
}
state Can't Reproduce {
on reopen[always] do {<define statements>} transit to Open
}
state Incomplete {
on reopen[always] do {<define statements>} transit to Open
}
state Approved {
enter {
Assignee.required(fail message);
}
on fixed[always] do {<define statements>} transit to Fixed
on obsolete[always] do {<define statements>} transit to Obsolete
}
state Fixed {
on verify[always] do {<define statements>} transit to Verified
on reopen[always] do {<define statements>} transit to Open
}
state Obsolete {
on reopen[always] do {<define statements>} transit to Open
}
state Verified {
on reopen[always] do {<define statements>} transit to Open
}
}
{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Fix in Build Management}Fix in Build Management {anchor} [(download zip file)|^Fix in Build Management.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
* Clear Fixed in build value when issue becomes unresolved.
* Copy Fixed in Build value from duplicate.
* Set Fixed in Build value for duplicate when it's specified in the main issue.
{td}
{td:class=confluenceTd|width=65%}
Clear Fixed in build on issue unresolve
{code}rule Clear Fixed in build on issue unresolve
when issue.isReported() && !(issue.State.isResolved)
&& issue.State.oldValue != null && issue.State.oldValue.isResolved {
if (issue.Fixed in build != null) {
issue.Fixed in build = null;
}
}
{code}
{expand:title=Copy Fixed in build to duplicate from the main issue}
{code}
rule Copy Fixed in build from duplicated issue
when State.becomes({Duplicate}) && duplicates.isNotEmpty {
for each duplicatedIssue in duplicates {
if (duplicatedIssue.project == project && duplicatedIssue.Fixed in build != null) {
Fixed in build = duplicatedIssue.Fixed in build;
break;
}
}
}
{code}
{expand}
{expand:title=Copy Fixed in build to duplicates when it is set in the main issue}
{code}
rule Copy Fixed in build to duplicate issues when it is set
when Fixed in build.changed && Fixed in build != null {
var duplicatedBy = is duplicated by;
for each duplicate in duplicatedBy {
if (duplicate.project == project) {
duplicate.Fixed in build = Fixed in build;
}
}
}
{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Fixed in Build for Won't Fix}Fixed in Build for Won't Fix {anchor} [(download zip file)|^Fixed in Build for Won't Fix.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Set Fixed in build to 'Never' for 'Won't fix' state.{note}{*}Requirements*: fixed in build 'Never' {note}
{td}
{td:class=confluenceTd|width=65%}
Set 'Never' fixed in build for 'Won't fix' state
{code}
rule Set 'Never' fixed in build for 'Won't fix' state
when State.becomes({Won't fix}) {
Fixed in build = {Never};
}
{code}
{td}
{tr}
{table}
{section}
{column:width=60%}
{tip:title=To use a ready workflow file in your YouTrack, please:} !screenshot1.png|align=right,thumbnail,border=1!
* Download a workflow zip file.
* If you are a project administrator, then import the workflow to your project in Administration > Projects > _project_ > Workflow tab.
* If you have system administrator permissions, then import the workflow to your YouTrack in Administration \-> Workflow page and attach it to one or multiple projects.
You can also try any of the custom workflows listed below in our [YouTrack InCloud test instance|http://workflows.myjetbrains.com/youtrack/]. All registered users get project admin permissions.
{tip}
{column}
{column:width=40%}
{panel:bgColor=#FFFFFF}(i) *Continue*
- New to workflow in YouTrack? Please take a look at the [workflow guide|http://confluence.jetbrains.net/display/YTD3/Workflow+Guide].
- Ready to create your own workflow rule? Use [YouTrack Workflow Editor|http://www.jetbrains.com/youtrack/download/index.html].
- Want to share your workflow? [Contact us|mailto:youtrack-feedback@jetbrains.com] or [create an issue|http://youtrack.jetbrains.com/dashboard/JT].
{panel}
{column}
{section}
h2. Custom Workflow Examples
This table contains various use cases and programmed workflow rules ready to be used in your YouTrack.
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Votes and watchers} Votes and Watchers
{anchor} [(download zip file)|^Votes and Watchers.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Add the project lead to an issue watchers list once the issue collects a certain number of votes.{td}
{td:class=confluenceTd}{code}rule watch if lots of votes
when votes == 10 {
project.leader.watchIssue(issue);
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Default visibility group} Default Visibility Group {anchor}[(download zip file)|^default visibility group.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Set the default visibility group for all new issues in a project.{note}{*}Requirements*: group 'Reporters'{note}{td}
{td:class=confluenceTd}{code}rule set permitted group for new issues
when !isReported() && reporter.isInGroup("Reporters") {
permittedGroup = {developers};
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:no comments added}User Comment(s) Awaiting Reply Notification {anchor} [(download zip file)|^User Comment(s) Awaiting Reply notification.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}If an issue has a recent non-developer comment, send a notification to a specific user (support team member). This check is performed at a given time (daily at 12pm by default). {note}{*}Requirements*: group 'developers-team'{note}{td}
{td:class=confluenceTd}{code}schedule rule Notify on no comments during time period
daily at 12 : 00 : 00 [!issue.State.isResolved] {
if (comments.last != null && !comments.last.author.isInGroup("developers-team")) {
project.leader.notify("[Youtrack, Notifier] Issue " +
getId() + " needs your attention",
"Issue " + summary + " has a recent comment added by a non-developer.");
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:reopenduplicate} Reopen Duplicate {anchor}[(download zip file)|^Reopen if no duplicates.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Reopen an issue if all duplicate links are removed.{td}
{td:class=confluenceTd}{code}rule Reopen issue on removing last duplicates link
when duplicates.changed && duplicates.isEmpty {
State = {Open};
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Clearfixforversion} Clear 'Fix For Version' {anchor}[(download zip file)|^Clear fix for version on reopen.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Clear 'Fix for version' field when an issue is reopened.{td}
{td:class=confluenceTd}{code}rule Clear fix for version on issue reopen
when State.oldValue != null && State.oldValue.isResolved && !State.isResolved {
Fix versions.clear;
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Unblocked state when no blockers}'Unblocked' State {anchor}[(download zip file)|^Unblocked state when no blockers.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Provide an ability to have the issue unblocked (denoted with a "is blocked by" - "blocks" relationship) when the last remaining blocker is resolved and the issue has no remaining blockers.{note}{*}Requirements*: link types 'blocks' - 'is blocked by' {note}{td}
{td:class=confluenceTd}{code}rule unblock on blockers resolving
when issue.State.isResolved && !issue.State.oldValue.isResolved {
for each dependant in issue.blocks {
if (dependant.is blocked by.first == dependant.is blocked by.last) { //e.g. it's source issue
dependant.State = {Unblocked};
}
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Semi-automatic 'Wait for reply' state management} Semi-Automatic 'Wait for Reply' State Management
{anchor} [(download zip file)|^Semi-automatic 'Wait for reply' state management.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}When a comment from an external user is required, internal user sets issue state to 'Wait for reply' manually. When a comment is added, issue is automatically reopened. Assignee gets a notification if no comment is added within 5 days. {note}{*}Requirements*:
* state 'Wait for Reply'
* group 'developers'
{note}{td}
{td:class=confluenceTd}Statemachine with 'Waiting for reply' state: {code}statemachine States for field State {
initial state Submitted {
on wait[always] do {<define statements>} transit to Wait for Reply
...
}
state Open {
on wait[always] do {<define statements>} transit to Wait for Reply
...
}
state Wait for Reply {
on fix[always] do {<define statements>} transit to Fixed
on open[always] do {<define statements>} transit to Open
on in progress[always] do {<define statements>} transit to In Progress
...
}
}{code}
{expand:title=Changes issue state from 'Wait for reply' to 'Open' when comment is added by an external user}
{code}
rule Wait for reply: reopen on answer
when State == {Wait for Reply} && comments.added != null && comments.added.isNotEmpty &&
comments.added.last != null && !comments.added.last.author.isInGroup("developers") {
State = {Open};
}
{code}
{expand}
{expand:title=Notify assignee if no answer is received within 5 days}
{code}
schedule rule Wait for reply reminder
daily at 12 : 05 : 00 [issue.State == {Wait for Reply}] {
var lastComment = comments.last;
if (lastComment != null && lastComment.author.isInGroup("developers")
&& lastComment.created + 5 days < now) {
if (Assignee != null) {
Assignee.notify("[Youtrack, 'Wait for reply' reminder] Unanswered comment within 5 days",
"Hi, " + Assignee.fullName + "! <br><br> Issue <a href=\"" + getUrl() + "\">" + getId()
+ " " + summary + "</a>" + " is in state \"Wait for reply\" more than 5 days."
+ " It has unanswered comment:<br><b>" + lastComment.text
+ "</b><br><br> Please answer or resolved issue.<br><br>"
+ "<p style=\"color: gray;font-size: 12px;margin-top: 1em;border-top: 1px solid #D4D5D6\">"
+ "Sincerely yours, YouTrack" + "</p>");
}
}
}
{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:notifyuser} Notify User Mentioned in Comment
{anchor}[(download zip file)|^Notify Mentioned in Comment User.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Send notification to a user mentioned in a comment.{td}
{td:class=confluenceTd}{code}schedule rule send notification to a user mentioned in comment
daily at 12 : 00 : 00 [<expression>] {
var mentionedUser;
for each comment in comments {
if (comment.text.contains("remind", opts) && comment.created + 1 day > now) {
mentionedUser = comment.text.substringBetween("remind @", " ");
}
}
for each user in permittedGroup.getUsers() {
if (user.login.eq(mentionedUser, opts)) {
user.notify("You were mentioned", "Your name was mentioned in a comment in " + getId());
}
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Managewaitingforreply}'Waiting for Reply' Tag Management {anchor}[(download zip file)|^Manage tag Waiting for reply.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd} Replace 'waiting for reply' tag with 'answered' when a developer adds a comment.{note}{*}Requirements*: group 'developers'{note}{td}
{td:class=confluenceTd}{code}rule Set tag waiting for reply (answered)
when comments.added.isNotEmpty {
if (!comments.added.last.author.isInGroup("developers")) {
removeTag("waiting for reply");
addTag("waiting for reply (answered?)");
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Display the last submitted date for duplicated exceptions}Last Submitted Date for Duplicate Exceptions
{anchor}[(download zip file)|^Display the last submitted date for duplicated exceptions.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd} Show the last submitted date for the main issue when a new 'is duplicated by' link is added to it.{note}{*}Requirements*: field 'Last duplicate' of 'date' type{note}{td}
{td:class=confluenceTd}{code}rule Display last submitted date for duplicated exceptions
when issue.is duplicated by.added.isNotEmpty {
var lastDuplicate = issue.Last duplicate;
for each source in issue.is duplicated by.added {
var addedDuplicate = source.created;
if (lastDuplicate < addedDuplicate) {
lastDuplicate = addedDuplicate;
issue.Last duplicate = lastDuplicate;
}
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Restrictionsonfieldchanging} Restrict Fields Changing {anchor}[(download zip file)|^Restrict Fields Changing.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd} Make some fields editable only by the issue assignee. Display an error message to any other user who attempts to modify the fields. {td}
{td:class=confluenceTd}{code}rule assert assignee can change field
when Priority.changed || Fix versions.changed || Fixed in build.changed {
assert loggedInUser == Assignee: "Only the Issue Assignee can set this field";
}
{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Duplicateissuesvisibilitygroup}Duplicate Issues Only With Same Visibility Groups
{anchor}[(download zip file)|^Duplicate Issues Only With Same Visibility Group.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Disable duplicating issues with different visibility groups.{td}
{td:class=confluenceTd}{code}rule Different duplicates issues visibility
when issue.duplicates.added.isNotEmpty {
for each duplicate in issue.duplicates.added {
assert permittedGroup == duplicate.permittedGroup:
"Issues can only be duplicated if they have the same visibility groups";
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:openassignedissues} 'Open' Assigned Issues {anchor}[(download zip file)|^Set assignee on open issue.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Change an issue state to Open when an assignee is added.{td}
{td:class=confluenceTd}{code}rule Open issue on set Assignee
when Assignee != null && Assignee.changed && Assignee.oldValue == null {
State = {Open};
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Testers process}QA Verification Process
{anchor}[(download zip file)|^QA Verification Process.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}
* Notifies a user when he/she is set to verify an issue
* Requires setting 'Verified in build' value when changing state to 'Verified', and sets current user name for 'Verified by' field value.
* Requires adding a comment when changing state to 'W/O verification'.
{note}{*}Requirements*:
* field 'Verified by' of 'user' type
* field 'Verified in build' of 'build' type
* state 'W/O verification'{note}
{td}
{td:class=confluenceTd}{code}rule Notify on set Verify by user
when isReported() && Verified by.changed {
if (Verified by != null && loggedInUser != Verified by) {
Verified by.notify("[Youtrack, Verify by] You became the 'Verified by' user for issue "
+ issue.getId() + " " + issue.summary, "Hi, " + Verified by.fullName +
"! <br><br> You became the 'Verified by' user for issue <a href=\"" + issue.getUrl()
+ "\">" + issue.getId() + "</a>" + " " + " <a href=\"" + issue.getUrl() + "\">"
+ issue.summary + "</a>" + "<br> Please verify it or close as \"W/O verification\".<br><br>"
+ "<p style=\"color: gray;font-size: 12px;margin-top: 1em;border-top: 1px solid #D4D5D6\">"
+ "Best regards, YouTrack" + "</p>");
}
}{code}
{expand:title=Set 'verified in build' value}
{code}rule Set Verified State on specifying Verified In Build
when Verified in build != null && State != {Verified} {
State = {Verified};
}{code}
{expand}
{expand:title=Set 'verified by' user name when adding 'verified in build' value}
{code}
rule Set verified by user on specifying Verified in build
when Verified in build != null && Verified in build.changed {
Verified by = loggedInUser;
}{code}
{expand}
{expand:title=States machine}
{code}
...
state W/O verification {
enter {
Verified by = loggedInUser;
}
on reopen[always] do {<define statements>} transit to Open
}
state Verified {
on reopen[always] do {<define statements>} transit to Open
enter {
Verified in build.required("Specify 'Verified in build'");
Verified by = loggedInUser;
}
exit {
Verified in build = null;
}
}
...
{code}
{expand}
{expand:title=Require adding a new comment when changing an issue state}
{code}rule Require comment on w/o verification
when State.becomes({W/O verification}) {
assert comments.added.isNotEmpty: "Add a comment about the missing details";
}{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Advanced states machine} Advanced States Machine {anchor}[(download zip file)|^Advanced States Machine.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Manage states transitions.{note}{*}Requirements*:
\\
* field 'Verified by' user of type 'user'
* field 'Verified in build' of type 'build'
* value 'W/O verification in bundle 'State'
* state 'Wait for Reply'
{note}
{td}
{td:class=confluenceTd}
{expand:title=Lifecycle of the State field}
{code}statemachine States for field State {
initial state Submitted {
in 3 days[State == {Submitted}] do {
var user = Assignee;
if (user == null) {
user = project.leader;
}
user.notify("[Youtrack, State reminder] Issue " + issue.getId()
+ " is still Submitted: " + issue.summary, "Hi, " + user.fullName
+ "! <br><br> Issue <a href=\"" + issue.getUrl() + "\">" + issue.getId()
+ "</a>" + " is still in Submitted state:" + " <a href=\"" + issue.getUrl()
+ "\">" + issue.summary + "</a>"
+ "<br> Please start working on it or specify a more accurate state.<br><br>"
+ "<p style=\"color: gray;font-size: 12px;margin-top: 1em;border-top: 1px solid #D4D5D6\">"
+ "Sincerely yours, YouTrack" + "</p>");
}
on fix[always] do {<define statements>} transit to Fixed
on open[always] do {<define statements>} transit to Open
on in progress[always] do {<define statements>} transit to In Progress
on discuss[always] do {<define statements>} transit to To be discussed
on can't reproduce[always] do {<define statements>} transit to Can't Reproduce
on obsolete[always] do {<define statements>} transit to Obsolete
on duplicate[always] do {<define statements>} transit to Duplicate
on as designed[always] do {<define statements>} transit to As designed
on invalidate[always] do {<define statements>} transit to Invalid
}
state Open {
on in progress[always] do {<define statements>} transit to In Progress
on discuss[always] do {<define statements>} transit to To be discussed
on fix[always] do {<define statements>} transit to Fixed
on obsolete[always] do {<define statements>} transit to Obsolete
on duplicate[always] do {<define statements>} transit to Duplicate
on can't reproduce[always] do {<define statements>} transit to Can't Reproduce
on as designed[always] do {<define statements>} transit to As designed
on invalid[always] do {<define statements>} transit to Invalid
on wait[always] do {<define statements>} transit to Wait for Reply
}
state Reopened {
on open[always] do {<define statements>} transit to Open
}
state Obsolete {
on reopen[always] do {<define statements>} transit to Open
}
state Duplicate {
on reopen[always] do {<define statements>} transit to Open
}
state In Progress {
on reopen[always] do {<define statements>} transit to Open
on fix[always] do {<define statements>} transit to Fixed
on can't reproduce[always] do {<define statements>} transit to Can't Reproduce
on obsolete[always] do {<define statements>} transit to Obsolete
on as designed[always] do {<define statements>} transit to As designed
}
state To be discussed {
on in progress[always] do {<define statements>} transit to In Progress
on duplicate[always] do {<define statements>} transit to Duplicate
on obsolete[always] do {<define statements>} transit to Obsolete
}
state Can't Reproduce {
on reopen[always] do {<define statements>} transit to Open
}
state As designed {
on reopen[always] do {<define statements>} transit to Open
}
state Won't fix {
on reopen[always] do {<define statements>} transit to Open
}
state Invalid {
on reopen[always] do {<define statements>} transit to Open
}
state Incomplete {
on reopen[always] do {<define statements>} transit to Open
}
state Fixed {
on reopen[always] do {<define statements>} transit to Open
on verify[always] do {<define statements>} transit to Verified
on can't verify[always] do {<define statements>} transit to W/O verification
}
state W/O verification {
enter {
Verified by = loggedInUser;
}
on reopen[always] do {<define statements>} transit to Open
}
state Verified {
on reopen[always] do {<define statements>} transit to Open
enter {
Verified in build.required("Specify verified in build");
Verified by = loggedInUser;
}
exit {
Verified in build = null;
}
}
state Wait for Reply {
on fix[always] do {<define statements>} transit to Fixed
on open[always] do {<define statements>} transit to Open
on in progress[always] do {<define statements>} transit to In Progress
on discuss[always] do {<define statements>} transit to To be discussed
on can't reproduce[always] do {<define statements>} transit to Can't Reproduce
on obsolete[always] do {<define statements>} transit to Obsolete
on duplicate[always] do {<define statements>} transit to Duplicate
on as designed[always] do {<define statements>} transit to As designed
on invalidate[always] do {<define statements>} transit to Invalid
}
}{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Duty} 'On Duty' Management
{anchor}[(download zip file)|^'On Duty' Management.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Manage duties. Requires a separate project to store duties. Duty is stored in the 'Assignee' field. The chain of duties is implemented as linked by 'is required for' link issues.
{note}{*}Requirements*:
* states: 'On duty', 'Active', 'On a rest'
* new field 'day of week' of type 'integer'
{note}{td}
{td:class=confluenceTd}
{expand:title=Set today's person-on-duty based on 'is required for' link}
{code}schedule rule Set Today's Duty Based on is required for
daily at 10 : 00 : 00 [issue.State == {On duty} && issue.Day of week != 5
&& issue.Day of week != 6] {
if (updated + 10 seconds < now) {
// set new duty
var nextDuty = is required for.first;
var stop = 0;
if (nextDuty == null || nextDuty.Assignee == null) {
project.leader.notify("Duty",
"Duties chain should be implemented via 'is required for' links,
and 'Duty' store field must be non-empty.",
true);
}
while (stop != -1 && stop < 50) {
if (nextDuty.State == {Active}) {
nextDuty.Day of week = Day of week;
nextDuty.State = {On duty};
Assignee.notify("Duty: You are no longer on duty", Assignee.fullName + ", "
+ " you just came off duty.<br><br>The new person on duty is "
+ nextDuty.Assignee.fullName + ".", true);
State = {Active};
debug("Duty changed to: " + nextDuty.Assignee.fullName);
stop = -1;
} else if (nextDuty.State == {On a rest}) {
nextDuty = nextDuty.is required for.first;
stop = stop + 1;
} else {
project.leader.notify("Duty", "States should be only 'Active' or 'On a rest'", true);
stop = -1;
}
}
nextDuty.Assignee.notify("Duty: You are on duty", nextDuty.Assignee.fullName + ", "
+ "you are on duty today.<br><br> Don't forget to process "
+ "<a href=\"http://youtrack.jetbrains.net/issues/JT?"
+ "q=%23Unassigned+%23Unresolved+created%3A+Yesterday%2C+Today+sort+by%3A+Priority+\">"
+ "any new issues in YouTrack." + "</a>" + "<br>"
+ "<p style=\"color: gray;font-size: 12px;margin-top: 1em;border-top: 1px solid #D4D5D6\">"
+ "Sincerely yours, YouTrack" + "</p>", true);
var curIssue = nextDuty;
var preventInfinite = 0;
while (curIssue != issue && preventInfinite < 50) {
if (curIssue.State == {Active}) {
assert curIssue.Assignee != null: "Assignee cannot not be null!";
curIssue.Assignee.notify("Duty: Person on duty has changed", "Today's person on duty is "
+ nextDuty.Assignee.fullName + ".", true);
}
curIssue = curIssue.is required for.first;
preventInfinite = preventInfinite + 1;
}
}
}{code}
{expand}
Manage day of week:
{code}schedule rule Set day of week
daily at 00 : 00 : 00 [issue.State == {On duty}] {
Day of week = (Day of week + 1) % 7;
}{code}
Disable dropping Assignee:
{code}rule Assert Assignee not null
when Assignee.changed {
assert Assignee != null: "Assignee cannot be null!";
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Warn on issue visibility changed}Change Visibility Warning
{anchor}[(download zip file)|^Warn on issue visibility changed.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Warn on change of issue visibility. {note}{*}Requirements*: group 'developers'
{note}{td}
{td:class=confluenceTd}{code}rule issueVisibility
when permittedGroup.changed {
var msg = "To post non-public data, please make the issue public
instead and add protected attachments and/or comments.";
assert loggedInUser.isInGroup("developers"): msg;
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Assignee and multiple subsystems} Assignee and Multiple Subsystems {anchor}[(download zip file)|^Assignee and multiple subsystems.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}When a Subsystem is changed, set issue assignee to the subsystem owner.{note}{*}Requirements*: field 'Subsystems' of type 'ownedField\[*\]'{note}{td}
{td:class=confluenceTd}{code}rule Set assignee from Subsystems
when Subsystems.changed {
var owner = null;
for each subsystem in Subsystems {
if (owner == null) {
owner = subsystem.owner;
} else if (subsystem.owner != owner) {
owner = null;
break;
}
}
if (owner != null && Assignee == null) {
Assignee = owner;
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Feedbackmanagement} Feedback Management {anchor}[(download zip file)|^Feedback management.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd}Handle feedback getting from mailbox. {note}{*}Requirements*:
* states: 'Answered', 'Unanswered'
* group 'developers'
* type 'Spam'
{note}{td}
{td:class=confluenceTd}Set the state to 'Answered' if a new comment is posted: {code}rule issue is answered if new comment posted
when comments.added.isNotEmpty {
if (comments.last.author.isInGroup("developers")) {
State = {Answered};
} else {
State = {Unanswered};
}
}{code}
{expand:title=Issue becomes answered if a new comment posted}
{code}rule issue is answered if new comment posted
when comments.added.isNotEmpty {
if (comments.last.author.isInGroup("developers")) {
State = {Answered};
} else {
State = {Unanswered};
}
}{code}
{expand}
{expand:title=SPAM issues become answered}
{code}rule resolve on mark as SPAM
when Type.becomes({Spam}) {
State = {Answered};
}{code}
{expand}
{expand:title=Assign an issue to the author of the last 'developer group' comment}
{code}rule set assignee on developer comment
when comments.added.isNotEmpty && comments.added.last.author.isInGroup("developers")
&& comments.added.last.author != Assignee {
Assignee = comments.added.last.author;
}
{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Commentwontfixstate}Comment Won't Fix State {anchor} [(download zip file)|^Comment Won't Fix State.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Require adding a comment when changing state to Won't fix.
{td}
{td:class=confluenceTd|width=65%}
Require adding comment when moving to Won't Fix state
{code}rule Comment when won't fix
when issue.State.becomes({Won't fix}) {
assert comments.added.isNotEmpty: "Please comment!";
}
{code}
{td}
{tr}
{table}
h2. Default Workflows
This table contains workflows included in YouTrack by default.
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:additionalnotifications}Additional Notifications {anchor} [(download zip file) |^jetbrains-youtrack-additionalNotifications.zip]
{th}{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Notify the previous assignee when issue is reassigned.
{td}
{td:class=confluenceTd|width=65%}
Notify the previous issue assignee
{code}rule Notify on issue reassign
when isReported() && Assignee.changed {
var oldAssignee = Assignee.oldValue;
var newAssignee = Assignee.login;
if (newAssignee == null) {
newAssignee = "nobody";
}
if (oldAssignee != null) {
oldAssignee.notify("[YouTrack, Reassigned] Issue " + getId() + ": " + summary,
"Issue " + "<a href=\"" + issue.getUrl() + "\">" + issue.getId() + "</a>
was reassigned from you to " + newAssignee);
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Nocommentsforverifiedissues} No Comments for Verified Issues {anchor} [(download zip file)|^No Comments for Verified Issues.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Disable adding comments to a verified issue.
{td}
{td:class=confluenceTd|width=65%}
No Comments for Verified Issues
{code}rule No Comments for Verified Issues
when State == {Verified} && !State.becomes({Verified}) {
assert comments.added.isEmpty: "Commenting fixed and verified issues is disabled.";
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:New issue description template}New Issue Description Template {anchor} [(download zip file)|^jetbrains-youtrack-defaultDescription.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Provide external reporters with a description template for New Issue.
{td}
{td:class=confluenceTd|width=65%}
Description template for external users
{code}rule Description template for external users
when !isReported() && description == null {
description = "What are the steps to reproduce the problem?\n" + "1.\n2.\n3.\n\n" +
"What is the expected result?\n\n" + "What happens instead?\n\n" +
"Please provide any additional information below.\n" +
"Attach a screenshot if possible\n";}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Fixing linked issues}{*}Fixing Linked Issues,*{anchor} [(download zip file)|^jetbrains-youtrack-dependencies.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Prohibit moving an issue to Fixed state if it 'depends on' unresolved issue(s).
{td}
{td:class=confluenceTd|width=65%}
Disable fixing issues with unresolved dependencies
{code}rule Do not allow fix issue with unresolved dependencies
when State.becomes({Fixed}) && depends on.isNotEmpty {
for each dep in depends on {
assert dep.State.isResolved: "The issue has unresolved dependencies and cannot be Fixed!";
}
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Voting for Resolved Issues}Voting for Resolved Issues {anchor} [(download zip file)|^Voting for Resolved Issues.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Disable voting for resolved issues.
{td}
{td:class=confluenceTd|width=65%}
Disable voting for resolved issues
{code}rule jetbrains-youtrack-doNotVoteForResolvedIssue
when isResolved() {
assert !votes.changed: "Voting for resolved issues is disabled.";
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Due Date Management}Due Date Management {anchor} [(download zip file)|^Due Date Management.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Disable submitting an issue without a Due Date set. Notify the issue assignee about overdue issues.
{note}{*}Requirements*: field 'Due date' is attached to project{note}
{td}
{td:class=confluenceTd|width=65%}
Disable submitting an issue without a due date
{code}
rule Don't allow to submit issue without Due Date set
when !issue.isReported() {
Due date.required("Please set the Due Date!");
}{code}
{expand:title=Notify an issue assignee about overdue issues}
{code}
schedule rule Notify assignee about overdue issues
daily at 10 : 00 : 00 [now > issue.Due Date] {
var user;
if (issue.Assignee == null) {
user = issue.project.leader;
} else {
user = issue.Assignee;
}
user.notify("Issue is overdue", "Please see the issue: " + issue.getId());
}
{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Intelligent Duplicates Management}Intelligent Duplicates Management {anchor} [(download zip file)|^jetbrains-youtrack-duplicates.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
* Automatically relink all duplicates directly to the main duplicated issue.
\\
* User can't remove all duplicate links from issue in Duplicate state.
\\
* Set issue state to duplicate when a Duplicate link is added.
\\
* Raise the Priority of a duplicated issue.
\\
* Require adding a duplicate link when changing issue state to Duplicate.
{td}
{td:class=confluenceTd|width=65%}
Users cannot remove all duplicate links from issue in state Duplicate
{code}
rule User can't remove all duplicate links from issue in state Duplicate
when issue.State == {Duplicate} && duplicates.removed.isNotEmpty {
assert duplicates.isNotEmpty: "Issue in Duplicate state should have at least one duplicate.";
}
{code}
{expand:title=Set issue state to Duplicate when duplicate link is added}
{code}
rule When have duplicate link set issue state to Duplicate
when duplicates.added.isNotEmpty && State != {Duplicate} {
if (State.canBeUpdatedBy(loggedInUser)) {
State = {Duplicate};
}
}
{code}
{expand}
{expand:title=Require adding a link to the main issue when issue state is set to Duplicate}
{code}
rule When issue becomes duplicate it must have duplicate link
when State.becomes({Duplicate}) {
duplicates.required("Add link to duplicate issue.");
}
{code}
{expand}
{expand:title=Don't allow duplicates to form a tree structure with height greater than one (target)}
{code}
rule Don't allow duplicates to form a tree structure with height greater than one (target)
when issue.is duplicated by.added.isNotEmpty {
info("Processing duplicate-target issue " + issue.getId());
var target = issue.duplicates.first;
if (target != null) {
// ban several duplicates for one issue
issue.duplicates.clear;
issue.duplicates.add(target);
for each incoming in issue.is duplicated by {
incoming.duplicates.remove(issue);
if (incoming == target) {
info("Target cycle resolved for issue " + target.getId());
} else {
incoming.duplicates.add(target);
}
}
}
}
{code}
{expand}
{expand:title=Don't allow duplicates to form a tree structure with height greater than one (source)}
{code}
rule Don't allow duplicates to form a tree structure with height greater than one (source)
when issue.duplicates.added.isNotEmpty {
info("Processing duplicate-source issue " + issue.getId());
var target = issue.duplicates.added.first;
for each incoming in issue.is duplicated by {
incoming.duplicates.remove(issue);
if (incoming == target) {
info("Source cycle resolved for issue " + target.getId());
} else {
incoming.duplicates.add(target);
}
}
}
{code}
{expand}
{expand:title=When 'is duplicated by' link is added, try to raise priority}
{code}
rule When is duplicated by link is added try to raise priority
when issue.is duplicated by.added.isNotEmpty {
var priorityOrdinal = issue.Priority.ordinal;
for each source in issue.is duplicated by.added {
var sourcePriority = source.Priority;
var sourcePriorityOrdinal = sourcePriority.ordinal;
if (priorityOrdinal > sourcePriorityOrdinal) {
priorityOrdinal = sourcePriorityOrdinal;
issue.Priority = sourcePriority;
}
}
}
{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Deny certain field values combination}Deny Certain Combination of Field Values {anchor} [(download zip file)|^jetbrains-youtrack-issuePropertiesCombinations.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Disable a certain combination of field values.
{td}
{td:class=confluenceTd|width=65%}
Denied Combinations
{code}rule deniedCombinations
when <issue created or updated> {
assert !(Priority == {Show-stopper} && State == {Submitted}):
"Denied fields combination detected (Submitted Show-stopper)";
assert !(State == {Open} && Assignee == null):
"Denied fields combination detected (Open Unassigned)";
}{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Notify issue reporter to approve the fix}Notify Reporter To Approve Fix {anchor} [(download zip file)|^jetbrains-youtrack-notifyReporterToApproveFix.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
* Send a specific notification to the reporter to approve the fix.
* Introduce State life-cycle including reporter verification.
{td}
{td:class=confluenceTd|width=65%}
Send a notification to the reporter to approve fix
{code}
rule Send specific notification to reporter to approve fix
when State.becomes({Pending verification}) {
reporter.notify("Please approve fix for the issue" + getId(), "You have reported issue"
+ getId() + "Please review the applied fix for your issue and set the appropriate state.");
Assignee = reporter;
}
{code}
{expand:title=State life-cycle with verified by reporter value}
{code}
statemachine State lifecycle with verification by reporter for field State {
initial state Submitted {
on Open[always] do {<define statements>} transit to Open
}
state Open {
on Fix[always] do {<define statements>} transit to Fixed
}
state Fixed {
enter {
Fixed in build.required("Please set the 'Fixed in build' value.");
}
on Send for verification[always] do {<define statements>} transit to Pending verification
}
state Pending verification {
enter {
Assignee = reporter;
reporter.notify("Please approve fix for the issue" + getId(), "You have reported issue"
+ getId() + "Please review the applied fix for the issue and set the appropriate state.");
}
on Approve[always] do {<define statements>} transit to Verified
on Reopen[always] do {<define statements>} transit to Open
}
state Verified {
on Reopen[always] do {<define statements>} transit to Open
}
}
{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Fix Version and Fixed State}Fix Version and Fixed State {anchor} [(download zip file)|^jetbrains-youtrack-setFixVersions.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
* Require Fix Version value for Fixed state.
* Set an issue state to Fixed when Fixed Version is added.
{td}
{td:class=confluenceTd|width=65%}
Require Fix Version value for Fixed state
{code}
rule Assert Fix version is set for Fixed issues
when State.becomes({Fixed}) {
Fix versions.required("Please set the 'Fix versions' field!");
}{code}
{expand:title=Set Fixed state on specifying Fixed in version}
{code}rule Set Fixed state on specifying Fixed in version
when State != {Fixed} && Fix versions.added.isNotEmpty {
State = {Fixed};
}{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Set assignee to subsystem owner}Set Assignee to Subsystem Owner {anchor} [(download zip file)|^jetbrains-youtrack-subsystemAssignee.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Set subsystem owner as assignee for new issues when subsystem is specified.
{td}
{td:class=confluenceTd|width=65%}
Set subsystem owner as assignee for new issues
{code}
rule Set subsystem owner as assignee for new issues
when Assignee == null && (Subsystem.changed || project.changed) {
if (issue.Subsystem != null) {
issue.Assignee = issue.Subsystem.owner;
}
}
{code}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Time Management }Time Management {anchor} [(download zip file)|^Time Management.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
* Change issue state in time automatically.
* Notify an issue assignee, project lead or subsystem owner when issue life-cycle is violated.
{note}{*}Requirements*:
* subsystem 'Support'
* state 'Overdue'
* state 'Wait for reproduce'
* state 'Approved'{note}
{td}
{td:class=confluenceTd|width=65%}
{expand:title=Time Management for State field}{code}
statemachine Time Management for field State {
initial state Submitted {
enter {
Subsystem = {Support};
Assignee.required("Responsible support engineer is required!");
}
in 1 hour[always] do {<define statements>} transit to Overdue
on reproducing[always] do {<define statements>} transit to Open
on incomplete[always] do {<define statements>} transit to Incomplete
}
state Overdue {
enter {
var user;
if (Assignee != null) {
user = Assignee;
} else if (Subsystem.owner != null) {
user = Subsystem.owner;
} else {
user = project.leader;
}
user.notify("Acknowledgement needed", "Issue" + getId() + "is waiting for acknowledgement.");
}
on incomplete[always] do {<define statements>} transit to Incomplete
on reproducing[always] do {<define statements>} transit to Open
}
state Open {
in 4 hours[always] do {<define statements>} transit to Wait for reproduce
on approved[always] do {<define statements>} transit to Approved
on incomplete[always] do {<define statements>} transit to Incomplete
on can't reproduce[always] do {<define statements>} transit to Can't Reproduce
}
state Wait for reproduce {
in 1 day[always] do {
var user;
if (Subsystem.owner != null) {
user = Subsystem.owner;
} else {
user = project.leader;
}
user.notify("Issue is not reproduced in 1 day",
"Issue " + getId() + " is still waiting for reproduction steps.");
// Notify sales?
}
in 3 days[always] do {
var user;
if (Subsystem.owner != null) {
user = Subsystem.owner;
} else {
user = project.leader;
}
user.notify("Issue is not reproduced in 4 days", "Issue " + getId() +
" is not reproduced. You may want to visit the customer at their site.");
// Notify sales?
}
on approved[always] do {<define statements>} transit to Approved
on can't reproduce[always] do {<define statements>} transit to Can't Reproduce
on incomplete[always] do {<define statements>} transit to Incomplete
}
state Can't Reproduce {
on reopen[always] do {<define statements>} transit to Open
}
state Incomplete {
on reopen[always] do {<define statements>} transit to Open
}
state Approved {
enter {
Assignee.required(fail message);
}
on fixed[always] do {<define statements>} transit to Fixed
on obsolete[always] do {<define statements>} transit to Obsolete
}
state Fixed {
on verify[always] do {<define statements>} transit to Verified
on reopen[always] do {<define statements>} transit to Open
}
state Obsolete {
on reopen[always] do {<define statements>} transit to Open
}
state Verified {
on reopen[always] do {<define statements>} transit to Open
}
}
{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Fix in Build Management}Fix in Build Management {anchor} [(download zip file)|^Fix in Build Management.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
* Clear Fixed in build value when issue becomes unresolved.
* Copy Fixed in Build value from duplicate.
* Set Fixed in Build value for duplicate when it's specified in the main issue.
{td}
{td:class=confluenceTd|width=65%}
Clear Fixed in build on issue unresolve
{code}rule Clear Fixed in build on issue unresolve
when issue.isReported() && !(issue.State.isResolved)
&& issue.State.oldValue != null && issue.State.oldValue.isResolved {
if (issue.Fixed in build != null) {
issue.Fixed in build = null;
}
}
{code}
{expand:title=Copy Fixed in build to duplicate from the main issue}
{code}
rule Copy Fixed in build from duplicated issue
when State.becomes({Duplicate}) && duplicates.isNotEmpty {
for each duplicatedIssue in duplicates {
if (duplicatedIssue.project == project && duplicatedIssue.Fixed in build != null) {
Fixed in build = duplicatedIssue.Fixed in build;
break;
}
}
}
{code}
{expand}
{expand:title=Copy Fixed in build to duplicates when it is set in the main issue}
{code}
rule Copy Fixed in build to duplicate issues when it is set
when Fixed in build.changed && Fixed in build != null {
var duplicatedBy = is duplicated by;
for each duplicate in duplicatedBy {
if (duplicate.project == project) {
duplicate.Fixed in build = Fixed in build;
}
}
}
{code}
{expand}
{td}
{tr}
{table}
{table:class=confluenceTable|width=100%}
{tr:class=confluenceTr}
{th:class=confluenceTh|width=35%}{anchor:Fixed in Build for Won't Fix}Fixed in Build for Won't Fix {anchor} [(download zip file)|^Fixed in Build for Won't Fix.zip]
{th}
{th:class=confluenceTh|width=65%}Source Code{th}
{tr}
{tr:class=confluenceTr}
{td:class=confluenceTd|width=35%}
Set Fixed in build to 'Never' for 'Won't fix' state.{note}{*}Requirements*: fixed in build 'Never' {note}
{td}
{td:class=confluenceTd|width=65%}
Set 'Never' fixed in build for 'Won't fix' state
{code}
rule Set 'Never' fixed in build for 'Won't fix' state
when State.becomes({Won't fix}) {
Fixed in build = {Never};
}
{code}
{td}
{tr}
{table}