Best Practices Events Livestreams

Introduction to JavaScript Workflows in YouTrack 2017.3 Webinar Recording and Q&A Session

Thanks to everyone who joined our live webinar to discover more about JavaScript workflows in YouTrack 2017.3. The recording of the webinar is now available on our JetBrains YouTube Channel.

In this webinar, we introduced you to two major changes for workflows in YouTrack:

  • Working with the built-in workflow editor
  • Writing workflows in JavaScript

We explored the new editor, demonstrated how to work with various types of rules and scripts, and shared real-life use cases. We also covered key changes to the workflow API and explained how to use webhooks to build your own integrations.

https://youtu.be/Jv6vGUPlLNk

If you missed the webinar for any reason, you are welcome to watch it now!

One more thing, we’ve set up a brand new YouTrack Workflow Community in Slack. We plan to use this channel to exchange knowledge about new workflows in YouTrack. You are welcome to join.

We presented several workflow scripts during the webinar to illustrate different types of rules. You’ll find the code for all the sample scripts below. You’re welcome to copy, modify, and use them to support your own use cases.

Title Star reviewer
Type On-change rule
Description Adds a user to the list of watchers when the user is set as the reviewer.
var entities = require('@jetbrains/youtrack-scripting-api/entities');

exports.rule = entities.Issue.onChange({
  title: 'Star reviewer',
  guard: function(ctx) {
    var fields = ctx.issue.fields;
    return fields.isChanged(ctx.Reviewer) && fields.Reviewer;
  },
  action: function(ctx) {
    var issue = ctx.issue;
    issue.fields.Reviewer.watchIssue(issue);
  },
  requirements: {
    Reviewer: {
      type: entities.User.fieldType
    }
  }
});
Title Ping reviewer by email
Type Action rule
Description Sends an email notification to the reviewer with a command.
var entities = require('@jetbrains/youtrack-scripting-api/entities');
var notify = require('./notify');

exports.rule = entities.Issue.action({
  title: 'Ping reviewer by email',
  command: 'ping-reviewer-by-email',
  guard: function(ctx) {
    var fields = ctx.issue.fields;
    return fields.Reviewer && fields.State.name === ctx.State.PR.name;
  },
  action: function(ctx) {
    notify.pingReviewerByEmail(ctx);
  },
  requirements: {
    Reviewer: {
      type: entities.User.fieldType
    },
    State: {
      type: entities.State.fieldType,
      PR: {
        name: 'Pending Review'
      }
    }
  }
});
Title Ping reviewers in the morning
Type On-schedule rule
Description Sends an email reminder to all reviewers every morning at 10:00.
var entities = require('@jetbrains/youtrack-scripting-api/entities');
var notify = require('./notify');

exports.rule = entities.Issue.onSchedule({
  title: 'Ping reviewers in the morning',
  search: 'State: {Pending Review} has: Reviewer',
  cron: '0 0 10 ? * MON-FRI',
  action: function(ctx) {
    notify.pingReviewerByEmail(ctx);
  },
  requirements: {
    Reviewer: {
      type: entities.User.fieldType
    },
    State: {
      type: entities.State.fieldType,
      PR: {
        name: 'Pending Review'
      }
    }
  }
});
Title State machine
Type State machine rule
Description Defines legal transitions between values for the State field, with reminders and restrictions for specific states.
var entities = require('@jetbrains/youtrack-scripting-api/entities');
var workflow = require('@jetbrains/youtrack-scripting-api/workflow');
var notify = require('./notify');

exports.rule = entities.Issue.stateMachine({
  title: 'State machine',
  fieldName: 'State',
  states: {
    Open: {
      initial: true,
      transitions: {
        'in progress': {
          targetState: 'In Progress',
          action: function(ctx) {
            if (!ctx.issue.fields.Assignee) {
              ctx.issue.fields.Assignee = ctx.currentUser;
            }
          }
        }
      }
    },
    'In Progress': {
      transitions: {
        'reopen': {
          targetState: 'Open'
        },
        'review': {
          targetState: 'Pending Review',
          action: function(ctx) {
            var fields = ctx.issue.fields;
            fields.required(ctx.Assignee, 'Please set an Assignee!');
            fields.required(ctx.Reviewer, 'Please set a Reviewer!');
            workflow.check(fields.Assignee.login !== fields.Reviewer.login,
              'Assignee can\'t review own code, please set another Reviewer!');
          }
        }
      }
    },
    'Pending Review': {
      transitions: {
        'reopen': {
          targetState: 'Open'
        },
        'reject': {
          targetState: 'In Progress'
        },
        'approve': {
          targetState: 'Fixed'
        },
        'remind': {
          after: 3 * 24 * 60 * 60 * 1000, // 3 days in ms
          targetState: 'Pending Review',
          action: function(ctx) {
            notify.pingReviewerByEmail(ctx);
          }
        }
      }
    },
    Fixed: {
      transitions: {
        'reopen': {
          targetState: 'Open'
        },
        'verify': {
          targetState: 'Verified'
        }
      }
    },
    Verified: {
      transitions: {
        'reopen': {
          targetState: 'Open'
        }
      }
    }
  },
  requirements: {
    Reviewer: {
      type: entities.User.fieldType
    },
    Assignee: {
      type: entities.User.fieldType
    }
  }
});
Title Notify
Type Custom script
Description Contains functions that push notifications to Slack and send notifications over email.
var http = require('@jetbrains/youtrack-scripting-api/http');

exports.pingReviewerByEmail = function(ctx) {
  var issue = ctx.issue;
  var subject = '[YouTrack] Review Reminder';
  var issueLink = '<a href="' + issue.url + '">' + issue.id + '</a>';
  var body = 'Please do not forget to review issue ' + issueLink;
  issue.fields.Reviewer.notify(subject, body);
};

// IMPORTANT: Use a valid Incoming Webhook from Slack. To get one, go to
// https://my.slack.com/services/new/incoming-webhook/
exports.SLACK_WEBHOOK_URL = 
    'https://hooks.slack.com/services/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';

exports.pingReviewerInSlack = function(ctx) {
  var issue = ctx.issue;
  var issueLink = '<' + issue.url + "|" + issue.id + '>';
  var message = 'Dear ' + issue.fields.Reviewer.fullName + 
    ', please do not forget to review issue ' + issueLink + 
    ' (' + issue.summary + ').';
  var payload = {
    "channel": "#webinar-wf-2017-3",
    "attachments": [{
      "fallback": message,
      "pretext": message,
      "color": "#ff8c00"
    }]
  }; 
  
  var connection = new http.Connection(exports.SLACK_WEBHOOK_URL, null, 2000);
  var response = connection.postSync('', null, JSON.stringify(payload));
  if (!response.isSuccess) {
    console.warn('Failed to post notification to Slack. Details: ' + 
                 response.toString());
  }
};
Title Ping reviewer on Slack
Type Action rule
Description Sends notifications to reviewers in Slack.
var entities = require('@jetbrains/youtrack-scripting-api/entities');
var notify = require('./notify');

exports.rule = entities.Issue.action({
  title: 'Ping reviewer in Slack',
  command: 'ping-reviewer-in-slack',
  guard: function(ctx) {
    var fields = ctx.issue.fields;
    return fields.Reviewer && fields.State.name === ctx.State.PR.name;
  },
  action: function(ctx) {    
    notify.pingReviewerInSlack(ctx);
  },
  requirements: {
    Reviewer: {
      type: entities.User.fieldType
    },
    State: {
      type: entities.State.fieldType,
      PR: {
        name: 'Pending Review'
      }
    }
  }
});

Q&A session

Q: Have you removed the Testing and Closed states from the default sets of State values?
A: The set (or sets) of values that are used for the State field are completely customizable, so you are welcome to use your own set of states. Each project template has its own default set of values for the State field, so the defaults vary by project type. Testing and Closed are not used as defaults in the current version, but you can add them to your projects if you prefer.

Q: Could you elaborate a bit more what the “guard:” part is about? Especially, the difference to requirements.
A: The guard is a function that is called to check if an issue should be processed by the rule (the action function actually). Whereas the requirements are a way to define what objects (Fields, Users, UserGroups etc.) a rule needs to function properly.

Q: What subset of JavaScript is available in scripts?
A: For now it’s ECMA 5.1. We have plans to support ECMA 6 (aka 2015), but don’t have an ETD at the moment.

Q: Can you notify the user via slack instead of mail ? Or the notify method is only mail?
A: Yes, you can send a notification to a Slack channel or individual user, using the workflow. Please see the example above.

Q: Will notifications support HipChat or Microsoft Messenger?
A: Technically it is possible to integrate with any service that supports REST API calls. HipChat and Microsoft Messenger have such APIs. We have an example of a workflow that integrates with HipChat (see the documentation), but this integration is written in the old workflow API. To modify this workflow as written, you have to use the external YouTrack Workflow Editor. You can always use this as a reference to create something similar in JavaScript. We don’t have any examples for integrations with Microsoft Messenger at this point in time, but we’d be happy to help you create one.

Q: Can I, for example, make a HTTP request from the rule?
A: Yes, you can. Please take a look at our guide.

Q: Any plan to support TypeScript / Kotlin JS?
A: We’ll release some tools that let you develop workflows on the npm infrastructure. This means that you’ll be able to use transpilers to write in practically any language you want. It’s currently in development, so will be released pretty soon. As for Kotlin JS, we plan to support it with transpilers as well, but we don’t have an ETD.

Q: Can JS workflows work together with old type workflows?
A: Yes, you are welcome to use both types of workflows on the same installation. However, we suggest that you write all new workflows in JavaScript. Support for workflows in the previous API will be discontinued in the not-so-distant future. If you already have custom workflows written in the previous version of the API, stay ahead of the curve and migrate early.

Q: Is there a way of loading external scripts in the online code editor: For eg. if I want to load an external json rule in the state-machine rules?
A: As there are now modules for custom scripts, you can store your JSON in such a module and reference this module in other scripts and rules. You can also fetch resources that are stored outside YouTrack using request methods from the http module.

Q: Does the cron feature work OOB with a self-hosted installation?
A: Yes, it works. Moreover, on self-hosted (YouTrack Standalone) installations, you can set a cron expression that executes once every second. For YouTrack InCloud instances, the shortest possible duration for executing on-schedule rules is once per minute.

Q: Which part of JS is running in user browser and which part on JB servers? For example conditions and actions?
A: All JavaScript workflows run on the server.

Q: Is it possible to integrate workflow editor with source code repository ?
A: Unfortunately, not at the moment. We have plans to support this feature, but cannot provide an ETD at this point in time.

Q: For the schedule rule, is it possible to send a list of issues that match a search query in one email, rather than a separate email for each issue? For example, ‘Here are all of the issues that need estimation’
A: Yes. Workflow rules are always linked to issues (they react on issues changes, run on schedule over selected issues, and so on). In your case, you want an on-schedule rule to be triggered once, and process multiple issues in one action. To achieve this, you need to create an “anchor” issue (some dedicated issue which is explicitly guaranteed not to be deleted) and use the search module from the workflow API to get the list of issues you are interested in. Please take a look at the following sample code. It is “anchored” to one issue, it finds all of the critical issues that are unresolved in the corresponding project, then types the list of issues that match this search criteria to the console:

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

exports.rule = entities.Issue.onSchedule({
  title: 'List all unresolved issues with Critical priority',
  search: 'issue id: OM-10',
  cron: '0 0 10 * * ?',
  action: function(ctx) {
    var issues = search.search(ctx.issue.project, 
                               '#Unresolved Priority: Critical');
    var text = 'Critical issues:\n';
    issues.forEach(function(task) {
  	text += task.id + ' ' + task.summary + '\n';
    });
    console.log(text);
  }
});

Q: Is it possible to write a workflow to enforce to fill in Time Tracking in YT when changing state?
A: Yes. There are two basic ways:
You can prompt the user to add a work item when the user, say, moves an issue from In Progress to Fixed. For that, you need to check that the value for the Spent time field has changed, using the check function from the workflow module. The guard and action will look as follows:

guard: function(ctx) {
  return ctx.issue.becomesResolved;
},
action: function(ctx) {
  workflow.check(ctx.issue.fields.isChanged(ctx[‘Spent time’]),
    ‘Please add a work item and change state using the command dialog’);
}

You can add a work item automatically, using the issue.addWorkItem method. In this case, you need an additional field that stores a date type to calculate the amount of spent time for the work item (e.g. the difference between current moment and the moment when issue was moved to the In Progress state).

Q: Is there a recommended way to test these JS workflows without committing them to a live project? How can I run a tests for scripts? What is a recommended life cycle for scripts? aka sdk versions compatibility etc?
A: The simplest way to test your workflows is to create a Sandbox project in YouTrack, which no one but you will see, and test the rules there. When testing and debugging, use the console.log method to dump any information you are interested in into the console (at the bottom of the editor) and into the workflow.log files. We have also a sandbox InCloud instance. When you sign up to this instance, you are assigned the Project Admin role in the Global project and can create new projects and workflows right away.
However, we are currently developing npm infrastructure for workflows. You can already use an npm package that contains the current version of the workflow API. We are building a package that contains tasks for downloading workflows from your server and uploading your changes directly. Further down the road, we hope to provide a Karma/Mocha-esque framework for testing workflows.

Q: Actually when we log an object in the console we cannot read it, do you plan to fix that?
A: At the moment, only some of the objects in the workflow API have meaningful toString implementations. We are aware of the issue and are working on it.