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/08/16 11:17:15 UTC

[ignite-teamcity-bot] branch master updated: Board: Suites problem support, migration to new displaying of issues lists; Board: Web links supported to refer to the tests Board: Commit author, hidden email

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


The following commit(s) were added to refs/heads/master by this push:
     new e2c8ab9  Board: Suites problem support, migration to new displaying of issues lists; Board: Web links supported to refer to the tests Board: Commit author, hidden email
e2c8ab9 is described below

commit e2c8ab9b0f840d02bbfd9df6c0d912987db30cde
Author: Dmitriy Pavlov <dp...@apache.org>
AuthorDate: Fri Aug 16 14:17:07 2019 +0300

    Board: Suites problem support, migration to new displaying of issues lists;
    Board: Web links supported to refer to the tests
    Board: Commit author, hidden email
    
    Queue builds: separate monitoring of all suites triggering for debug
    
    UI: fixes for Authorisation from board; Chain Href fixed for build and history - Fixes #154.
    Signed-off-by: Dmitriy Pavlov <dp...@apache.org>
---
 .../org/apache/ignite/ci/jobs/CheckQueueJob.java   | 123 +++++++++++----------
 .../src/main/webapp/board/index.html               |  29 +++--
 .../src/main/webapp/js/common-1.6.js               |   2 +-
 .../src/main/webapp/js/testfails-2.2.js            |  24 ++--
 .../ignite/tcbot/engine/board/BoardService.java    | 104 +++++++++--------
 .../ignite/tcbot/engine/ui/BoardDefectIssueUi.java |  19 +++-
 .../tcbot/engine/ui/BoardDefectSummaryUi.java      |  83 +++++++-------
 .../apache/ignite/tcbot/engine/ui/DsChainUi.java   |  16 ++-
 .../apache/ignite/tcbot/engine/ui/DsSuiteUi.java   |  18 +--
 .../ignite/tcbot/engine/ui/DsTestFailureUi.java    |   4 +-
 10 files changed, 234 insertions(+), 188 deletions(-)

diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jobs/CheckQueueJob.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jobs/CheckQueueJob.java
index 537792f..e8eac15 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jobs/CheckQueueJob.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jobs/CheckQueueJob.java
@@ -55,7 +55,7 @@ public class CheckQueueJob implements Runnable {
 
     /** Percentage of free agents required to trigger build. */
     private static final int CHECK_QUEUE_MIN_FREE_AGENTS_PERCENT =
-        Integer.getInteger("CHECK_QUEUE_MIN_FREE_AGENTS_PERCENT", 20);
+        Integer.getInteger("CHECK_QUEUE_MIN_FREE_AGENTS_PERCENT", 15);
 
     /** */
     private ITcBotUserCreds creds;
@@ -94,7 +94,7 @@ public class CheckQueueJob implements Runnable {
     /**   */
     @SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
     @AutoProfiling
-    @MonitoredTask(name = "Check Queue")
+    @MonitoredTask(name = "Check Servers Queue")
     protected String runEx() {
         if (Boolean.valueOf(System.getProperty(AUTO_TRIGGERING_BUILD_DISABLED))) {
             final String msg = "Automatic build triggering was disabled.";
@@ -145,8 +145,8 @@ public class CheckQueueJob implements Runnable {
     @SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
     @AutoProfiling
     @MonitoredTask(name = "Check Server Queue", nameExtArgIndex = 0)
-    protected String checkQueue(String srvId, List<ITrackedChain> chains) {
-        ITeamcityIgnited tcIgn = tcIgnitedProv.server(srvId, creds);
+    protected String checkQueue(String srvCode, List<ITrackedChain> chains) {
+        ITeamcityIgnited tcIgn = tcIgnitedProv.server(srvCode, creds);
 
         List<Agent> agents = tcIgn.agents(true, true);
 
@@ -170,96 +170,97 @@ public class CheckQueueJob implements Runnable {
         logger.info("There are more than {}% free agents (total={}, free={}).", CHECK_QUEUE_MIN_FREE_AGENTS_PERCENT,
             total, total - running);
 
-        String selfLogin = creds.getUser(srvId);
+        String selfLogin = creds.getUser(srvCode);
 
         tcIgn.actualizeRecentBuildRefs();
 
         StringBuilder res = new StringBuilder();
 
         for (ITrackedChain chain : chains) {
-            if(!Objects.equals(chain.serverCode(), srvId))
+            if (!Objects.equals(chain.serverCode(), srvCode))
                 continue;
 
-            boolean trigger = true;
+            String result = checkIfChainTriggerable(chain.serverCode(), chain.tcSuiteId(), chain.tcBranch(), tcIgn, selfLogin, chain);
 
-            List<BuildRefCompacted> buildsForBr = tcIgn.getQueuedBuildsCompacted(chain.tcBranch());
+            res.append(result).append("; ");
+        }
 
-            for (BuildRefCompacted refComp : buildsForBr) {
-                Integer buildId = refComp.getId();
-                if (buildId == null)
-                    continue; // should not occur;
+        return res.toString();
+    }
 
-                final FatBuildCompacted fatBuild = tcIgn.getFatBuild(buildId);
-                final Build build = fatBuild.toBuild(compactor);
-                final Triggered triggered = build.getTriggered();
+    @SuppressWarnings("WeakerAccess")
+    @MonitoredTask(name = "Check Server Queue", nameExtArgsIndexes = {0, 1, 2})
+    protected String checkIfChainTriggerable(String serverCode,
+                                             String buildTypeId,
+                                             String tcBranch,
+                                             ITeamcityIgnited tcIgn,
+                                             String selfLogin,
+                                             ITrackedChain chain) {
+        List<BuildRefCompacted> buildsForBr = tcIgn.getQueuedBuildsCompacted(tcBranch);
 
-                if (triggered == null) {
-                    logger.info("Unable to get triggering info for queued build {} (type={}).", buildId, build.buildTypeId);
+        for (BuildRefCompacted refComp : buildsForBr) {
+            Integer buildId = refComp.getId();
+            if (buildId == null)
+                continue; // should not occur;
 
-                    continue;
-                }
+            final FatBuildCompacted fatBuild = tcIgn.getFatBuild(buildId);
+            final Build build = fatBuild.toBuild(compactor);
+            final Triggered triggered = build.getTriggered();
 
-                User user = build.getTriggered().getUser();
+            if (triggered == null) {
+                logger.info("Unable to get triggering info for queued build {} (type={}).", buildId, build.buildTypeId);
 
-                if (user == null) {
-                    logger.info("Unable to get username for queued build {} (type={}).", buildId, build.buildTypeId);
+                continue;
+            }
 
-                    continue;
-                }
+            User user = build.getTriggered().getUser();
 
-                String login = user.username;
+            if (user == null) {
+                logger.info("Unable to get username for queued build {} (type={}).", buildId, build.buildTypeId);
 
-                if (selfLogin.equalsIgnoreCase(login)) {
-                    final String msg
-                            = MessageFormat.format("Queued build {0} was early triggered " +
-                            "(user {1}, branch {2}, suite {3})." +
-                            " Will not start Ignite build.", buildId, login, chain.tcBranch(), build.buildTypeId);
+                continue;
+            }
 
-                    logger.info(msg);
+            String login = user.username;
 
-                    res.append(msg).append("; ");
+            if (selfLogin.equalsIgnoreCase(login)) {
+                final String msg
+                        = MessageFormat.format("Queued build {0} was early triggered " +
+                        "(user {1}, branch {2}, suite {3})." +
+                        " Will not start new build.", buildId, login, tcBranch, build.buildTypeId);
 
-                    trigger = false;
+                logger.info(msg);
 
-                    break;
-                }
+                return msg;
             }
+        }
 
-            if (!trigger)
-                continue;
-
-            long curr = System.currentTimeMillis();
-            long delay = chain.triggerBuildQuietPeriod();
-
-            if (delay > 0) {
-                Long lastStart = startTimes.get(chain);
+        long curr = System.currentTimeMillis();
+        long delay = chain.triggerBuildQuietPeriod();
 
-                long minsPassed;
+        if (delay > 0) {
+            Long lastStart = startTimes.get(chain);
 
-                if (lastStart != null &&
-                    (minsPassed = TimeUnit.MILLISECONDS.toMinutes(curr - lastStart)) < delay) {
+            long minsPassed;
 
-                    final String msg = MessageFormat.format("Skip triggering build, timeout has not expired " +
-                                    "(server={0}, suite={1}, branch={2}, delay={3} mins, passed={4} mins)",
-                            chain.serverCode(), chain.tcSuiteId(), chain.tcBranch(),
-                            chain.triggerBuildQuietPeriod(), minsPassed);
-                    logger.info(msg);
+            if (lastStart != null &&
+                (minsPassed = TimeUnit.MILLISECONDS.toMinutes(curr - lastStart)) < delay) {
 
-                    res.append(msg).append("; ");
+                final String msg = MessageFormat.format("Skip triggering build, timeout has not expired " +
+                                "(server={0}, suite={1}, branch={2}, delay={3} mins, passed={4} mins)",
+                        serverCode, buildTypeId, tcBranch,
+                        chain.triggerBuildQuietPeriod(), minsPassed);
+                logger.info(msg);
 
-                    continue;
-                }
+                return msg;
             }
+        }
 
-            startTimes.put(chain, curr);
-
-            tcIgn.triggerBuild(chain.tcSuiteId(), chain.tcBranch(), true, false,
-                    chain.generateBuildParameters());
+        startTimes.put(chain, curr);
 
-            res.append(chain.tcBranch()).append(" ").append(chain.tcBranch()).append(" triggered; ");
-        }
+        tcIgn.triggerBuild(buildTypeId, tcBranch, true, false, chain.generateBuildParameters());
 
-        return res.toString();
+        return buildTypeId + " " +  tcBranch  + " triggered; ";
     }
 
     /**
diff --git a/ignite-tc-helper-web/src/main/webapp/board/index.html b/ignite-tc-helper-web/src/main/webapp/board/index.html
index 92ebc68..e7da95d 100644
--- a/ignite-tc-helper-web/src/main/webapp/board/index.html
+++ b/ignite-tc-helper-web/src/main/webapp/board/index.html
@@ -53,10 +53,10 @@
                     {text: 'Suites', value: 'suitesSummary'},
                     {text: "Commits", value: 'blameCandidateSummary'},
                     {text: "Issues", value: 'cntissues'},
-                    {text: "Fixed", value: 'fixedissues'},
+                    {text: "Fixed", value: 'cntfixedissues'},
                     {text: "Ignored", value: 'cntignoredissues'},
                     {text: "Not Fixed", value: 'cntfailingissues'},
-                    {text: "Unclear", value: 'unclearissues'}
+                    {text: "Unclear", value: 'cntunclearissues'}
                 ]
             },
             methods: {
@@ -197,7 +197,14 @@
                             </td>
 
                             <td>{{ issue.issueType }}</td>
-                            <td>{{ issue.name }}</td>
+                            <td>
+                                <a v-if="issue.webUrl != null" :href="issue.webUrl">
+                                    {{ issue.name }}
+                                </a>
+                                <span v-else>
+                                {{ issue.name }}
+                                </span>
+                            </td>
                         </tr>
                     </table>
 
@@ -220,8 +227,10 @@
             <template v-slot:item.cntIssues="{ item }">
                 {{ item.cntIssues }}
             </template>
-            <template v-slot:item.fixedIssues="{ item }">
-                <span class='visaStage' style="background: #12AD5E" v-if="item.fixedIssues!=null" >  {{ item.fixedIssues }} </span>
+            <template v-slot:item.cntFixedIssues="{ item }">
+                <span class='visaStage' style="background: #12AD5E"
+                      v-if="item.cntFixedIssues!=0"
+                      :title="item.summaryFixedIssues">  {{ item.cntFixedIssues }} </span>
             </template>
             <template v-slot:item.cntIgnoredIssues="{ item }">
                 <span class='visaStage' style="background: darkorange"
@@ -229,11 +238,15 @@
                       :title="item.summaryIgnoredIssues"> {{ item.cntIgnoredIssues }} </span>
             </template>
             <template v-slot:item.cntFailingIssues="{ item }" >
-                <span class='visaStage' style="background: red" v-if="item.cntFailingIssues!=0" :title="item.summaryFailingIssues">  {{ item.cntFailingIssues }} </span>
+                <span class='visaStage' style="background: red"
+                      v-if="item.cntFailingIssues!=0"
+                      :title="item.summaryFailingIssues">  {{ item.cntFailingIssues }} </span>
             </template>
 
-            <template v-slot:item.unclearIssues="{ item }" >
-                <span class='visaStage' style="background: grey"  v-if="item.unclearIssues!=null" >  {{ item.unclearIssues }} </span>
+            <template v-slot:item.cntUnclearIssues="{ item }" >
+                <span class='visaStage' style="background: grey"
+                      v-if="item.cntUnclearIssues!=0"
+                      :title="item.summaryUnclearIssues" >  {{ item.cntUnclearIssues }} </span>
             </template>
 
         </v-data-table>
diff --git a/ignite-tc-helper-web/src/main/webapp/js/common-1.6.js b/ignite-tc-helper-web/src/main/webapp/js/common-1.6.js
index 76b2c9a..f80ad3f 100644
--- a/ignite-tc-helper-web/src/main/webapp/js/common-1.6.js
+++ b/ignite-tc-helper-web/src/main/webapp/js/common-1.6.js
@@ -198,7 +198,7 @@ function showMenu(menuData) {
 function authorizeServer() {
     $.ajax({
         type: "POST",
-        url: "rest/user/authorize",
+        url: "/rest/user/authorize",
         success: resetMenu,
         error:   showErrInLoadStatus
     });
diff --git a/ignite-tc-helper-web/src/main/webapp/js/testfails-2.2.js b/ignite-tc-helper-web/src/main/webapp/js/testfails-2.2.js
index c73068b..2fb3542 100644
--- a/ignite-tc-helper-web/src/main/webapp/js/testfails-2.2.js
+++ b/ignite-tc-helper-web/src/main/webapp/js/testfails-2.2.js
@@ -131,12 +131,21 @@ function showChainCurrentStatusData(chain, settings) {
 
     if (isDefinedAndFilled(findGetParameter("suiteId")))
         parentSuitId = findGetParameter("suiteId");
-    else if (isDefinedAndFilled(chain))
-        parentSuitId = findGetParameter("buildTypeId", chain.webToHist);
-
-    if (isDefinedAndFilled(parentSuitId)) {
-        res += "<tr><td><b> Suite: </b></td><td>[" + parentSuitId + "] ";
-        res += " <a href='" + chain.webToHist + "'>[TC history]</a>";
+    else if (isDefinedAndFilled(chain.suiteId))
+        parentSuitId = chain.suiteId;
+
+    if (isDefinedAndFilled(parentSuitId) || isDefinedAndFilled(chain.webToHist)) {
+        res += "<tr><td>";
+        if (isDefinedAndFilled(parentSuitId)) {
+            res += "<b> Suite: </b></td>" +
+                "<td>[" + parentSuitId + "] ";
+        }
+        if (isDefinedAndFilled(chain.webToHist)) {
+            res += " <a href='" + chain.webToHist + "' title='Chain history'>[TC history]</a>";
+        }
+        if (isDefinedAndFilled(chain.webToBuild)) {
+            res += " <a href='" + chain.webToBuild + "' title='Build without applying re-runs'>[Build]</a>";
+        }
         res += "</td></tr>";
     }
 
@@ -155,9 +164,6 @@ function showChainCurrentStatusData(chain, settings) {
         res += "empty";
 
     res += " ";
-    res += "<a href='longRunningTestsReport.html'>";
-    res += "Long running tests report";
-    res += "</a>";
 
     var moreInfoTxt = "";
 
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/BoardService.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/BoardService.java
index 0405eef..e134512 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/BoardService.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/BoardService.java
@@ -30,13 +30,12 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import javax.annotation.Nullable;
 import javax.inject.Inject;
-
-import com.google.common.base.Preconditions;
 import org.apache.ignite.ci.issue.Issue;
 import org.apache.ignite.ci.issue.IssueKey;
 import org.apache.ignite.ci.teamcity.ignited.change.ChangeCompacted;
 import org.apache.ignite.ci.teamcity.ignited.change.ChangeDao;
 import org.apache.ignite.ci.teamcity.ignited.fatbuild.FatBuildCompacted;
+import org.apache.ignite.ci.teamcity.ignited.fatbuild.ProblemCompacted;
 import org.apache.ignite.ci.user.TcHelperUser;
 import org.apache.ignite.tcbot.common.conf.ITcServerConfig;
 import org.apache.ignite.tcbot.common.interceptor.MonitoredTask;
@@ -53,6 +52,8 @@ import org.apache.ignite.tcbot.engine.issue.IssueType;
 import org.apache.ignite.tcbot.engine.ui.BoardDefectIssueUi;
 import org.apache.ignite.tcbot.engine.ui.BoardDefectSummaryUi;
 import org.apache.ignite.tcbot.engine.ui.BoardSummaryUi;
+import org.apache.ignite.tcbot.engine.ui.DsSuiteUi;
+import org.apache.ignite.tcbot.engine.ui.DsTestFailureUi;
 import org.apache.ignite.tcbot.engine.user.IUserStorage;
 import org.apache.ignite.tcbot.persistence.IStringCompactor;
 import org.apache.ignite.tcbot.persistence.scheduler.IScheduler;
@@ -114,9 +115,9 @@ public class BoardService {
                 rebuild = !freshRebuild.isEmpty() ? freshRebuild.stream().findFirst() : Optional.empty();
 
                 for (DefectIssue issue : cause.issues()) {
-                    BoardDefectIssueUi issueUi = processIssue(defectUi, rebuild, issue);
+                    BoardDefectIssueUi issueUi = processIssue(tcIgn, rebuild, issue);
 
-                    defectUi.addIssue(compactor.getStringFromId(issue.testNameCid()), issueUi);
+                    defectUi.addIssue(issueUi);
                 }
             }
 
@@ -128,55 +129,70 @@ public class BoardService {
         return res;
     }
 
-    public BoardDefectIssueUi processIssue(BoardDefectSummaryUi defectUi, Optional<FatBuildCompacted> rebuild,
-        DefectIssue issue) {
+    public BoardDefectIssueUi processIssue(ITeamcityIgnited tcIgn,
+                                           Optional<FatBuildCompacted> rebuild,
+                                           DefectIssue issue) {
         Optional<ITest> testResult;
 
-        if (rebuild.isPresent()) {
-            testResult = rebuild.get().getAllTests()
-                .filter(t -> t.testName() == issue.testNameCid())
-                .findAny();
-        }
-        else
-            testResult = Optional.empty();
-
-        IssueResolveStatus status;
-        if (testResult.isPresent()) {
-            ITest test = testResult.get();
+        String issueType = compactor.getStringFromId(issue.issueTypeCode());
 
-            if (test.isIgnoredTest() || test.isMutedTest())
-                status = IssueResolveStatus.IGNORED;
-            else {
-                boolean failed = test.isFailedTest(compactor);
-                if (!failed) {
-                    defectUi.addFixedIssue();
+        boolean suiteProblem = IssueType.newCriticalFailure.code().equals(issueType)
+                || IssueType.newTrustedSuiteFailure.code().equals(issueType);
 
-                    status = IssueResolveStatus.FIXED;
+        String webUrl = null;
+        IssueResolveStatus status;
+        if (suiteProblem) {
+            if (rebuild.isPresent()) {
+                FatBuildCompacted fatBuildCompacted = rebuild.get();
+                List<ProblemCompacted> problems = fatBuildCompacted.problems();
+
+                if (IssueType.newCriticalFailure.code().equals(issueType)) {
+                    boolean hasCriticalProblem = problems.stream().anyMatch(occurrence -> occurrence.isCriticalProblem(compactor));
+                    status = hasCriticalProblem ? IssueResolveStatus.FAILING : IssueResolveStatus.FIXED;
+                } else {
+                    boolean hasBuildProblem = problems.stream().anyMatch(p -> !p.isFailedTests(compactor) && !p.isSnapshotDepProblem(compactor));
+                    status = hasBuildProblem ? IssueResolveStatus.FAILING : IssueResolveStatus.FIXED;
                 }
-                else {
-                    defectUi.addNotFixedIssue();
-
-                    status = IssueResolveStatus.FAILING;
 
-                }
+                webUrl = DsSuiteUi.buildWebLinkToHist(tcIgn,
+                        fatBuildCompacted.buildTypeId(compactor),
+                        fatBuildCompacted.branchName(compactor)
+                );
+            } else {
+                status = IssueResolveStatus.UNKNOWN;
             }
-        } else{
-            //exception for new test. removal of test means test is fixed
-            if (IssueType.newContributedTestFailure.code().equals(compactor.getStringFromId(issue.issueTypeCode()))) {
-                defectUi.addFixedIssue();
-
-                status = IssueResolveStatus.FIXED;
+        } else {
+            if (rebuild.isPresent()) {
+                testResult = rebuild.get().getAllTests()
+                        .filter(t -> t.testName() == issue.testNameCid())
+                        .findAny();
+            } else
+                testResult = Optional.empty();
+
+            if (testResult.isPresent()) {
+                ITest test = testResult.get();
+
+                if (test.isIgnoredTest() || test.isMutedTest())
+                    status = IssueResolveStatus.IGNORED;
+                else
+                    status = test.isFailedTest(compactor) ? IssueResolveStatus.FAILING : IssueResolveStatus.FIXED;
+
+                FatBuildCompacted fatBuildCompacted = rebuild.get();
+                Long testNameId = test.getTestId();
+                String projectId = fatBuildCompacted.projectId(compactor);
+                String branchName = fatBuildCompacted.branchName(compactor);
+
+                webUrl = DsTestFailureUi.buildWebLink(tcIgn, testNameId, projectId, branchName);
             }
             else {
-                defectUi.addUnclearIssue();
-                status = IssueResolveStatus.UNKNOWN;
+                //exception for new test. removal of test means test is fixed
+                status = IssueType.newContributedTestFailure.code().equals(issueType)
+                    ? IssueResolveStatus.FIXED
+                    : IssueResolveStatus.UNKNOWN;
             }
         }
 
-        return new BoardDefectIssueUi(status,
-            compactor, issue,
-            issue.testNameCid(),
-            issue.issueTypeCode());
+        return new BoardDefectIssueUi(status, compactor, issue, suiteProblem, webUrl);
     }
 
     public void issuesToDefectsLater() {
@@ -188,7 +204,7 @@ public class BoardService {
         Stream<Issue> stream = issuesStorage.allIssues();
 
         //todo make property how old issues can be considered as configuration parameter
-        long minIssueTs = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7);
+        long minIssueTs = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(9);
 
         //todo not so good to to call init() twice
         fatBuildDao.init();
@@ -297,8 +313,8 @@ public class BoardService {
         String principalId = creds.getPrincipalId();
         TcHelperUser user = userStorage.getUser(principalId);
 
-        int stringId = compactor.getStringId(principalId);
-        defect.resolvedByUsernameId(stringId);
+        int strId = compactor.getStringId(principalId);
+        defect.resolvedByUsernameId(strId);
 
         defectStorage.save(defect);
     }
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectIssueUi.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectIssueUi.java
index 76927ad..ee05ba1 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectIssueUi.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectIssueUi.java
@@ -21,25 +21,35 @@ import org.apache.ignite.tcbot.engine.board.IssueResolveStatus;
 import org.apache.ignite.tcbot.engine.defect.DefectIssue;
 import org.apache.ignite.tcbot.persistence.IStringCompactor;
 
+import javax.annotation.Nullable;
+
 /**
  * UI version for displaying org.apache.ignite.tcbot.engine.defect.DefectIssue and its current status
  */
+@SuppressWarnings("unused")
 public class BoardDefectIssueUi {
     private transient IStringCompactor compactor;
     private transient DefectIssue issue;
+    private boolean suiteProblem;
+    @Nullable
+    private String webUrl;
     private IssueResolveStatus status;
 
     public BoardDefectIssueUi(IssueResolveStatus status, IStringCompactor compactor,
-        DefectIssue issue, int testNameCid, int code) {
+                              DefectIssue issue, boolean suiteProblem,
+                              @Nullable String webUrl) {
         this.status = status;
         this.compactor = compactor;
         this.issue = issue;
+        this.suiteProblem = suiteProblem;
+        this.webUrl = webUrl;
     }
 
     public String getName() {
         String name = compactor.getStringFromId(issue.testNameCid());
 
-        //todo check if it is a suite name
+        if(suiteProblem)
+            return name;
 
         String suiteName = null, testName = null;
 
@@ -67,4 +77,9 @@ public class BoardDefectIssueUi {
     public IssueResolveStatus status() {
         return status;
     }
+
+    @Nullable
+    public String getWebUrl() {
+        return webUrl;
+    }
 }
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectSummaryUi.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectSummaryUi.java
index d9a61cb..44021b8 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectSummaryUi.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectSummaryUi.java
@@ -16,13 +16,16 @@
  */
 package org.apache.ignite.tcbot.engine.ui;
 
+import com.google.common.base.Strings;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import org.apache.ignite.tcbot.engine.board.IssueResolveStatus;
 import org.apache.ignite.tcbot.engine.defect.BlameCandidate;
 import org.apache.ignite.tcbot.engine.defect.DefectCompacted;
@@ -34,13 +37,6 @@ public class BoardDefectSummaryUi {
 
     public String branch;
 
-    public Integer cntIssues;
-    public Integer fixedIssues;
-    public Integer notFixedIssues;
-    public Integer unclearIssues;
-
-    @Deprecated
-    public List<String> testOrSuitesAffected = new ArrayList<>();
     private Set<String> tags = new HashSet<>();
 
     private List<BoardDefectIssueUi> issuesList = new ArrayList<>();
@@ -67,9 +63,10 @@ public class BoardDefectSummaryUi {
         return limitedListPrint(suites, t -> t);
     }
 
-    public <X> String limitedListPrint(List<X> suites, Function<X, String> elemToStr) {
+    private static <X> String limitedListPrint(Collection<X> suites, Function<X, String> elemToStr) {
         StringBuilder res = new StringBuilder();
         int i = 0;
+
         for (X next : suites) {
             if (i >= 3) {
                 int rest = suites.size() - i;
@@ -102,8 +99,16 @@ public class BoardDefectSummaryUi {
             if (fullName != null)
                 return fullName;
 
-            // default, returing VCS usename used
-            return next.vcsUsername(compactor);
+            // default, returning VCS username used
+            String strVcsUsername = next.vcsUsername(compactor);
+
+            if (!Strings.isNullOrEmpty(strVcsUsername) &&
+                    strVcsUsername.contains("<") && strVcsUsername.contains(">")) {
+                int emailStartIdx = strVcsUsername.indexOf('<');
+                return strVcsUsername.substring(0, emailStartIdx).trim();
+            }
+
+            return strVcsUsername;
         });
     }
 
@@ -115,13 +120,7 @@ public class BoardDefectSummaryUi {
         return defect.id();
     }
 
-    public void addIssue(String testOrBuildName, BoardDefectIssueUi issue) {
-        if (cntIssues == null)
-            cntIssues = 0;
-
-        cntIssues++;
-
-        testOrSuitesAffected.add(testOrBuildName);
+    public void addIssue(BoardDefectIssueUi issue) {
         issuesList.add(issue);
     }
 
@@ -129,8 +128,16 @@ public class BoardDefectSummaryUi {
         return Collections.unmodifiableList(issuesList);
     }
 
+    public int getCntIssues() {
+        return issuesList.size();
+    }
+
+    private Stream<BoardDefectIssueUi> issues(IssueResolveStatus type) {
+        return issuesList.stream().filter(iss -> iss.status() == type);
+    }
+
     public List<BoardDefectIssueUi> getIgnoredIssues() {
-        return issuesList.stream().filter(iss -> iss.status() == IssueResolveStatus.IGNORED).collect(Collectors.toList());
+        return issues(IssueResolveStatus.IGNORED).collect(Collectors.toList());
     }
 
     public String getSummaryIgnoredIssues() {
@@ -138,12 +145,11 @@ public class BoardDefectSummaryUi {
     }
 
     public Integer getCntIgnoredIssues() {
-        return getIgnoredIssues().size();
+        return (int) issues(IssueResolveStatus.IGNORED).count();
     }
 
-
     public List<BoardDefectIssueUi> getFailingIssues() {
-        return issuesList.stream().filter(iss -> iss.status() == IssueResolveStatus.FAILING).collect(Collectors.toList());
+        return issues(IssueResolveStatus.FAILING).collect(Collectors.toList());
     }
 
     public String getSummaryFailingIssues() {
@@ -151,33 +157,34 @@ public class BoardDefectSummaryUi {
     }
 
     public Integer getCntFailingIssues() {
-        return getFailingIssues().size();
+        return (int) issues(IssueResolveStatus.FAILING).count();
     }
 
+    public List<BoardDefectIssueUi> getFixedIssues() {
+        return issues(IssueResolveStatus.FIXED).collect(Collectors.toList());
+    }
 
-    public void addFixedIssue() {
-        if (fixedIssues == null)
-            fixedIssues = 0;
-
-        fixedIssues++;
+    public String getSummaryFixedIssues() {
+        return limitedListPrint(getFixedIssues(), BoardDefectIssueUi::getName);
     }
 
-    public void addNotFixedIssue() {
-        if (notFixedIssues == null)
-            notFixedIssues = 0;
+    public Integer getCntFixedIssues() {
+        return (int) issues(IssueResolveStatus.FIXED).count();
+    }
 
-        notFixedIssues++;
+    public List<BoardDefectIssueUi> getUnclearIssues() {
+        return issues(IssueResolveStatus.UNKNOWN).collect(Collectors.toList());
     }
 
-    public void addTags(Set<String> parameters) {
-        this.tags.addAll(parameters);
+    public String getSummaryUnclearIssues() {
+        return limitedListPrint(getUnclearIssues(), BoardDefectIssueUi::getName);
     }
 
-    @Deprecated
-    public void addUnclearIssue() {
-        if (unclearIssues == null)
-            unclearIssues = 0;
+    public Integer getCntUnclearIssues() {
+        return (int) issues(IssueResolveStatus.UNKNOWN).count();
+    }
 
-        unclearIssues++;
+    public void addTags(Set<String> parameters) {
+        this.tags.addAll(parameters);
     }
 }
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/DsChainUi.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/DsChainUi.java
index 5034abc..bc8f2da 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/DsChainUi.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/DsChainUi.java
@@ -28,7 +28,6 @@ import java.util.stream.Stream;
 import javax.annotation.Nullable;
 import org.apache.ignite.internal.util.typedef.T2;
 import org.apache.ignite.tcbot.common.util.CollectionUtil;
-import org.apache.ignite.tcbot.common.util.UrlUtil;
 import org.apache.ignite.tcbot.engine.chain.FullChainRunCtx;
 import org.apache.ignite.tcbot.engine.chain.MultBuildRunCtx;
 import org.apache.ignite.tcbot.engine.chain.TestCompactedMult;
@@ -72,6 +71,8 @@ public class DsChainUi {
     /** Web Href. to suite runs history. */
     public String webToHist = "";
 
+    public String suiteId;
+
     /** Web Href. to suite particular run */
     public String webToBuild = "";
 
@@ -233,7 +234,11 @@ public class DsChainUi {
         artifcactPublishingDurationPrintable = ctx.artifcactPublishingDurationPrintable(suiteFilter);
         dependeciesResolvingDurationPrintable = ctx.dependeciesResolvingDurationPrintable(suiteFilter);
         lostInTimeouts = ctx.getLostInTimeoutsPrintable(suiteFilter);
-        webToHist = buildWebLink(tcIgnited, ctx);
+
+        String suiteId = ctx.suiteId();
+        this.suiteId  = suiteId;
+        chainName = ctx.suiteName();
+        webToHist = DsSuiteUi.buildWebLinkToHist(tcIgnited, suiteId, ctx.branchName());
         webToBuild = buildWebLinkToBuild(tcIgnited, ctx);
 
         Stream<T2<MultBuildRunCtx, TestCompactedMult>> allLongRunning = ctx.suites()
@@ -284,13 +289,6 @@ public class DsChainUi {
         return teamcity.host() + "viewLog.html?buildId=" + chain.getSuiteBuildId();
     }
 
-    private static String buildWebLink(ITeamcityIgnited teamcity, FullChainRunCtx suite) {
-        final String branch = normalizeBranch(suite.branchName());
-        return teamcity.host() + "viewType.html?buildTypeId=" + suite.suiteId()
-            + "&branch=" + UrlUtil.escape(branch)
-            + "&tab=buildTypeStatusDiv";
-    }
-
     /**
      * @return Server name.
      */
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/DsSuiteUi.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/DsSuiteUi.java
index c3f49bb..772b015 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/DsSuiteUi.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/DsSuiteUi.java
@@ -175,7 +175,7 @@ public class DsSuiteUi extends ShortSuiteUi {
         artifcactPublishingDurationPrintable = millisToDurationPrintable(suite.artifcactPublishingDuration());
         dependeciesResolvingDurationPrintable = millisToDurationPrintable(suite.dependeciesResolvingDuration());
         testsDurationPrintable = millisToDurationPrintable(suite.getAvgTestsDuration());
-        webToHist = buildWebLinkToHist(tcIgnited, suite);
+        webToHist = buildWebLinkToHist(tcIgnited, suite, suite.branchName());
         webToHistBaseBranch = buildWebLinkToHist(tcIgnited, suite, baseBranch);
 
         if (true) {
@@ -343,23 +343,13 @@ public class DsSuiteUi extends ShortSuiteUi {
         return teamcity.host() + "viewLog.html?buildId=" + suite.getBuildId();
     }
 
-    private static String buildWebLinkToHist(ITeamcityIgnited teamcity, MultBuildRunCtx suite) {
-        String branchName = suite.branchName();
-
-        return buildWebLinkToHist(teamcity, suite, branchName);
-    }
-
     @Nonnull private static String buildWebLinkToHist(ITeamcityIgnited teamcity, MultBuildRunCtx suite, String branchName) {
-        final String branch = normalizeBranch(branchName);
-        return teamcity.host() + "buildConfiguration/" + suite.suiteId()
-            + "?branch=" + UrlUtil.escape(branch);
+        return buildWebLinkToHist(teamcity, suite.suiteId(), branchName);
     }
 
-    @Nonnull private static String buildWebLinkToHistOldUi(ITeamcityIgnited teamcity, MultBuildRunCtx suite, String branchName) {
+    public static String buildWebLinkToHist(ITeamcityIgnited teamcity, String suiteId, String branchName) {
         final String branch = normalizeBranch(branchName);
-        return teamcity.host() + "viewType.html?buildTypeId=" + suite.suiteId()
-            + "&branch=" + UrlUtil.escape(branch)
-            + "&tab=buildTypeStatusDiv";
+        return teamcity.host() + "buildConfiguration/" + suiteId  + "?branch=" + UrlUtil.escape(branch);
     }
 
     /** {@inheritDoc} */
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/DsTestFailureUi.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/DsTestFailureUi.java
index 6b9612c..daadcc7 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/DsTestFailureUi.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/DsTestFailureUi.java
@@ -168,9 +168,9 @@ public class DsTestFailureUi extends ShortTestFailureUi {
         }
     }
 
-    private static String buildWebLink(ITeamcityIgnited tcIgn, Long testNameId,
+    public static String buildWebLink(ITeamcityIgnited tcIgn, Long testNameId,
         @Nullable String projectId, @Nullable String branchName) {
-        if (projectId == null)
+        if (projectId == null || testNameId == null)
             return null;
 
         final String branch = normalizeBranch(branchName);