This is an automated email from the ASF dual-hosted git repository.

dpavlov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite-teamcity-bot.git

commit 3c3927e69e6c1f8a6dd5af539d1550617d1de535
Author: Dmitrii Ryabov <[email protected]>
AuthorDate: Wed Sep 12 18:22:20 2018 +0300

    IGNITE-9377 Handle print crit failures from RunAll to the JIRA ticket - 
Fixes #6.
---
 .../java/org/apache/ignite/ci/HelperConfig.java    |  22 +++
 .../main/java/org/apache/ignite/ci/ITcHelper.java  |   6 +
 .../main/java/org/apache/ignite/ci/ITeamcity.java  |  28 ++-
 .../apache/ignite/ci/IgnitePersistentTeamcity.java |  36 ++--
 .../org/apache/ignite/ci/IgniteTeamcityHelper.java |  93 ++++++----
 .../main/java/org/apache/ignite/ci/TcHelper.java   |  13 ++
 .../org/apache/ignite/ci/github/PullRequest.java   |   2 +-
 .../org/apache/ignite/ci/observer/BuildInfo.java   |  81 ++++++++
 .../apache/ignite/ci/observer/BuildObserver.java   |  63 +++++++
 .../apache/ignite/ci/observer/ObserverTask.java    | 204 +++++++++++++++++++++
 .../apache/ignite/ci/tcmodel/hist/BuildRef.java    |   4 +-
 .../java/org/apache/ignite/ci/util/HttpUtil.java   |  42 ++++-
 .../java/org/apache/ignite/ci/util/XmlUtil.java    |  26 +++
 .../ignite/ci/web/model/current/TestFailure.java   |  12 ++
 .../ignite/ci/web/model/current/UpdateInfo.java    |   6 +
 .../apache/ignite/ci/web/rest/TriggerBuild.java    |  44 ++++-
 .../ignite/ci/web/rest/pr/GetPrTestFailures.java   |  49 +++--
 ignite-tc-helper-web/src/main/webapp/index.html    |  55 ++++++
 18 files changed, 708 insertions(+), 78 deletions(-)

diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/HelperConfig.java 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/HelperConfig.java
index b3b51c7..37880c1 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/HelperConfig.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/HelperConfig.java
@@ -48,7 +48,14 @@ public class HelperConfig {
     @Deprecated
     private static final String PASSWORD = "password";
     public static final String ENCODED_PASSWORD = "encoded_password";
+
+    /** GitHub authorization token property name. */
     public static final String GITHUB_AUTH_TOKEN = "github.auth_token";
+
+    /** JIRA authorization token property name. */
+    public static final String JIRA_AUTH_TOKEN = "jira.auth_token";
+
+    /** Slack authorization token property name. */
     public static final String SLACK_AUTH_TOKEN = "slack.auth_token";
     public static final String SLACK_CHANNEL = "slack.channel";
     public static final String LOGS = "logs";
@@ -136,6 +143,21 @@ public class HelperConfig {
     }
 
     /**
+     * Extract JIRA basic authorization token from properties.
+     *
+     * @param props Properties, where token is placed.
+     * @return Null or decoded auth token for Github.
+     */
+    @Nullable static String prepareJiraHttpAuthToken(Properties props) {
+        String pwd = props.getProperty(JIRA_AUTH_TOKEN);
+
+        if (isNullOrEmpty(pwd))
+            return null;
+
+        return pwd;
+    }
+
+    /**
      * Extract TeamCity authorization token from properties.
      *
      * @param props Properties, where token is placed.
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITcHelper.java 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITcHelper.java
index b2beb51..69df43d 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITcHelper.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITcHelper.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.ci;
 
 import java.util.List;
+import org.apache.ignite.ci.observer.BuildObserver;
 import org.apache.ignite.ci.issue.IssueDetector;
 import org.apache.ignite.ci.issue.IssuesStorage;
 import org.apache.ignite.ci.user.ICredentialsProv;
@@ -42,6 +43,11 @@ public interface ITcHelper {
 
     IssueDetector issueDetector();
 
+    /**
+     * @return Build observer.
+     */
+    BuildObserver buildObserver();
+
     IAnalyticsEnabledTeamcity server(String srvId, @Nullable ICredentialsProv 
prov);
 
     ITcAnalytics tcAnalytics(String serverId);
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java
index 7e105c7..8201f5c 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java
@@ -239,7 +239,12 @@ public interface ITeamcity extends AutoCloseable {
      * @param cleanRebuild Rebuild all dependencies.
      * @param queueAtTop Put at the top of the build queue.
      */
-    void triggerBuild(String id, String name, boolean cleanRebuild, boolean 
queueAtTop);
+    Build triggerBuild(String id, String name, boolean cleanRebuild, boolean 
queueAtTop);
+
+    /**
+     * @param tok TeamCity authorization token.
+     */
+    void setAuthToken(String tok);
 
     /**
      * @return {@code True} if TeamCity authorization token is available.
@@ -247,9 +252,9 @@ public interface ITeamcity extends AutoCloseable {
     boolean isTeamCityTokenAvailable();
 
     /**
-     * @param token TeamCity authorization token.
+     * @param token GitHub authorization token.
      */
-    void setAuthToken(String token);
+    void setGitToken(String token);
 
     /**
      * @return {@code True} if GitHub authorization token is available.
@@ -257,9 +262,14 @@ public interface ITeamcity extends AutoCloseable {
     boolean isGitTokenAvailable();
 
     /**
-     * @param token GitHub authorization token.
+     * @param tok Jira authorization token.
      */
-    void setGitToken(String token);
+    void setJiraToken(String tok);
+
+    /**
+     * @return {@code True} if JIRA authorization token is available.
+     */
+    boolean isJiraTokenAvailable();
 
     /**
      * Send POST request with given body.
@@ -276,6 +286,14 @@ public interface ITeamcity extends AutoCloseable {
      */
     PullRequest getPullRequest(String branch);
 
+    /**
+     * @param ticket JIRA ticket name.
+     * @param comment Comment to be placed in the ticket conversation.
+     * @return {@code True} if ticket was succesfully commented. Otherwise - 
{@code false}.
+     */
+    boolean commentJiraTicket(String ticket, String comment);
+
+
     default void setAuthData(String user, String password) {
         setAuthToken(
                 Base64Util.encodeUtf8String(user + ":" + password));
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java
index 16e3a22..1e10d78 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java
@@ -77,7 +77,6 @@ import static 
org.apache.ignite.ci.BuildChainProcessor.normalizeBranch;
  *
  */
 public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, 
ITeamcity, ITcAnalytics {
-
     //V1 caches, 1024 parts
 
     //V2 caches, 32 parts
@@ -817,10 +816,15 @@ public class IgnitePersistentTeamcity implements 
IAnalyticsEnabledTeamcity, ITea
     }
 
     /** {@inheritDoc} */
-    @Override public void triggerBuild(String id, String name, boolean 
cleanRebuild, boolean queueAtTop) {
+    @Override public Build triggerBuild(String id, String name, boolean 
cleanRebuild, boolean queueAtTop) {
         lastTriggerMs = System.currentTimeMillis();
 
-        teamcity.triggerBuild(id, name, cleanRebuild, queueAtTop);
+        return teamcity.triggerBuild(id, name, cleanRebuild, queueAtTop);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void setAuthToken(String tok) {
+        teamcity.setAuthToken(tok);
     }
 
     /** {@inheritDoc} */
@@ -829,8 +833,8 @@ public class IgnitePersistentTeamcity implements 
IAnalyticsEnabledTeamcity, ITea
     }
 
     /** {@inheritDoc} */
-    @Override public void setAuthToken(String token) {
-        teamcity.setAuthToken(token);
+    @Override public void setGitToken(String tok) {
+        teamcity.setGitToken(tok);
     }
 
     /** {@inheritDoc} */
@@ -839,18 +843,28 @@ public class IgnitePersistentTeamcity implements 
IAnalyticsEnabledTeamcity, ITea
     }
 
     /** {@inheritDoc} */
-    @Override public void setGitToken(String token) {
-        teamcity.setGitToken(token);
+    @Override public void setJiraToken(String tok) {
+        teamcity.setJiraToken(tok);
     }
 
     /** {@inheritDoc} */
-    @Override public boolean notifyGit(String url, String body) {
-        return teamcity.notifyGit(url, body);
+    @Override public boolean isJiraTokenAvailable() {
+        return teamcity.isJiraTokenAvailable();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean commentJiraTicket(String ticket, String comment) {
+        return teamcity.commentJiraTicket(ticket, comment);
     }
 
     /** {@inheritDoc} */
-    @Override public PullRequest getPullRequest(String branch) {
-        return teamcity.getPullRequest(branch);
+    @Override public PullRequest getPullRequest(String branchForTc) {
+        return teamcity.getPullRequest(branchForTc);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean notifyGit(String url, String body) {
+        return teamcity.notifyGit(url, body);
     }
 
     /** {@inheritDoc} */
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityHelper.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityHelper.java
index 0813e2d..3bbc78e 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityHelper.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityHelper.java
@@ -27,6 +27,7 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.StringReader;
 import java.io.UncheckedIOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -83,6 +84,7 @@ import org.slf4j.LoggerFactory;
 import static com.google.common.base.Strings.isNullOrEmpty;
 import static java.util.concurrent.CompletableFuture.supplyAsync;
 import static org.apache.ignite.ci.HelperConfig.ensureDirExist;
+import static org.apache.ignite.ci.util.XmlUtil.xmlEscapeText;
 
 /**
  * Class for access to Teamcity REST API without any caching.
@@ -106,6 +108,9 @@ public class IgniteTeamcityHelper implements ITeamcity {
     /** GitHub authorization token. */
     private String gitAuthTok;
 
+    /** JIRA authorization token. */
+    private String jiraBasicAuthTok;
+
     private final String configName; //main properties file name
     private final String tcName;
 
@@ -131,6 +136,8 @@ public class IgniteTeamcityHelper implements ITeamcity {
 
         setGitToken(HelperConfig.prepareGithubHttpAuthToken(props));
 
+        setJiraToken(HelperConfig.prepareJiraHttpAuthToken(props));
+
         final File logsDirFile = HelperConfig.resolveLogs(workDir, props);
 
         logsDir = ensureDirExist(logsDirFile);
@@ -139,13 +146,18 @@ public class IgniteTeamcityHelper implements ITeamcity {
     }
 
     /** {@inheritDoc} */
+    @Override public void setAuthToken(String tok) {
+        basicAuthTok = tok;
+    }
+
+    /** {@inheritDoc} */
     @Override public boolean isTeamCityTokenAvailable() {
         return basicAuthTok != null;
     }
 
     /** {@inheritDoc} */
-    @Override public void setAuthToken(String token) {
-        basicAuthTok = token;
+    @Override public void setGitToken(String tok) {
+        gitAuthTok = tok;
     }
 
     /** {@inheritDoc} */
@@ -154,33 +166,40 @@ public class IgniteTeamcityHelper implements ITeamcity {
     }
 
     /** {@inheritDoc} */
-    @Override public void setGitToken(String token) {
-        gitAuthTok = token;
+    @Override public void setJiraToken(String tok) {
+        jiraBasicAuthTok = tok;
     }
 
     /** {@inheritDoc} */
-    @Override public boolean notifyGit(String url, String body) {
+    @Override public boolean isJiraTokenAvailable() {
+        return jiraBasicAuthTok != null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean commentJiraTicket(String ticket, String comment) {
         try {
-            HttpUtil.sendPostAsStringToGit(gitAuthTok, url, body);
+            String url = "https://issues.apache.org/jira/rest/api/2/issue/"; + 
ticket + "/comment";
+
+            HttpUtil.sendPostAsStringToJira(jiraBasicAuthTok, url, "{\"body\": 
\"" + comment + "\"}");
 
             return true;
         }
         catch (IOException e) {
-            logger.error("Failed to notify Git [errMsg="+e.getMessage()+']');
+            logger.error("Failed to notify JIRA [errMsg="+e.getMessage()+']');
 
             return false;
         }
     }
 
     /** {@inheritDoc} */
-    @Override public PullRequest getPullRequest(String branch) {
+    @Override public PullRequest getPullRequest(String branchForTc) {
         String id = null;
 
-        for (int i = 5; i < branch.length(); i++) {
-            char c = branch.charAt(i);
+        for (int i = 5; i < branchForTc.length(); i++) {
+            char c = branchForTc.charAt(i);
 
             if (!Character.isDigit(c)) {
-                id = branch.substring(5, i);
+                id = branchForTc.substring(5, i);
 
                 break;
             }
@@ -199,6 +218,20 @@ public class IgniteTeamcityHelper implements ITeamcity {
     }
 
     /** {@inheritDoc} */
+    @Override public boolean notifyGit(String url, String body) {
+        try {
+            HttpUtil.sendPostAsStringToGit(gitAuthTok, url, body);
+
+            return true;
+        }
+        catch (IOException e) {
+            logger.error("Failed to notify Git [errMsg="+e.getMessage()+']');
+
+            return false;
+        }
+    }
+
+    /** {@inheritDoc} */
     @Override public List<Agent> agents(boolean connected, boolean authorized) 
{
         String url = "app/rest/agents?locator=connected:" + connected + 
",authorized:" + authorized;
 
@@ -259,31 +292,13 @@ public class IgniteTeamcityHelper implements ITeamcity {
             .thenApply(LogCheckTask::getResult);
     }
 
-    /**
-     * @param t Text to process.
-     */
-    private String xmlEscapeText(CharSequence t) {
-        StringBuilder sb = new StringBuilder();
-        for(int i = 0; i < t.length(); i++){
-            char c = t.charAt(i);
-            switch(c){
-                case '<': sb.append("&lt;"); break;
-                case '>': sb.append("&gt;"); break;
-                case '\"': sb.append("&quot;"); break;
-                case '&': sb.append("&amp;"); break;
-                case '\'': sb.append("&apos;"); break;
-                default:
-                    if(c>0x7e)
-                        sb.append("&#").append((int)c).append(";");
-                    else
-                        sb.append(c);
-            }
-        }
-        return sb.toString();
-    }
-
     /** {@inheritDoc} */
-    @Override public void triggerBuild(String buildTypeId, String branchName, 
boolean cleanRebuild, boolean queueAtTop) {
+    @Override public Build triggerBuild(
+        String buildTypeId,
+        String branchName,
+        boolean cleanRebuild,
+        boolean queueAtTop
+    ) {
         String triggeringOptions =
             " <triggeringOptions" +
                 " cleanSources=\"" + cleanRebuild + "\"" +
@@ -307,11 +322,17 @@ public class IgniteTeamcityHelper implements ITeamcity {
             "</build>";
 
         String url = host + "app/rest/buildQueue";
+
         try {
             logger.info("Triggering build: buildTypeId={}, branchName={}, 
cleanRebuild={}, queueAtTop={}",
                 buildTypeId, branchName, cleanRebuild, queueAtTop);
 
-            HttpUtil.sendPostAsString(basicAuthTok, url, param);
+            try (StringReader reader = new 
StringReader(HttpUtil.sendPostAsString(basicAuthTok, url, param))) {
+                return XmlUtil.load(Build.class, reader);
+            }
+            catch (JAXBException e) {
+                throw Throwables.propagate(e);
+            }
         }
         catch (IOException e) {
             throw new UncheckedIOException(e);
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/TcHelper.java 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/TcHelper.java
index 1f6bc0d..a95483b 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/TcHelper.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/TcHelper.java
@@ -23,6 +23,7 @@ import com.google.common.cache.CacheBuilder;
 import java.util.List;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.ci.conf.BranchesTracked;
+import org.apache.ignite.ci.observer.BuildObserver;
 import org.apache.ignite.ci.issue.IssueDetector;
 import org.apache.ignite.ci.issue.IssuesStorage;
 import org.apache.ignite.ci.user.ICredentialsProv;
@@ -56,6 +57,10 @@ public class TcHelper implements ITcHelper {
     private TcUpdatePool tcUpdatePool = new TcUpdatePool();
     private IssuesStorage issuesStorage;
     private IssueDetector detector;
+
+    /** Build observer. */
+    private BuildObserver buildObserver;
+
     private UserAndSessionsStorage userAndSessionsStorage;
 
     public TcHelper(Ignite ignite) {
@@ -65,6 +70,7 @@ public class TcHelper implements ITcHelper {
         userAndSessionsStorage = new UserAndSessionsStorage(ignite);
 
         detector = new IssueDetector(ignite, issuesStorage, 
userAndSessionsStorage);
+        buildObserver = new BuildObserver(this);
     }
 
     /** {@inheritDoc} */
@@ -78,6 +84,11 @@ public class TcHelper implements ITcHelper {
     }
 
     /** {@inheritDoc} */
+    @Override public BuildObserver buildObserver() {
+        return buildObserver;
+    }
+
+    /** {@inheritDoc} */
     @Override public IAnalyticsEnabledTeamcity server(String srvId, @Nullable 
ICredentialsProv prov) {
         if (stop.get())
             throw new IllegalStateException("Shutdown");
@@ -150,6 +161,8 @@ public class TcHelper implements ITcHelper {
         tcUpdatePool.stop();
 
         detector.stop();
+
+        buildObserver.stop();
     }
 
     public ExecutorService getService() {
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/PullRequest.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/PullRequest.java
index d381d12..43a6b83 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/PullRequest.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/PullRequest.java
@@ -32,7 +32,7 @@ public class PullRequest {
     /** Pull Request title. */
     private String title;
 
-    /** Pull Request url to get statuses. */
+    /** Pull Request statuses URL. */
     @SerializedName("statuses_url") private String statusesUrl;
 
     /**
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildInfo.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildInfo.java
new file mode 100644
index 0000000..445cd06
--- /dev/null
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildInfo.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ci.observer;
+
+import org.apache.ignite.ci.tcmodel.result.Build;
+import org.apache.ignite.ci.user.ICredentialsProv;
+
+/**
+ *
+ */
+class BuildInfo {
+    /** Build. */
+    final Build build;
+
+    /** Server id. */
+    final String srvId;
+
+    /** */
+    final ICredentialsProv prov;
+
+    /** JIRA ticket name. */
+    final String ticket;
+
+    /**
+     * @param build Build.
+     * @param srvId Server id.
+     * @param prov Credentials.
+     * @param ticket JIRA ticket name.
+     */
+    BuildInfo(Build build, String srvId, ICredentialsProv prov, String ticket) 
{
+        this.build = build;
+        this.srvId = srvId;
+        this.prov = prov;
+        this.ticket = ticket;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+
+        BuildInfo info = (BuildInfo)o;
+
+        if (!build.equals(info.build))
+            return false;
+        if (!srvId.equals(info.srvId))
+            return false;
+        if (!prov.equals(info.prov))
+            return false;
+
+        return ticket.equals(info.ticket);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        int res = build.hashCode();
+
+        res = 31 * res + srvId.hashCode();
+        res = 31 * res + prov.hashCode();
+        res = 31 * res + ticket.hashCode();
+
+        return res;
+    }
+}
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildObserver.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildObserver.java
new file mode 100644
index 0000000..a7a7b1b
--- /dev/null
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildObserver.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ci.observer;
+
+import java.util.Timer;
+import org.apache.ignite.ci.ITcHelper;
+import org.apache.ignite.ci.tcmodel.result.Build;
+import org.apache.ignite.ci.user.ICredentialsProv;
+
+/**
+ *
+ */
+public class BuildObserver {
+    /** Time between observing actions in milliseconds. */
+    private final long period = 30 * 60 * 1_000;
+
+    /** Timer. */
+    private final Timer timer;
+
+    /** Task, which should be done periodically. */
+    private final ObserverTask task;
+
+    /**
+     * @param helper Helper.
+     */
+    public BuildObserver(ITcHelper helper) {
+        timer = new Timer();
+
+        timer.schedule(task = new ObserverTask(helper), period, period);
+    }
+
+    /**
+     * Stop observer.
+     */
+    public void stop() {
+        timer.cancel();
+    }
+
+    /**
+     * @param build Build id.
+     * @param srvId Server id.
+     * @param prov Credentials.
+     * @param ticket JIRA ticket name.
+     */
+    public void observe(Build build, String srvId, ICredentialsProv prov, 
String ticket) {
+        task.builds.add(new BuildInfo(build, srvId, prov, ticket));
+    }
+}
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/ObserverTask.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/ObserverTask.java
new file mode 100644
index 0000000..7466a30
--- /dev/null
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/ObserverTask.java
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ci.observer;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import org.apache.ignite.ci.IAnalyticsEnabledTeamcity;
+import org.apache.ignite.ci.ITcHelper;
+import org.apache.ignite.ci.tcmodel.result.Build;
+import org.apache.ignite.ci.web.model.current.ChainAtServerCurrentStatus;
+import org.apache.ignite.ci.web.model.current.SuiteCurrentStatus;
+import org.apache.ignite.ci.web.model.current.TestFailure;
+import org.apache.ignite.ci.web.model.current.TestFailuresSummary;
+import org.apache.ignite.ci.web.model.hist.FailureSummary;
+import org.apache.ignite.ci.web.rest.pr.GetPrTestFailures;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.ignite.ci.analysis.RunStat.MAX_LATEST_RUNS;
+import static org.apache.ignite.ci.util.XmlUtil.xmlEscapeText;
+
+/**
+ * Checks observed builds for finished status and comments JIRA ticket.
+ */
+public class ObserverTask extends TimerTask {
+    /** Logger. */
+    private static final Logger logger = 
LoggerFactory.getLogger(ObserverTask.class);
+
+    /** Helper. */
+    private final ITcHelper helper;
+
+    /** Builds. */
+    final Queue<BuildInfo> builds;
+
+    /**
+     * @param helper Helper.
+     */
+    ObserverTask(ITcHelper helper) {
+        this.helper = helper;
+        builds = new ConcurrentLinkedQueue<>();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void run() {
+        for (BuildInfo info : builds) {
+            IAnalyticsEnabledTeamcity teamcity = helper.server(info.srvId, 
info.prov);
+            Build build = teamcity.getBuild(info.build.getId());
+            String comment;
+
+            try {
+                comment = generateComment(build, info);
+            }
+            catch (RuntimeException e) {
+                logger.error("Exception happened during generating comment for 
JIRA " +
+                    "[build=" + build.getId() + ", errMsg=" + e.getMessage() + 
']');
+
+                continue;
+            }
+
+            if (build.state.equals("finished")) {
+                if (teamcity.commentJiraTicket(info.ticket, comment))
+                    builds.remove(info);
+            }
+        }
+    }
+
+    /**
+     * @param build Build.
+     * @param info Info.
+     */
+    private String generateComment(Build build, BuildInfo info) {
+        StringBuilder res = new StringBuilder();
+        TestFailuresSummary summary = GetPrTestFailures.getTestFailuresSummary(
+            helper, info.prov, info.srvId, build.getBuildType().getId(), 
build.branchName, "Latest", null);
+
+        if (summary != null) {
+            for (ChainAtServerCurrentStatus server : summary.servers) {
+                if (!server.serverName().equals("apache"))
+                    continue;
+
+                Map<String, List<SuiteCurrentStatus>> fails = 
findFailures(server);
+
+                for (List<SuiteCurrentStatus> suites : fails.values()) {
+                    for (SuiteCurrentStatus suite : suites) {
+                        
res.append("{color:#d04437}").append(suite.name).append("{color}");
+                        res.append(" [[tests ").append(suite.failedTests);
+
+                        if (suite.result != null && !suite.result.equals(""))
+                            res.append(' ').append(suite.result);
+
+                        
res.append('|').append(suite.webToBuild).append("]]\\n");
+
+                        for (TestFailure failure : suite.testFailures) {
+                            res.append("* ");
+
+                            if (failure.suiteName != null && failure.testName 
!= null)
+                                res.append(failure.suiteName).append(": 
").append(failure.testName);
+                            else
+                                res.append(failure.name);
+
+                            FailureSummary recent = 
failure.histBaseBranch.recent;
+
+                            if (recent != null) {
+                                if (recent.failureRate != null) {
+                                    res.append(" - 
").append(recent.failureRate).append("% fails in last ")
+                                        .append(MAX_LATEST_RUNS).append(" 
master runs.");
+                                }
+                                else if (recent.failures != null && 
recent.runs != null) {
+                                    res.append(" - 
").append(recent.failures).append(" fails / ")
+                                        .append(recent.runs).append(" runs.");
+                                }
+                            }
+
+                            res.append("\\n");
+                        }
+
+                        res.append("\\n");
+                    }
+                }
+
+                if (res.length() > 0) {
+                    res.insert(0, "{panel:title=Possible Blockers|" +
+                        
"borderStyle=dashed|borderColor=#ccc|titleBGColor=#F7D6C1}\\n")
+                        .append("{panel}");
+                }
+                else {
+                    res.append("{panel:title=No blockers found!|" +
+                        
"borderStyle=dashed|borderColor=#ccc|titleBGColor=#D6F7C1}{panel}");
+                }
+            }
+        }
+
+        res.append("\\n").append("[TeamCity Run 
All|").append(build.webUrl).append(']');
+
+        return xmlEscapeText(res.toString());
+    }
+
+    /**
+     * @param srv Server.
+     * @return Failures for given server.
+     */
+    private Map<String, List<SuiteCurrentStatus>> 
findFailures(ChainAtServerCurrentStatus srv) {
+        Map<String, List<SuiteCurrentStatus>> fails = new LinkedHashMap<>();
+
+        fails.put("compilation", new ArrayList<>());
+        fails.put("timeout", new ArrayList<>());
+        fails.put("exit code", new ArrayList<>());
+        fails.put("failed tests", new ArrayList<>());
+
+        for (SuiteCurrentStatus suite : srv.suites) {
+            String suiteRes = suite.result.toLowerCase();
+            String failType = null;
+
+            if (suiteRes.contains("compilation"))
+                failType = "compilation";
+
+            if (suiteRes.contains("timeout"))
+                failType = "timeout";
+
+            if (suiteRes.contains("exit code"))
+                failType = "exit code";
+
+            if (failType == null) {
+                List<TestFailure> failures = new ArrayList<>();
+
+                for (TestFailure testFailure : suite.testFailures) {
+                    if (testFailure.isNewFailedTest())
+                        failures.add(testFailure);
+                }
+
+                if (!failures.isEmpty()) {
+                    suite.testFailures = failures;
+
+                    failType = "failed tests";
+                }
+            }
+
+            if (failType != null)
+                fails.get(failType).add(suite);
+        }
+
+        return fails;
+    }
+}
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/hist/BuildRef.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/hist/BuildRef.java
index 0e6202b..2305fc7 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/hist/BuildRef.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/hist/BuildRef.java
@@ -44,7 +44,8 @@ public class BuildRef extends AbstractRef {
 
     public static final String STATE_QUEUED = "queued";
 
-    @XmlAttribute private String state;
+    /** Current state of build. */
+    @XmlAttribute public String state;
 
     @XmlAttribute(name = "number") public String buildNumber;
 
@@ -52,6 +53,7 @@ public class BuildRef extends AbstractRef {
 
     @XmlAttribute public Boolean composite;
 
+    /** Build page URL. */
     @XmlAttribute public String webUrl;
 
     /**
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/HttpUtil.java 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/HttpUtil.java
index 8234f8a..45f249b 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/HttpUtil.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/HttpUtil.java
@@ -20,6 +20,7 @@ package org.apache.ignite.ci.util;
 import com.google.common.base.Stopwatch;
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -81,18 +82,18 @@ public class HttpUtil {
     /**
      * Send GET request to the GitHub url.
      *
-     * @param githubAuthToken Authorization token.
+     * @param githubAuthTok Authorization OAuth token.
      * @param url URL.
      * @return Input stream from connection.
      * @throws IOException If failed.
      */
-    public static InputStream sendGetToGit(String githubAuthToken, String url) 
throws IOException {
+    public static InputStream sendGetToGit(String githubAuthTok, String url) 
throws IOException {
         Stopwatch started = Stopwatch.createStarted();
         URL obj = new URL(url);
         HttpURLConnection con = (HttpURLConnection)obj.openConnection();
 
         con.setRequestProperty("accept-charset", 
StandardCharsets.UTF_8.toString());
-        con.setRequestProperty("Authorization", "token " + githubAuthToken);
+        con.setRequestProperty("Authorization", "token " + githubAuthTok);
         con.setRequestProperty("Connection", "Keep-Alive");
         con.setRequestProperty("Keep-Alive", "header");
 
@@ -193,4 +194,39 @@ public class HttpUtil {
             return readIsToString(inputStream);
         }
     }
+
+    /**
+     * Send POST request to the GitHub url.
+     *
+     * @param jiraAuthTok Authorization Base64 token.
+     * @param url URL.
+     * @param body Request POST params.
+     * @return Response body from given url.
+     * @throws IOException If failed.
+     */
+    public static String sendPostAsStringToJira(String jiraAuthTok, String 
url, String body) throws IOException {
+        URL obj = new URL(url);
+        HttpURLConnection con = (HttpURLConnection)obj.openConnection();
+        Charset charset = StandardCharsets.UTF_8;
+
+        con.setRequestProperty("accept-charset", charset.toString());
+        con.setRequestProperty("Authorization", "Basic " + jiraAuthTok);
+        con.setRequestProperty("content-type", "application/json");
+        con.setRequestProperty("Connection", "Keep-Alive");
+        con.setRequestProperty("Keep-Alive", "header");
+
+        con.setRequestMethod("POST");
+
+        con.setDoOutput(true);
+
+        try (OutputStreamWriter writer = new 
OutputStreamWriter(con.getOutputStream(), charset)){
+            writer.write(body); // Write POST query string (if any needed).
+        }
+
+        logger.info("\nSending 'POST' request to URL : " + url + "\n" + body);
+
+        try (InputStream inputStream = getInputStream(con)){
+            return readIsToString(inputStream);
+        }
+    }
 }
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/XmlUtil.java 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/XmlUtil.java
index c636e0b..5eb1510 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/XmlUtil.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/XmlUtil.java
@@ -48,4 +48,30 @@ public class XmlUtil {
 
         return unmarshal;
     }
+
+    /**
+     * @param t Text to process.
+     */
+    public static String xmlEscapeText(CharSequence t) {
+        StringBuilder sb = new StringBuilder();
+
+        for (int i = 0; i < t.length(); i++) {
+            char c = t.charAt(i);
+
+            switch(c){
+                case '<': sb.append("&lt;"); break;
+                case '>': sb.append("&gt;"); break;
+                case '\"': sb.append("&quot;"); break;
+                case '&': sb.append("&amp;"); break;
+                case '\'': sb.append("&apos;"); break;
+                default:
+                    if (c>0x7e)
+                        sb.append("&#").append((int)c).append(";");
+                    else
+                        sb.append(c);
+            }
+        }
+
+        return sb.toString();
+    }
 }
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/TestFailure.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/TestFailure.java
index 795cc63..9325f60 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/TestFailure.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/TestFailure.java
@@ -242,6 +242,18 @@ import static 
org.apache.ignite.ci.web.model.current.SuiteCurrentStatus.branchFo
         }
     }
 
+    /**
+     * @return {@code True} if this failure is appeared in the current branch.
+     */
+    public boolean isNewFailedTest() {
+        FailureSummary recent = histBaseBranch.recent;
+
+        boolean lowFailureRate = recent != null && recent.failureRate != null 
&&
+            Float.valueOf(recent.failureRate.replace(',', '.')) < 4.;
+
+        return lowFailureRate && flakyComments == null;
+    }
+
     /** {@inheritDoc} */
     @Override public boolean equals(Object o) {
         if (this == o)
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/UpdateInfo.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/UpdateInfo.java
index 50b71f7..59e64ec 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/UpdateInfo.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/UpdateInfo.java
@@ -29,6 +29,9 @@ import org.apache.ignite.ci.IAnalyticsEnabledTeamcity;
     /** GitHub auth token availability flag. */
     public static int GITHUB_FLAG = 2;
 
+    /** JIRA auth token availability flag. */
+    public static int JIRA_FLAG = 2;
+
     /** Flags to use in javascript. */
     public Integer javaFlags = 0;
 
@@ -59,5 +62,8 @@ import org.apache.ignite.ci.IAnalyticsEnabledTeamcity;
 
         if (teamcity.isGitTokenAvailable())
             javaFlags = javaFlags | GITHUB_FLAG;
+
+        if (teamcity.isJiraTokenAvailable())
+            javaFlags = javaFlags | JIRA_FLAG;
     }
 }
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuild.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuild.java
index 4986f3d..2148a35 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuild.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuild.java
@@ -27,7 +27,10 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
+import org.apache.ignite.ci.ITcHelper;
 import org.apache.ignite.ci.ITeamcity;
+import org.apache.ignite.ci.github.PullRequest;
+import org.apache.ignite.ci.tcmodel.result.Build;
 import org.apache.ignite.ci.user.ICredentialsProv;
 import org.apache.ignite.ci.web.CtxListener;
 import org.apache.ignite.ci.web.rest.login.ServiceUnauthorizedException;
@@ -47,19 +50,44 @@ public class TriggerBuild {
     @GET
     @Path("trigger")
     public SimpleResult triggerBuild(
-        @Nullable @QueryParam("serverId") String serverId,
+        @Nullable @QueryParam("serverId") String srvId,
         @Nullable @QueryParam("branchName") String branchName,
         @Nullable @QueryParam("suiteId") String suiteId,
-        @Nullable @QueryParam("top") Boolean top) {
-
+        @Nullable @QueryParam("top") Boolean top,
+        @Nullable @QueryParam("observe") Boolean observe
+    ) {
         final ICredentialsProv prov = ICredentialsProv.get(req);
 
-        if(!prov.hasAccess(serverId)) {
-            throw ServiceUnauthorizedException.noCreds(serverId);
-        }
+        if (!prov.hasAccess(srvId))
+            throw ServiceUnauthorizedException.noCreds(srvId);
 
-        try (final ITeamcity helper = 
CtxListener.getTcHelper(context).server(serverId, prov)) {
-            helper.triggerBuild(suiteId, branchName, false, top != null && 
top);
+        ITcHelper helper = CtxListener.getTcHelper(context);
+
+        try (final ITeamcity teamcity = helper.server(srvId, prov)) {
+            PullRequest pr = teamcity.getPullRequest(branchName);
+
+            String ticketId = "";
+
+            if (pr.getTitle().startsWith("IGNITE-")) {
+                int beginIdx = 7;
+                int endIdx = 7;
+
+                while (endIdx < pr.getTitle().length() && 
Character.isDigit(pr.getTitle().charAt(endIdx)))
+                    endIdx++;
+
+                ticketId = pr.getTitle().substring(beginIdx, endIdx);
+            }
+
+            if (ticketId.equals(""))
+                return new SimpleResult("PR title \"" + pr.getTitle() + "\" 
should starts with \"IGNITE-XXXX\"." +
+                    " Please, rename PR according to the" +
+                    " <a 
href='https://cwiki.apache.org/confluence/display/IGNITE/How+to+Contribute"; +
+                    "#HowtoContribute-1.CreateGitHubpull-request'>contributing 
guide</a>.");
+
+            Build build = teamcity.triggerBuild(suiteId, branchName, false, 
top != null && top);
+
+            if (observe != null && observe)
+                helper.buildObserver().observe(build, srvId, prov, "ignite-" + 
ticketId);
         }
 
         return new SimpleResult("OK");
diff --git 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/pr/GetPrTestFailures.java
 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/pr/GetPrTestFailures.java
index 12344b4..eba2b58 100644
--- 
a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/pr/GetPrTestFailures.java
+++ 
b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/pr/GetPrTestFailures.java
@@ -103,22 +103,44 @@ public class GetPrTestFailures {
         @Nonnull @QueryParam("action") String action,
         @Nullable @QueryParam("count") Integer count) {
 
-        final TestFailuresSummary res = new TestFailuresSummary();
-        final AtomicInteger runningUpdates = new AtomicInteger();
-
         final ITcHelper tcHelper = CtxListener.getTcHelper(context);
         final ICredentialsProv creds = ICredentialsProv.get(req);
 
+        return getTestFailuresSummary(tcHelper, creds, srvId, suiteId, 
branchForTc, action, count);
+    }
+
+    /**
+     * @param helper Helper.
+     * @param creds Credentials.
+     * @param srvId Server id.
+     * @param suiteId Suite id.
+     * @param branchForTc Branch name.
+     * @param act Action.
+     * @param cnt Count.
+     * @return Test failures summary.
+     */
+    public static TestFailuresSummary getTestFailuresSummary(
+        ITcHelper helper,
+        ICredentialsProv creds,
+        String srvId,
+        String suiteId,
+        String branchForTc,
+        String act,
+        Integer cnt
+    ) {
+        final TestFailuresSummary res = new TestFailuresSummary();
+        final AtomicInteger runningUpdates = new AtomicInteger();
+
         //using here non persistent TC allows to skip update statistic
-        try (IAnalyticsEnabledTeamcity teamcity = tcHelper.server(srvId, 
creds)) {
+        try (IAnalyticsEnabledTeamcity teamcity = helper.server(srvId, creds)) 
{
             res.setJavaFlags(teamcity);
 
             LatestRebuildMode rebuild;
-            if (FullQueryParams.HISTORY.equals(action))
+            if (FullQueryParams.HISTORY.equals(act))
                 rebuild = LatestRebuildMode.ALL;
-            else if (FullQueryParams.LATEST.equals(action))
+            else if (FullQueryParams.LATEST.equals(act))
                 rebuild = LatestRebuildMode.LATEST;
-            else if (FullQueryParams.CHAIN.equals(action))
+            else if (FullQueryParams.CHAIN.equals(act))
                 rebuild = LatestRebuildMode.NONE;
             else
                 rebuild = LatestRebuildMode.LATEST;
@@ -129,7 +151,7 @@ public class GetPrTestFailures {
 
             long limit;
             if (rebuild == LatestRebuildMode.ALL)
-                limit = count == null ? 10 : count;
+                limit = cnt == null ? 10 : cnt;
             else
                 limit = 1;
 
@@ -156,9 +178,10 @@ public class GetPrTestFailures {
                 if (ctx.isFakeStub())
                     chainStatus.setBuildNotFound(true);
                 else {
-                    int cnt = (int)ctx.getRunningUpdates().count();
-                    if (cnt > 0)
-                        runningUpdates.addAndGet(cnt);
+                    int cnt0 = (int)ctx.getRunningUpdates().count();
+
+                    if (cnt0 > 0)
+                        runningUpdates.addAndGet(cnt0);
 
                     //fail rate reference is always default (master)
                     chainStatus.initFromContext(teamcity, ctx, teamcity, 
failRateBranch);
@@ -179,8 +202,8 @@ public class GetPrTestFailures {
         @Nullable @QueryParam("serverId") String srvId,
         @Nonnull @QueryParam("suiteId") String suiteId,
         @Nonnull @QueryParam("branchForTc") String branchForTc,
-        @Nonnull @QueryParam("action") String action,
-        @Nullable @QueryParam("count") Integer count,
+        @Nonnull @QueryParam("action") String act,
+        @Nullable @QueryParam("count") Integer cnt,
         @Nonnull @FormParam("notifyMsg") String msg) {
         if (!branchForTc.startsWith("pull/"))
             return "Given branch is not a pull request. Notify works only for 
pull requests.";
diff --git a/ignite-tc-helper-web/src/main/webapp/index.html 
b/ignite-tc-helper-web/src/main/webapp/index.html
index c642863..481706e 100644
--- a/ignite-tc-helper-web/src/main/webapp/index.html
+++ b/ignite-tc-helper-web/src/main/webapp/index.html
@@ -9,6 +9,7 @@
 
     <script src="https://code.jquery.com/jquery-1.12.4.js";></script>
     <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js";></script>
+
     <script src="js/common-1.6.js"></script>
     <script>
 $(document).ready(function() {
@@ -21,6 +22,7 @@ $(document).ready(function() {
 
 function loadData() {
     $("#loadStatus").html("<img 
src='https://www.wallies.com/filebin/images/loading_apple.gif' width=20px 
height=20px> Please wait");
+
     $.ajax({
         url: "rest/branches/version",
         success: showVersionInfo,
@@ -50,6 +52,7 @@ function loadData() {
         success: function(result) {
             $("#loadStatus").html("");
             showBuildsOnServers(result);
+            showRunAllForm(result);
         },
         error: showErrInLoadStatus
     });
@@ -108,6 +111,55 @@ function showBuildsOnServers(result) {
     }
     $("#buildsCheck").html(res);
 }
+
+/**
+ * This form allows user to start TeamCity Run All build.
+ */
+function showRunAllForm(result) {
+    var res = "";
+
+    for (var i = 0; i < result.length; i++) {
+        var serverId = result[i];
+
+        res+="Server: <input type='text' name='serverId' value=" + serverId +" 
readonly>" ;
+        res+="Pull Request #<input type='text' name='prId' onkeypress='return 
trigBuild(event)'> ";
+        res+="<button onclick='trigBuild()'>Run All</button>";
+    }
+
+    $("#runAll").html(res);
+}
+
+/**
+ * Start Run All build on TeamCity and comment in JIRA ticket when build will 
be finished.
+ */
+function trigBuild(event) {
+    if (event != null && event.key != "Enter")
+        return;
+
+    var fields = document.getElementById("runAll").children;
+    var url = "rest/build/trigger?suiteId=IgniteTests24Java8_RunAll";
+    var prId = null;
+
+    for (let field of fields) {
+        if (field.name == "serverId")
+            url += "&serverId=" + field.value;
+
+        if (field.name == "prId") {
+            url += "&branchName=pull%2F" + field.value + "%2Fhead";
+            prId = field.value;
+        }
+    }
+
+    url += "&observe=true"
+
+    $.ajax({
+        url: url,
+        success: function(result) {
+            $("#runAll").html("Run All test build was started for PR #" + 
prId);
+        },
+        error: showErrInLoadStatus
+    });
+}
 </script>
 </head>
 <body>
@@ -132,6 +184,9 @@ Check build:   <br>
 <div id="buildsCheck"></div>
 <br>
 
+TeamCity Run All:   <br>
+<div id="runAll"></div>
+<br>
 
 <a href="ignval.html">Ignite Log Values pretty-print</a><br>
 

Reply via email to