You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by dp...@apache.org on 2019/01/14 16:33:32 UTC

[ignite-teamcity-bot] branch ignite-10930 updated: IGNITE-10930: PR-less contributions: refactoring of JIRA integration

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

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


The following commit(s) were added to refs/heads/ignite-10930 by this push:
     new fdf593e  IGNITE-10930: PR-less contributions: refactoring of JIRA integration
fdf593e is described below

commit fdf593e01b05fe1f90e5539215fd58ee9d94471b
Author: Dmitriy Pavlov <dp...@apache.org>
AuthorDate: Mon Jan 14 19:33:27 2019 +0300

    IGNITE-10930: PR-less contributions: refactoring of JIRA integration
---
 .../java/org/apache/ignite/ci/HelperConfig.java    |   2 +-
 .../main/java/org/apache/ignite/ci/ITcHelper.java  |  17 --
 .../main/java/org/apache/ignite/ci/ITeamcity.java  |  36 ----
 .../apache/ignite/ci/IgnitePersistentTeamcity.java |  30 ----
 .../apache/ignite/ci/IgniteTeamcityConnection.java |  47 +-----
 .../main/java/org/apache/ignite/ci/TcHelper.java   | 154 -----------------
 .../ignite/ci/jira/pure/IJiraIntegration.java      |  25 ++-
 .../java/org/apache/ignite/ci/jira/pure/Jira.java  |  79 ++++++++-
 .../apache/ignite/ci/observer/ObserverTask.java    |   5 +-
 .../ignite/ci/tcbot/chain/PrChainsProcessor.java   |   2 +-
 .../tcbot/visa/TcBotTriggerAndSignOffService.java  | 184 +++++++++++++++++++--
 .../ignite/ci/web/model/current/UpdateInfo.java    |   6 +-
 .../apache/ignite/ci/web/rest/TriggerBuilds.java   |   5 +-
 .../ignite/ci/web/rest/visa/TcBotVisaService.java  |   3 +-
 14 files changed, 266 insertions(+), 329 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 f773100..fe38350 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
@@ -159,7 +159,7 @@ public class HelperConfig {
      * @param props Properties, where token is placed.
      * @return Null or decoded auth token for Github.
      */
-    @Nullable static String prepareJiraHttpAuthToken(Properties props) {
+    @Nullable public static String prepareJiraHttpAuthToken(Properties props) {
         String tok = props.getProperty(JIRA_AUTH_TOKEN);
 
         if (isNullOrEmpty(tok))
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 1c084c1..e93dbab 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
@@ -55,21 +55,4 @@ public interface ITcHelper extends ITcServerProvider {
     /** */
     boolean isServerAuthorized();
 
-    /**
-     * @param srvId Server id.
-     * @param prov Credentials.
-     * @param buildTypeId Suite name.
-     * @param branchForTc Branch for TeamCity.
-     * @param ticket JIRA ticket full name.
-     * @return {@code Visa} which contains info about JIRA notification.
-     */
-    Visa notifyJira(String srvId, ICredentialsProv prov, String buildTypeId, String branchForTc, String ticket);
-
-    /**
-     * @param srvId Server id.
-     * @param prov Credentials.
-     * @param ticketId Ticket.
-     * @return Jira tickets.
-     */
-    Tickets getJiraTickets(String srvId, ICredentialsProv prov, String ticketId);
 }
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 fa1f3a7..4ce064d 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
@@ -91,42 +91,6 @@ public interface ITeamcity extends ITeamcityConn {
     boolean isTeamCityTokenAvailable();
 
 
-    /**
-     * @param tok Jira authorization token.
-     */
-    void setJiraToken(String tok);
-
-    /**
-     * @return {@code True} if JIRA authorization token is available.
-     */
-    boolean isJiraTokenAvailable();
-
-    /**
-     * @param ticket JIRA ticket full name. E.g 'IGNITE-5555'.
-     * @param comment Comment to be placed in the ticket conversation.
-     * @return {@code True} if ticket was succesfully commented. Otherwise - {@code false}.
-     *
-     * @throws IOException If failed to comment JIRA ticket.
-     * @throws IllegalStateException If can't find URL to the JIRA.
-     */
-    public String sendJiraComment(String ticket, String comment) throws IOException;
-
-    /**
-     * @param url Url.
-     * @return Response as gson string.
-     */
-    String sendGetToJira(String url) throws IOException;
-
-    /**
-     * @param url URL for JIRA integration.
-     */
-    void setJiraApiUrl(String url);
-
-    /**
-     * @return URL for JIRA integration.
-     */
-    String getJiraApiUrl();
-
     default void setAuthData(String user, String pwd) {
         setAuthToken(
                 Base64Util.encodeUtf8String(user + ":" + pwd));
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 76db429..bdf0309 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
@@ -540,36 +540,6 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea
     }
 
     /** {@inheritDoc} */
-    @Override public void setJiraToken(String tok) {
-        teamcity.setJiraToken(tok);
-    }
-
-    /** {@inheritDoc} */
-    @Override public boolean isJiraTokenAvailable() {
-        return teamcity.isJiraTokenAvailable();
-    }
-
-    /** {@inheritDoc} */
-    @Override public String sendJiraComment(String ticket, String comment) throws IOException {
-        return teamcity.sendJiraComment(ticket, comment);
-    }
-
-    /** {@inheritDoc} */
-    @Override public String sendGetToJira(String url) throws IOException {
-        return teamcity.sendGetToJira(url);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void setJiraApiUrl(String url) {
-        teamcity.setJiraApiUrl(url);
-    }
-
-    /** {@inheritDoc} */
-    @Override public String getJiraApiUrl() {
-        return teamcity.getJiraApiUrl();
-    }
-
-    /** {@inheritDoc} */
     @Override public List<Agent> agents(boolean connected, boolean authorized) {
         return teamcity.agents(connected, authorized);
     }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityConnection.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityConnection.java
index 50c707f..6ce1986 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityConnection.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityConnection.java
@@ -103,11 +103,6 @@ public class IgniteTeamcityConnection implements ITeamcity {
     /** Teamcity http connection. */
     @Inject private ITeamcityHttpConnection teamcityHttpConn;
 
-    /**  JIRA authorization token. */
-    private String jiraBasicAuthTok;
-
-    /** URL for JIRA integration. */
-    private String jiraApiUrl;
 
     private String configName; //main properties file name
     private String tcName;
@@ -140,8 +135,7 @@ public class IgniteTeamcityConnection implements ITeamcity {
             logger.error("Failed to set credentials", e);
         }
 
-        setJiraToken(HelperConfig.prepareJiraHttpAuthToken(props));
-        setJiraApiUrl(props.getProperty(HelperConfig.JIRA_API_URL));
+
 
         final File logsDirFile = HelperConfig.resolveLogs(workDir, props);
 
@@ -160,45 +154,6 @@ public class IgniteTeamcityConnection implements ITeamcity {
         return basicAuthTok != null;
     }
 
-    /** {@inheritDoc} */
-    @Override public void setJiraToken(String tok) {
-        jiraBasicAuthTok = tok;
-    }
-
-    /** {@inheritDoc} */
-    @Override public boolean isJiraTokenAvailable() {
-        return jiraBasicAuthTok != null;
-    }
-
-    /** {@inheritDoc} */
-    @AutoProfiling
-    @Override public String sendJiraComment(String ticket, String comment) throws IOException {
-        if (isNullOrEmpty(jiraApiUrl))
-            throw new IllegalStateException("JIRA API URL is not configured for this server.");
-
-        String url = jiraApiUrl + "issue/" + ticket + "/comment";
-
-        return HttpUtil.sendPostAsStringToJira(jiraBasicAuthTok, url, "{\"body\": \"" + comment + "\"}");
-    }
-
-    /** {@inheritDoc} */
-    @Override public String sendGetToJira(String url) throws IOException {
-        if (isNullOrEmpty(jiraApiUrl))
-            throw new IllegalStateException("JIRA API URL is not configured for this server.");
-
-        return HttpUtil.sendGetToJira(jiraBasicAuthTok, jiraApiUrl + url);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void setJiraApiUrl(String url) {
-        jiraApiUrl = url;
-    }
-
-    /** {@inheritDoc} */
-    @Override public String getJiraApiUrl() {
-        return jiraApiUrl;
-    }
-
 
     /** {@inheritDoc} */
     @AutoProfiling
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 5640139..459a669 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
@@ -53,9 +53,6 @@ import static org.apache.ignite.ci.util.XmlUtil.xmlEscapeText;
  */
 @Deprecated
 public class TcHelper implements ITcHelper {
-    /** Logger. */
-    private static final Logger logger = LoggerFactory.getLogger(TcHelper.class);
-
     /** Stop guard. */
     private AtomicBoolean stop = new AtomicBoolean();
 
@@ -70,18 +67,6 @@ public class TcHelper implements ITcHelper {
 
     @Inject private UserAndSessionsStorage userAndSessionsStorage;
 
-    @Inject private PrChainsProcessor prChainsProcessor;
-
-    @Inject private ITeamcityIgnitedProvider tcProv;
-
-    @Inject private IStringCompactor compactor;
-
-    /** */
-    private final ObjectMapper objectMapper;
-
-    public TcHelper() {
-        objectMapper = new ObjectMapper();
-    }
 
     /** {@inheritDoc} */
     @Override public void setServerAuthorizerCreds(ICredentialsProv creds) {
@@ -126,145 +111,6 @@ public class TcHelper implements ITcHelper {
     }
 
 
-    /** {@inheritDoc} */
-    @Override public Visa notifyJira(
-        String srvId,
-        ICredentialsProv prov,
-        String buildTypeId,
-        String branchForTc,
-        String ticket
-    ) {
-        IAnalyticsEnabledTeamcity teamcity = server(srvId, prov);
-
-        ITeamcityIgnited tcIgnited = tcProv.server(srvId, prov);
-
-        List<Integer> builds = tcIgnited.getLastNBuildsFromHistory(buildTypeId, branchForTc, 1);
-
-        if (builds.isEmpty())
-            return new Visa("JIRA wasn't commented - no finished builds to analyze.");
-
-        Integer buildId = builds.get(0);
-
-        FatBuildCompacted fatBuild = tcIgnited.getFatBuild(buildId);
-        Build build = fatBuild.toBuild(compactor);
-
-        build.webUrl = tcIgnited.host() + "viewLog.html?buildId=" + build.getId() + "&buildTypeId=" + build.buildTypeId;
-
-        int blockers;
-
-        JiraCommentResponse res;
-
-        try {
-            List<SuiteCurrentStatus> suitesStatuses = prChainsProcessor.getBlockersSuitesStatuses(buildTypeId, build.branchName, srvId, prov);
-
-            if (suitesStatuses == null)
-                return new Visa("JIRA wasn't commented - no finished builds to analyze.");
-
-            String comment = generateJiraComment(suitesStatuses, build.webUrl, buildTypeId, tcIgnited);
-
-            blockers = suitesStatuses.stream()
-                .mapToInt(suite -> {
-                    if (suite.testFailures.isEmpty())
-                        return 1;
-
-                    return suite.testFailures.size();
-                })
-                .sum();
-
-            res = objectMapper.readValue(teamcity.sendJiraComment(ticket, comment), JiraCommentResponse.class);
-        }
-        catch (Exception e) {
-            String errMsg = "Exception happened during commenting JIRA ticket " +
-                "[build=" + build.getId() + ", errMsg=" + e.getMessage() + ']';
-
-            logger.error(errMsg);
-
-            return new Visa("JIRA wasn't commented - " + errMsg);
-        }
-
-        return new Visa(IJiraIntegration.JIRA_COMMENTED, res, blockers);
-    }
-
-    /** {@inheritDoc} */
-    @Override public Tickets getJiraTickets(String srvId, ICredentialsProv prov, String url) {
-        IAnalyticsEnabledTeamcity teamcity = server(srvId, prov);
-
-        try {
-            return new Gson().fromJson(teamcity.sendGetToJira(url), Tickets.class);
-        }
-        catch (Exception e) {
-            String errMsg = "Exception happened during receiving JIRA tickets " +
-                "[url=" + url + ", errMsg=" + e.getMessage() + ']';
-
-            logger.error(errMsg);
-
-            return new Tickets();
-        }
-    }
-
-    /**
-     * @param suites Suite Current Status.
-     * @param webUrl Build URL.
-     * @return Comment, which should be sent to the JIRA ticket.
-     */
-    private String generateJiraComment(List<SuiteCurrentStatus> suites, String webUrl, String buildTypeId,
-        ITeamcityIgnited tcIgnited) {
-        BuildTypeRefCompacted bt = tcIgnited.getBuildTypeRef(buildTypeId);
-
-        String suiteName = (bt != null ? bt.name(compactor) : buildTypeId);
-
-        StringBuilder res = new StringBuilder();
-
-        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.isEmpty())
-                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(recent.runs).append(" master runs.");
-                    }
-                    else if (recent.failures != null && recent.runs != null) {
-                        res.append(" - ").append(recent.failures).append(" fails / ")
-                            .append(recent.runs).append(" master runs.");
-                    }
-                }
-
-                res.append("\\n");
-            }
-
-            res.append("\\n");
-        }
-
-        if (res.length() > 0) {
-            res.insert(0, "{panel:title=" + suiteName + ": Possible Blockers|" +
-                "borderStyle=dashed|borderColor=#ccc|titleBGColor=#F7D6C1}\\n")
-                .append("{panel}");
-        }
-        else {
-            res.append("{panel:title=").append(suiteName).append(": No blockers found!|")
-                .append("borderStyle=dashed|borderColor=#ccc|titleBGColor=#D6F7C1}{panel}");
-        }
-
-        res.append("\\n").append("[TeamCity *").append(suiteName).append("* Results|").append(webUrl).append(']');
-
-        return xmlEscapeText(res.toString());
-    }
 
     public void close() {
         if (stop.compareAndSet(false, true))
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/IJiraIntegration.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/IJiraIntegration.java
index ef55cc6..b068366 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/IJiraIntegration.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/IJiraIntegration.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.ci.jira.pure;
 
+import java.io.IOException;
 import org.apache.ignite.ci.jira.Tickets;
 import org.apache.ignite.ci.web.model.Visa;
 import org.apache.ignite.ci.user.ICredentialsProv;
@@ -30,19 +31,13 @@ public interface IJiraIntegration {
     public static String JIRA_COMMENTED = "JIRA commented.";
 
     /**
-     * Produce visa message(see {@link Visa}) based on passed
-     * parameters and publish it as a comment for specified ticket
-     * on Jira server.
-     *
-     * @param srvId TC Server ID to take information about token from.
-     * @param prov Credentials.
-     * @param buildTypeId Suite name.
-     * @param branchForTc Branch for TeamCity.
-     * @param ticket JIRA ticket full name. E.g. IGNITE-5555
-     * @return {@link Visa} instance.
+     * @param ticket JIRA ticket full name. E.g 'IGNITE-5555'.
+     * @param comment Comment to be placed in the ticket conversation.
+     * @return {@code True} if ticket was succesfully commented. Otherwise - {@code false}.
+     * @throws IOException If failed to comment JIRA ticket.
+     * @throws IllegalStateException If can't find URL to the JIRA.
      */
-    public Visa notifyJira(String srvId, ICredentialsProv prov, String buildTypeId, String branchForTc,
-        String ticket);
+    public String sendJiraComment(String ticket, String comment) throws IOException;
 
     /**
      * Produce wrapper for collection of Jira tickets for given server.
@@ -65,15 +60,17 @@ public interface IJiraIntegration {
 
     /**
      * @param ticketFullName Ticket full name (e.g IGNITE-8331)
-     *
      * @return URL which is used as link to Jira ticket with specified name.
      */
     public String generateTicketUrl(String ticketFullName);
 
     /**
      * @param ticketFullName Ticket full name (e.g IGNITE-8331)
-     *
      * @return URL which is used as link to Jira comment with specified id.
      */
     public String generateCommentUrl(String ticketFullName, int commentId);
+
+    String getJiraApiUrl();
+
+    boolean isJiraTokenAvailable();
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Jira.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Jira.java
index 7a163cc..d0866dd 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Jira.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Jira.java
@@ -18,15 +18,24 @@
 package org.apache.ignite.ci.jira.pure;
 
 import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.gson.Gson;
 import java.io.File;
+import java.io.IOException;
 import java.util.Properties;
 import javax.inject.Inject;
 import org.apache.ignite.ci.HelperConfig;
+import org.apache.ignite.ci.IAnalyticsEnabledTeamcity;
 import org.apache.ignite.ci.ITcHelper;
+import org.apache.ignite.ci.di.AutoProfiling;
 import org.apache.ignite.ci.jira.Tickets;
+import org.apache.ignite.ci.tcbot.visa.TcBotTriggerAndSignOffService;
 import org.apache.ignite.ci.user.ICredentialsProv;
+import org.apache.ignite.ci.util.HttpUtil;
 import org.apache.ignite.ci.web.model.Visa;
 import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import static com.google.common.base.Strings.isNullOrEmpty;
 
@@ -34,8 +43,8 @@ import static com.google.common.base.Strings.isNullOrEmpty;
  *
  */
 class Jira implements IJiraIntegration {
-    /** */
-    @Inject ITcHelper helper;
+    /** Logger. */
+    private static final Logger logger = LoggerFactory.getLogger(Jira.class);
 
     /** */
     private String jiraUrl;
@@ -43,6 +52,12 @@ class Jira implements IJiraIntegration {
     /** JIRA ticket prefix. */
     @NotNull private String jiraTicketPrefix;
 
+    /** JIRA authorization token. */
+    private String jiraBasicAuthTok;
+
+    /** URL for JIRA integration. */
+    private String jiraApiUrl;
+
     /** {@inheritDoc} */
     @Override public void init(String srvId) {
         final File workDir = HelperConfig.resolveWorkDir();
@@ -54,8 +69,16 @@ class Jira implements IJiraIntegration {
         jiraUrl = props.getProperty(HelperConfig.JIRA_URL);
 
         jiraTicketPrefix = props.getProperty(HelperConfig.JIRA_TICKET_TEMPLATE, "IGNITE-");
+
+        jiraBasicAuthTok = HelperConfig.prepareJiraHttpAuthToken(props);
+        jiraApiUrl = props.getProperty(HelperConfig.JIRA_API_URL);
     }
 
+    /**
+     * @return {@code True} if JIRA authorization token is available.
+     */
+    // boolean isJiraTokenAvailable();
+
     /** {@inheritDoc} */
     @Override public String jiraUrl() {
         return jiraUrl;
@@ -67,14 +90,23 @@ class Jira implements IJiraIntegration {
     }
 
     /** {@inheritDoc} */
-    @Override public Visa notifyJira(String srvId, ICredentialsProv prov, String buildTypeId, String branchForTc,
-        String ticket) {
-        return helper.notifyJira(srvId, prov, buildTypeId, branchForTc, ticket);
+    @Override public Tickets getTickets(String srvId, ICredentialsProv prov, String url) {
+        return getJiraTickets(srvId, prov, url);
     }
 
     /** {@inheritDoc} */
-    @Override public Tickets getTickets(String srvId, ICredentialsProv prov, String url) {
-        return helper.getJiraTickets(srvId, prov, url);
+    public Tickets getJiraTickets(String srvId, ICredentialsProv prov, String url) {
+        try {
+            return new Gson().fromJson(sendGetToJira(url), Tickets.class);
+        }
+        catch (Exception e) {
+            String errMsg = "Exception happened during receiving JIRA tickets " +
+                "[url=" + url + ", errMsg=" + e.getMessage() + ']';
+
+            logger.error(errMsg);
+
+            return new Tickets();
+        }
     }
 
     /** {@inheritDoc} */
@@ -91,4 +123,37 @@ class Jira implements IJiraIntegration {
             "&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-" +
             commentId;
     }
+
+    /** {@inheritDoc} */
+    @Override public String getJiraApiUrl() {
+        return jiraApiUrl;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isJiraTokenAvailable() {
+        return !Strings.isNullOrEmpty(jiraBasicAuthTok);
+    }
+
+    /** {@inheritDoc} */
+    @AutoProfiling
+    @Override public String sendJiraComment(String ticket, String comment) throws IOException {
+        if (isNullOrEmpty(jiraApiUrl))
+            throw new IllegalStateException("JIRA API URL is not configured for this server.");
+
+        String url = jiraApiUrl + "issue/" + ticket + "/comment";
+
+        return HttpUtil.sendPostAsStringToJira(jiraBasicAuthTok, url, "{\"body\": \"" + comment + "\"}");
+    }
+
+    /**
+     * @param url Url.
+     * @return Response as gson string.
+     */
+    public String sendGetToJira(String url) throws IOException {
+        if (isNullOrEmpty(jiraApiUrl))
+            throw new IllegalStateException("JIRA API URL is not configured for this server.");
+
+        return HttpUtil.sendGetToJira(jiraBasicAuthTok, jiraApiUrl + url);
+    }
+
 }
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
index cd7cdc9..5dd7091 100644
--- 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
@@ -32,6 +32,7 @@ import org.apache.ignite.ci.di.AutoProfiling;
 import org.apache.ignite.ci.di.MonitoredTask;
 import org.apache.ignite.ci.jira.pure.IJiraIntegration;
 import org.apache.ignite.ci.jira.pure.IJiraIntegrationProvider;
+import org.apache.ignite.ci.tcbot.visa.TcBotTriggerAndSignOffService;
 import org.apache.ignite.ci.teamcity.ignited.IStringCompactor;
 import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnited;
 import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnitedProvider;
@@ -67,6 +68,8 @@ public class ObserverTask extends TimerTask {
     /** */
     @Inject private VisasHistoryStorage visasHistStorage;
 
+    @Inject private TcBotTriggerAndSignOffService visaIssuer;
+
     /** */
     private ReentrantLock observationLock = new ReentrantLock();
 
@@ -194,7 +197,7 @@ public class ObserverTask extends TimerTask {
 
                     IJiraIntegration jiraIntegration = jiraIntegrationProvider.server(info.srvId);
 
-                    Visa updatedVisa = jiraIntegration.notifyJira(info.srvId, creds, info.buildTypeId,
+                    Visa updatedVisa = visaIssuer.notifyJira(info.srvId, creds, info.buildTypeId,
                         info.branchForTc, info.ticket);
 
                     visasHistStorage.updateLastVisaRequest(info.getContributionKey(), (req -> req.setResult(updatedVisa)));
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/chain/PrChainsProcessor.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/chain/PrChainsProcessor.java
index a4f5d69..6d84015 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/chain/PrChainsProcessor.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/chain/PrChainsProcessor.java
@@ -108,7 +108,7 @@ public class PrChainsProcessor {
 
         IJiraIntegration jiraIntegration = jiraIntegrationProvider.server(srvId);
 
-        res.setJavaFlags(teamcity, gitHubConn);
+        res.setJavaFlags(teamcity, gitHubConn, jiraIntegration);
 
         LatestRebuildMode rebuild;
         if (FullQueryParams.HISTORY.equals(act))
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/TcBotTriggerAndSignOffService.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/TcBotTriggerAndSignOffService.java
index 2921b29..a36944b 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/TcBotTriggerAndSignOffService.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/TcBotTriggerAndSignOffService.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.ci.tcbot.visa;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Strings;
 import com.google.inject.Provider;
 import java.text.DateFormat;
@@ -36,6 +37,7 @@ import javax.ws.rs.QueryParam;
 import org.apache.ignite.ci.HelperConfig;
 import org.apache.ignite.ci.ITcHelper;
 import org.apache.ignite.ci.ITeamcity;
+import org.apache.ignite.ci.TcHelper;
 import org.apache.ignite.ci.github.GitHubBranch;
 import org.apache.ignite.ci.github.GitHubUser;
 import org.apache.ignite.ci.github.PullRequest;
@@ -56,26 +58,36 @@ import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnitedProvider;
 import org.apache.ignite.ci.teamcity.ignited.SyncMode;
 import org.apache.ignite.ci.teamcity.ignited.buildtype.BuildTypeCompacted;
 import org.apache.ignite.ci.teamcity.ignited.buildtype.BuildTypeRefCompacted;
+import org.apache.ignite.ci.teamcity.ignited.fatbuild.FatBuildCompacted;
 import org.apache.ignite.ci.user.ICredentialsProv;
 import org.apache.ignite.ci.web.model.ContributionKey;
+import org.apache.ignite.ci.web.model.JiraCommentResponse;
 import org.apache.ignite.ci.web.model.SimpleResult;
 import org.apache.ignite.ci.web.model.Visa;
 import org.apache.ignite.ci.web.model.VisaRequest;
 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.hist.FailureSummary;
 import org.apache.ignite.ci.web.model.hist.VisasHistoryStorage;
 import org.apache.ignite.internal.util.typedef.F;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import static org.apache.ignite.ci.observer.BuildsInfo.CANCELLED_STATUS;
 import static org.apache.ignite.ci.observer.BuildsInfo.FINISHED_STATUS;
 import static org.apache.ignite.ci.observer.BuildsInfo.RUNNING_STATUS;
+import static org.apache.ignite.ci.util.XmlUtil.xmlEscapeText;
 import static org.apache.ignite.ci.web.rest.parms.FullQueryParams.DEFAULT_TRACKED_BRANCH_NAME;
 
 /**
  * Provides method for TC Bot Visa obtaining
  */
 public class TcBotTriggerAndSignOffService {
+    /** Logger. */
+    private static final Logger logger = LoggerFactory.getLogger(TcBotTriggerAndSignOffService.class);
+
     /** */
     private static final ThreadLocal<DateFormat> THREAD_FORMATTER = new ThreadLocal<DateFormat>() {
         @Override protected DateFormat initialValue() {
@@ -96,7 +108,7 @@ public class TcBotTriggerAndSignOffService {
     @Inject IJiraIntegrationProvider jiraIntegrationProvider;
 
     /** */
-    @Inject private VisasHistoryStorage visasHistoryStorage;
+    @Inject private VisasHistoryStorage visasHistStorage;
 
     /** */
     @Inject private IStringCompactor strCompactor;
@@ -107,9 +119,11 @@ public class TcBotTriggerAndSignOffService {
     /** Helper. */
     @Inject ITcHelper tcHelper;
 
-
     @Inject PrChainsProcessor prChainsProcessor;
 
+    /** Jackson serializer. */
+    private final ObjectMapper objMapper = new ObjectMapper();
+
     /** */
     public void startObserver() {
         buildObserverProvider.get();
@@ -123,7 +137,7 @@ public class TcBotTriggerAndSignOffService {
 
         IJiraIntegration jiraIntegration = jiraIntegrationProvider.server(srvId);
 
-        for (VisaRequest visaRequest : visasHistoryStorage.getVisas()) {
+        for (VisaRequest visaRequest : visasHistStorage.getVisas()) {
             VisaStatus visaStatus = new VisaStatus();
 
             BuildsInfo info = visaRequest.getInfo();
@@ -187,17 +201,6 @@ public class TcBotTriggerAndSignOffService {
 
     /**
      * @param pr Pull Request.
-     * @return JIRA ticket full name or empty string.
-     */
-    @Deprecated
-    @NotNull public static String getTicketFullName(PullRequest pr) {
-        String prefix = "IGNITE-";
-
-        return getTicketFullName(pr, prefix);
-    }
-
-    /**
-     * @param pr Pull Request.
      * @param prefix Ticket prefix.
      * @return JIRA ticket full name or empty string.
      */
@@ -343,17 +346,16 @@ public class TcBotTriggerAndSignOffService {
         if (!Strings.isNullOrEmpty(ticketFullName)) {
             BuildsInfo buildsInfo = new BuildsInfo(srvId, prov, ticketFullName, branchForTc, suiteId);
 
-            VisaRequest lastVisaReq = visasHistoryStorage.getLastVisaRequest(buildsInfo.getContributionKey());
+            VisaRequest lastVisaReq = visasHistStorage.getLastVisaRequest(buildsInfo.getContributionKey());
 
             if (Objects.nonNull(lastVisaReq) && lastVisaReq.isObserving())
                 return new SimpleResult("Jira wasn't commented." +
                     " \"Re-run possible blockers & Comment JIRA\" was triggered for current branch." +
                     " Wait for the end or cancel exsiting observing.");
 
+            Visa visa = notifyJira(srvId, prov, suiteId, branchForTc, ticketFullName);
 
-            Visa visa = jiraIntegration.notifyJira(srvId, prov, suiteId, branchForTc, ticketFullName);
-
-            visasHistoryStorage.put(new VisaRequest(buildsInfo).setResult(visa));
+            visasHistStorage.put(new VisaRequest(buildsInfo).setResult(visa));
 
             return new SimpleResult(visa.status);
         }
@@ -587,4 +589,150 @@ public class TcBotTriggerAndSignOffService {
 
         return status;
     }
+   /**
+     * @param srvId Server id.
+     * @param prov Credentials.
+     * @param buildTypeId Suite name.
+     * @param branchForTc Branch for TeamCity.
+     * @param ticket JIRA ticket full name.
+     * @return {@code Visa} which contains info about JIRA notification.
+     */
+    //Visa notifyJira(String srvId, ICredentialsProv prov, String buildTypeId, String branchForTc, String ticket);
+
+
+    /**
+     * Produce visa message(see {@link Visa}) based on passed
+     * parameters and publish it as a comment for specified ticket
+     * on Jira server.
+     *
+     * @param srvId TC Server ID to take information about token from.
+     * @param prov Credentials.
+     * @param buildTypeId Suite name.
+     * @param branchForTc Branch for TeamCity.
+     * @param ticket JIRA ticket full name. E.g. IGNITE-5555
+     * @return {@link Visa} instance.
+     */
+    public Visa notifyJira(
+        String srvId,
+        ICredentialsProv prov,
+        String buildTypeId,
+        String branchForTc,
+        String ticket
+    ) {
+        ITeamcityIgnited tcIgnited = tcIgnitedProv.server(srvId, prov);
+
+        IJiraIntegration jira = jiraIntegrationProvider.server(srvId);
+
+        List<Integer> builds = tcIgnited.getLastNBuildsFromHistory(buildTypeId, branchForTc, 1);
+
+        if (builds.isEmpty())
+            return new Visa("JIRA wasn't commented - no finished builds to analyze.");
+
+        Integer buildId = builds.get(0);
+
+        FatBuildCompacted fatBuild = tcIgnited.getFatBuild(buildId);
+        Build build = fatBuild.toBuild(compactor);
+
+        build.webUrl = tcIgnited.host() + "viewLog.html?buildId=" + build.getId() + "&buildTypeId=" + build.buildTypeId;
+
+        int blockers;
+
+        JiraCommentResponse res;
+
+        try {
+            List<SuiteCurrentStatus> suitesStatuses = prChainsProcessor.getBlockersSuitesStatuses(buildTypeId, build.branchName, srvId, prov);
+
+            if (suitesStatuses == null)
+                return new Visa("JIRA wasn't commented - no finished builds to analyze.");
+
+            String comment = generateJiraComment(suitesStatuses, build.webUrl, buildTypeId, tcIgnited);
+
+            blockers = suitesStatuses.stream()
+                .mapToInt(suite -> {
+                    if (suite.testFailures.isEmpty())
+                        return 1;
+
+                    return suite.testFailures.size();
+                })
+                .sum();
+
+            res = objMapper.readValue(jira.sendJiraComment(ticket, comment), JiraCommentResponse.class);
+        }
+        catch (Exception e) {
+            String errMsg = "Exception happened during commenting JIRA ticket " +
+                "[build=" + build.getId() + ", errMsg=" + e.getMessage() + ']';
+
+            logger.error(errMsg);
+
+            return new Visa("JIRA wasn't commented - " + errMsg);
+        }
+
+        return new Visa(IJiraIntegration.JIRA_COMMENTED, res, blockers);
+    }
+
+
+    /**
+     * @param suites Suite Current Status.
+     * @param webUrl Build URL.
+     * @return Comment, which should be sent to the JIRA ticket.
+     */
+    private String generateJiraComment(List<SuiteCurrentStatus> suites, String webUrl, String buildTypeId,
+        ITeamcityIgnited tcIgnited) {
+        BuildTypeRefCompacted bt = tcIgnited.getBuildTypeRef(buildTypeId);
+
+        String suiteName = (bt != null ? bt.name(compactor) : buildTypeId);
+
+        StringBuilder res = new StringBuilder();
+
+        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.isEmpty())
+                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(recent.runs).append(" master runs.");
+                    }
+                    else if (recent.failures != null && recent.runs != null) {
+                        res.append(" - ").append(recent.failures).append(" fails / ")
+                            .append(recent.runs).append(" master runs.");
+                    }
+                }
+
+                res.append("\\n");
+            }
+
+            res.append("\\n");
+        }
+
+        if (res.length() > 0) {
+            res.insert(0, "{panel:title=" + suiteName + ": Possible Blockers|" +
+                "borderStyle=dashed|borderColor=#ccc|titleBGColor=#F7D6C1}\\n")
+                .append("{panel}");
+        }
+        else {
+            res.append("{panel:title=").append(suiteName).append(": No blockers found!|")
+                .append("borderStyle=dashed|borderColor=#ccc|titleBGColor=#D6F7C1}{panel}");
+        }
+
+        res.append("\\n").append("[TeamCity *").append(suiteName).append("* Results|").append(webUrl).append(']');
+
+        return xmlEscapeText(res.toString());
+    }
+
 }
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 381855d..84810c4 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
@@ -19,6 +19,7 @@ package org.apache.ignite.ci.web.model.current;
 
 import org.apache.ignite.ci.IAnalyticsEnabledTeamcity;
 import org.apache.ignite.ci.github.pure.IGitHubConnection;
+import org.apache.ignite.ci.jira.pure.IJiraIntegration;
 
 /**
  *
@@ -57,16 +58,17 @@ import org.apache.ignite.ci.github.pure.IGitHubConnection;
     /**
      * @param teamcity TeamCity to get info about tokens.
      * @param gitHubConn GitHub integration associated with this server.
+     * @param jiraIntegration
      */
     public void setJavaFlags(IAnalyticsEnabledTeamcity teamcity,
-        IGitHubConnection gitHubConn) {
+        IGitHubConnection gitHubConn, IJiraIntegration jiraIntegration) {
         if (teamcity.isTeamCityTokenAvailable())
             javaFlags |= TEAMCITY_FLAG;
 
         if (gitHubConn.isGitTokenAvailable())
             javaFlags |= GITHUB_FLAG;
 
-        if (teamcity.isJiraTokenAvailable())
+        if (jiraIntegration.isJiraTokenAvailable())
             javaFlags |= JIRA_FLAG;
     }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuilds.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuilds.java
index f983758..d6f0169 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuilds.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuilds.java
@@ -36,6 +36,8 @@ import org.apache.ignite.ci.ITeamcity;
 import org.apache.ignite.ci.conf.ServerIntegrationLinks;
 import org.apache.ignite.ci.github.pure.IGitHubConnection;
 import org.apache.ignite.ci.github.pure.IGitHubConnectionProvider;
+import org.apache.ignite.ci.jira.pure.IJiraIntegration;
+import org.apache.ignite.ci.jira.pure.IJiraIntegrationProvider;
 import org.apache.ignite.ci.user.ICredentialsProv;
 import org.apache.ignite.ci.tcbot.visa.TcBotTriggerAndSignOffService;
 import org.apache.ignite.ci.web.CtxListener;
@@ -124,10 +126,11 @@ public class TriggerBuilds {
             ITcHelper tcHelper = injector.getInstance(ITcHelper.class);
             final ICredentialsProv creds = ICredentialsProv.get(req);
             ITeamcity teamcity = tcHelper.server(srvId, creds);
+            IJiraIntegration jira = injector.getInstance(IJiraIntegrationProvider.class).server(srvId);
 
             IGitHubConnection gh = injector.getInstance(IGitHubConnectionProvider.class).server(srvId);
 
-            return new ServerIntegrationLinks(srvId, gh.gitApiUrl(), teamcity.getJiraApiUrl());
+            return new ServerIntegrationLinks(srvId, gh.gitApiUrl(), jira.getJiraApiUrl());
         }).filter(Objects::nonNull).collect(Collectors.toSet());
     }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/visa/TcBotVisaService.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/visa/TcBotVisaService.java
index 241d2f7..f7bfa78 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/visa/TcBotVisaService.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/visa/TcBotVisaService.java
@@ -110,7 +110,8 @@ public class TcBotVisaService {
         if (!prov.hasAccess(srvId))
             throw ServiceUnauthorizedException.noCreds(srvId);
 
-        TcBotTriggerAndSignOffService instance = CtxListener.getInjector(ctx).getInstance(TcBotTriggerAndSignOffService.class);
+        TcBotTriggerAndSignOffService instance = CtxListener.getInjector(ctx)
+            .getInstance(TcBotTriggerAndSignOffService.class);
 
         return instance.currentVisaStatus(srvId, prov, suiteId, tcBranch);
     }