YouTrack Standalone 2017.2 Help

Using REST API Methods in Workflows

YouTrack 7.0 brings REST client implementation to the workflow API. You can now use workflows to script push-style integrations with your favorite tools.

Here's a basic example:

// post new issue content to third-party tool and add response as a comment when issue.becomesReported() { addHttpHeader("Content-Type", "text/html"); var response = doHttpPost("http://server.com/issueRegistry", issue.description); issue.addComment(response) }

API Reference

Use the following methods to add API calls to your workflow rules.

Method

Description

void addHttpHeader(key: String, value: String)

Adds an HTTP header to the next request. Subsequent calls add multiple headers. Any doHttp* call uses up all of the accumulated headers and sets the internal header to an empty value.

void addHttpFormField(key: String, value: String)

Adds a field to an HTTP form that is submitted with the next request. Subsequent calls add multiple fields. The API implementation seamlessly encodes any form field value with application/x-www-form-urlencoded and sets the MIME type (i.e. Content-Type header) accordingly. Set to an empty value following any doHttp* call.

void addHttpRequestParameter(key: String, value: String)

Adds a URL parameter (scheme://domain.com?key=value) to the next request. Subsequent calls add multiple parameters. URL parameter encoding is applied automatically to every parameter. Set to an empty value following any doHttp* call.

String doHttpGet(url: String)

Executes an HTTP GET request to the specified URL. Returns the response code, message, and body as a single string. For further details on how to parse the response, see Server Response.

String doHttpPost(url: String, body: String)

Executes an HTTP POST request to the specified URL. You should either specify the string request payload or use the addHttpFormField method to set the form field beforehand. If you have already set the form field, the body parameter is ignored.

String doHttpPut(url: String, body: String)

Executes an HTTP PUT request to the specified URL. Payload handling rules are the same as for the POST method.

String doHttpDelete(url: String)

Executes an HTTP DELETE request to the specified URL.

Authentication

The REST client supports the HTTP basic access authentication scheme via headers. To utilize this scheme, compute a base64(login:password) value and set the authorization header as follows:

addHttpHeader("Authorization", "Basic amsudmNAbWFFR5ydTp5b3V0cmFjaw==");

Set the authorization header for every request, unless the target server provides cookies upon successful authentication.

HTTP cookies are managed transparently under the hood, when present. That is, if any RESET call returns cookies, they persist automatically and provide access to the same domain until they expire. You can also set cookies manually in the header:

addHttpHeader("Cookie", "MyServiceCookie=732423sdfs73242");

Server Response

The REST client returns the server response as a single string with response code, response message, and an optional response body. For example:

HTTP/1.1 404 Not Found {"status":"404","error":"Not Found"}

or

HTTP/1.1 201 Created

It's always worth checking whether the HTTP response matches your expectations.

As for response parsing, the REST client doesn't offer any special utilities beyond the usual language string API. The following code sample gives illustrates how to parse a server response:

if (response.contains("201", opts)) { debug(response); id = response.substringBetween("\"id\":", ","); notes = response.substringBetween("\"notes\":", "}"); } else { error(response); }

Secure Connections (SSL/TLS)

The REST client supports https:// connections out of the box. Although it's currently unable to present a client certificate during the handshake, it can still validate a server certificate against known certificate authorities. To learn more about adding trusted certificates to YouTrack, see SSL Certificates.

Best Practices

For best results, observe the following guidelines.

  1. Know your protocol. If you're not yet familiar with HTTP, it's time to fill the gap. You should have at least a basic understanding of the protocol to script the integration and decrypt errors.

  2. Know your API. Your favorite application that you're going to integrate with YouTrack almost certainly has documentation that tells you how to use their API. Check it out before you start to script an integration. For instance, here's a manual for the Pastebin service.

  3. Use logging. Log errors with error(...) and everything else with debug(...).

  4. Use a third-party REST client to make sure your requests are formatted correctly. Diagnostic tools in clients like cURL, Wget or the Postman extension for Chrome can help you to find out why your workflow is not acting as expected.

  5. Don't forget to add Content-Type and Accept headers to your requests. The majority of APIs out there rely on these headers and refuse to work without them.

Case Studies

The following case studies illustrate how you can use the workflow REST API to integrate YouTrack with an external application.

Pastebin Integration

Pastebin is a website where you can store text online for a set period of time. You can paste any string of text like code snippets and extracts from log files.

In this case study, we extract code snippets from new issues and store them on Pastebin instead. The issue description retains a link to the content that is moved to Pastebin. The following workflow rule demonstrates how this scenario is implemented:

rule Export code samples to pastebin.com when issue.becomesReported() || (issue.isReported() && issue.description.changed) { // find a code sample in issue description: the text between code markup tokens var code = issue.description.substringBetween("{" + "code" + "}", "{" + "code" + "}"); if (code.isNotBlank) { // pastebin accepts only forms, so we pack everything as form fields // authentication of performed via api developer key issue.addHttpFormField("api_dev_key", "98bcac75e1e327b54c08947ea1dbcb7e"); // other fields describing our "paste" issue.addHttpFormField("api_option", "paste"); issue.addHttpFormField("api_paste_private", "1"); issue.addHttpFormField("api_paste_name", "Code sample from issue " + issue.getId()); issue.addHttpFormField("api_paste_code", code.trim(side: both)); // form is ready, let's send it over the wire var response = issue.doHttpPost("http://pastebin.com/api/api_post.php", ""); if (response.contains("200 OK", opts)) { // split the response by lines to get to the body // response body contains a public URL for our code snippet var url = response.split("\n", opts)[1]; issue.description = issue.description.replace("{" + "code" + "}" + code + "{" + "code"+ "}", "See sample at " + url); message("Code sample is moved at <a href='" + url + "'>" + url + "</a>"); } else { // something went wrong, we'd better log the response error(response) } } }

On the other hand, we may want to do the opposite: to expand any Pastebin link we met into a code snippet, i.e. to download it and insert into issue. Let's try to code it:

rule Import code samples from pastebin.com when issue.becomesReported() || (issue.isReported() && issue.description.changed) { // check, if issue description contains links to pastebin var baseUrl = "http://pastebin.com/"; var urlBaseLength = baseUrl.length; var linkStart = issue.description.indexOf(baseUrl, opts); if (linkStart != -1) { // so we found a link, let's extract the key and download the contents via API var pasteKey = issue.description.substring(linkStart + urlBaseLength, linkStart + urlBaseLength + 8); var response = issue.doHttpGet("http://pastebin.com/raw/" + pasteKey); if (response.contains("200 OK", opts)) { var code = response.split("\n", opts)[1]; // insert downloaded content into issue description issue.description = issue.description.replace(baseUrl + pasteKey, "{" + "code" + "}" + code + "{" + "code" + "}"); } else { // something went wrong, we'd better log the response error(response) } } }

Custom Time Tracking with the Harvest Web Service

Suppose that we want to bill customers for the working hours that we record in YouTrack. The problem is that YouTrack isn't really built for managing invoices and associating spent time with specific customers. An integration with a dedicated time tracking service can make life a lot easier.

One possible scenario is to introduce a custom field - Billable hours - and post changes to the value of this field to the Harvest web service.

rule Post work item to Harvest when issue.Billable hours.changed { // we've created an admin user in Harvest and use these credentials for authentication addHttpHeader("Authorization","Basic amsudmNAbWFpbC5ydTp5b3V0cmFjaw=="); addHttpHeader("Accept","application/json"); addHttpHeader("Content-Type","application/json"); // post hours on behalf of the specific user. // this trick requires Harvest and YouTrack user logins to be synchronized addHttpRequestUrlParameter("of_user",loggedInUser.login); var hours = Billable hours - Billable hours.oldValue; var json = "{\"hours\":\""+hours+"\", \"project_id\": \"11698305\", \"task_id\": \"11698305\"}"; var response = doHttpPost("https://youtrack.harvestapp.com/daily/add",json); if (response.contains("201",opts)) { debug(response); }else{ error(response); } }

Let's consider another option: start time tracking when an issue moves to an In Progress state and stop time tracking when the issue is resolved. Lucky for us, Harvest has a timer API that we can use to start and stop the timers remotely. The Harvest ID custom field is required to store the timer identifier.

rule Start Harvest timer when issue.State.becomes({In Progress}) { // we've created an admin user in Harvest and use his credentials for authentication addHttpHeader("Authorization","Basic amsudmNAbWFpbC5ydTp5b3V0cmFjaw=="); addHttpHeader("Accept","application/json"); addHttpHeader("Content-Type","application/json"); // start timer on behalf of the specific user. // this trick requires Harvest and YouTrack user logins to be synchronized addHttpRequestUrlParameter("of_user",loggedInUser.login); var json = "{\"project_id\": \"11698305\", \"task_id\": \"11698305\"}"; var response = doHttpPost("https://youtrack.harvestapp.com/daily/add",json); if(response.contains("201",opts) ){ debug(response); // persist timer id to be able stop the timer later Harvest ID = response.substringBetween("\"id\":",","); } else { error(response); } }

The following workflow rule stops the Harvest timer when an issue is resolved.

rule Stop Harvest Timer when issue.becomesResolved() { // we've created an admin user in Harvest and use his credentials for authentication addHttpHeader("Authorization","Basic amsudmNAbWFpbC5ydTp5b3V0cmFjaw=="); addHttpHeader("Accept","application/json"); addHttpHeader("Content-Type","application/json"); // stop timer on behalf of the specific user. // this trick requires Harvest and YouTrack user logins to be synchronized addHttpRequestUrlParameter("of_user",loggedInUser.login); var json = "{\"project_id\": \"11698305\", \"task_id\": \"11698305\"}"; var response = doHttpGet("https://youtrack.harvestapp.com/daily/timer/" + Harvest ID); if(response.contains("200",opts)) { debug(response); } else { error(response); } }
Last modified: 7 March 2019