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/06 17:54:58 UTC

[ignite-teamcity-bot] branch master updated: Board: Summary of non-fixed failures as new screen (phase 1) - Fixes #140.

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 d5c757b  Board: Summary of non-fixed failures as new screen (phase 1) - Fixes #140.
d5c757b is described below

commit d5c757b86fddd58ad53ee593d60087e3e92ce1e4
Author: Dmitriy Pavlov <dp...@apache.org>
AuthorDate: Tue Aug 6 20:54:32 2019 +0300

    Board: Summary of non-fixed failures as new screen (phase 1) - Fixes #140.
    
    Signed-off-by: Dmitriy Pavlov <dp...@apache.org>
---
 .../java/org/apache/ignite/ci/db/DbMigrations.java |   2 +-
 .../java/org/apache/ignite/ci/db/TcHelperDb.java   |   5 -
 .../apache/ignite/ci/observer/package-info.java    |   2 +-
 .../apache/ignite/ci/runners/ClientTmpHelper.java  |   2 +-
 .../ignite/ci/runners/RemoteClientTmpHelper.java   |   5 +-
 .../apache/ignite/ci/tcbot/TcBotWebAppModule.java  |   5 +-
 .../ignite/ci/tcbot/issue/IssueDetector.java       |   3 +-
 .../ci/tcbot/user/UserAndSessionsStorage.java      |   6 +-
 .../ignite/ci/web/rest/board/BoardRestService.java |  55 ++++++
 .../apache/ignite/ci/web/rest/issues/TcIssues.java |   8 +-
 .../ignite/ci/web/rest/login/UserService.java      |   2 +-
 .../src/main/webapp/board/index.html               | 204 +++++++++++++++++++++
 ignite-tc-helper-web/src/main/webapp/trends.html   |   2 +-
 .../ci/tcbot/chain/MockBasedTcBotModule.java       |   2 +-
 .../ci/tcbot/chain/PrChainsProcessorTest.java      |   2 +-
 .../java/org/apache/ignite/ci/issue/ChangeUi.java  |   6 +
 .../java/org/apache/ignite/ci/issue/Issue.java     |   2 +
 .../java/org/apache/ignite/ci/issue/IssueKey.java  |   1 +
 .../ignite/tcbot/engine/TcBotEngineModule.java     |   7 +
 .../ignite/tcbot/engine/board/BoardService.java    | 201 ++++++++++++++++++++
 .../tcbot/engine/buildtime/BuildTimeService.java   |  30 +--
 .../tcbot/engine/chain/BuildChainProcessor.java    |  17 +-
 .../tcbot/engine/chain/SingleBuildRunCtx.java      |  32 ++--
 .../ignite/tcbot/engine/defect/BlameCandidate.java |  26 +--
 .../tcbot/engine/defect/CommitCompacted.java       |  77 ++++++++
 .../tcbot/engine/defect/DefectCompacted.java       | 164 +++++++++++++++++
 .../tcbot/engine/defect/DefectFirstBuild.java      |  44 ++---
 .../ignite/tcbot/engine/defect/DefectIssue.java    |  47 +++--
 .../DefectKey.java}                                |  15 +-
 .../ignite/tcbot/engine/defect/DefectsStorage.java | 155 ++++++++++++++++
 .../ignite/tcbot/engine}/issue/IIssuesStorage.java |   2 +-
 .../ignite/tcbot/engine}/issue/IssueType.java      |   2 +-
 .../ignite/tcbot/engine}/issue/IssuesStorage.java  |   9 +-
 .../tcbot/engine/ui/BoardDefectSummaryUi.java      |  94 ++++++++++
 ...{BuildTimeRecordUi.java => BoardSummaryUi.java} |  19 +-
 .../ignite/tcbot/engine/ui/BuildTimeRecordUi.java  |  11 ++
 .../apache/ignite/tcbot/engine/ui/IssueListUi.java |   8 +-
 .../ignite/tcbot/persistence/CacheConfigs.java     |   7 +
 .../teamcity/ignited/change/ChangeCompacted.java   |  17 +-
 .../ci/teamcity/ignited/change/ChangeDao.java      |  15 +-
 .../ignited/fatbuild/FatBuildCompacted.java        |   6 +-
 .../ignite/tcignited/TeamcityIgnitedImpl.java      |  13 +-
 .../apache/ignite/tcignited/build/FatBuildDao.java |   2 +-
 .../org/apache/ignite/tcignited/build/ITest.java   |   8 +-
 .../ignite/tcignited/build/TestCompactedV2.java    |   4 +
 .../ignite/tcignited/buildlog/LogMsgToWarn.java    |   2 +-
 .../ignite/tcignited/buildref/BuildRefDao.java     |   4 +
 .../tcignited/buildtime/BuildTimeRecord.java       |   4 +
 .../tcignited/buildtime/BuildTimeResult.java       |   8 +-
 49 files changed, 1199 insertions(+), 165 deletions(-)

diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/db/DbMigrations.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/db/DbMigrations.java
index 1bbb8fe..6781920 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/db/DbMigrations.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/db/DbMigrations.java
@@ -25,7 +25,7 @@ import org.apache.ignite.cache.CacheAtomicityMode;
 import org.apache.ignite.cache.CacheMode;
 import org.apache.ignite.ci.issue.Issue;
 import org.apache.ignite.ci.issue.IssueKey;
-import org.apache.ignite.ci.issue.IssuesStorage;
+import org.apache.ignite.tcbot.engine.issue.IssuesStorage;
 import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.tcservice.model.result.Build;
 import org.jetbrains.annotations.NotNull;
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/db/TcHelperDb.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/db/TcHelperDb.java
index e706b33..6850194 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/db/TcHelperDb.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/db/TcHelperDb.java
@@ -106,11 +106,6 @@ public class TcHelperDb {
         return TcHelperDb.<K, V>getCacheV3Config(name).setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL);
     }
 
-    public static <K, V> CacheConfiguration<K, V> getCacheV2TxConfig(String name) {
-        return CacheConfigs.<K, V>getCacheV2Config(name).setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL);
-
-    }
-
     public static class LocalOnlyTcpDiscoveryIpFinder implements TcpDiscoveryIpFinder {
         /** Port. */
         private int port;
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/package-info.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/package-info.java
index f7e1a1c..d0b2ec3 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/package-info.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/package-info.java
@@ -35,7 +35,7 @@
  * list for specific {@link org.apache.ignite.ci.web.model.ContributionKey}.
  * It's needed for proper changing of status and result of
  * {@link org.apache.ignite.ci.web.model.VisaRequest} by {@link org.apache.ignite.ci.observer.ObserverTask}.
- * If happens an attempt to add observation
+ * If happens an attempt to addBuild observation
  * for {@link org.apache.ignite.ci.web.model.ContributionKey} while current
  * observation is not finished, then current observation will be marked as
  * cancelled and overwritten by the new one.
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/runners/ClientTmpHelper.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/runners/ClientTmpHelper.java
index 75774d2..0541e80 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/runners/ClientTmpHelper.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/runners/ClientTmpHelper.java
@@ -22,7 +22,7 @@ import org.apache.ignite.IgniteCache;
 import org.apache.ignite.ci.db.TcHelperDb;
 import org.apache.ignite.githubignited.IGitHubConnIgnited;
 import org.apache.ignite.ci.issue.Issue;
-import org.apache.ignite.ci.issue.IssuesStorage;
+import org.apache.ignite.tcbot.engine.issue.IssuesStorage;
 import org.apache.ignite.jiraignited.JiraTicketDao;
 
 /**
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/runners/RemoteClientTmpHelper.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/runners/RemoteClientTmpHelper.java
index ec9c219..ad01803 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/runners/RemoteClientTmpHelper.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/runners/RemoteClientTmpHelper.java
@@ -35,8 +35,8 @@ import org.apache.ignite.IgniteCache;
 import org.apache.ignite.Ignition;
 import org.apache.ignite.ci.issue.Issue;
 import org.apache.ignite.ci.issue.IssueKey;
-import org.apache.ignite.ci.issue.IssueType;
-import org.apache.ignite.ci.issue.IssuesStorage;
+import org.apache.ignite.tcbot.engine.issue.IssueType;
+import org.apache.ignite.tcbot.engine.issue.IssuesStorage;
 import org.apache.ignite.ci.tcbot.user.UserAndSessionsStorage;
 import org.apache.ignite.ci.teamcity.ignited.BuildRefCompacted;
 import org.apache.ignite.ci.teamcity.ignited.fatbuild.FatBuildCompacted;
@@ -49,7 +49,6 @@ import org.apache.ignite.tcbot.persistence.IgniteStringCompactor;
 import org.apache.ignite.tcignited.ITeamcityIgnited;
 import org.apache.ignite.tcignited.build.FatBuildDao;
 import org.apache.ignite.tcignited.buildref.BuildRefDao;
-import org.apache.ignite.tcignited.history.BuildStartTimeStorage;
 import org.apache.ignite.tcservice.model.hist.BuildRef;
 import org.apache.ignite.tcservice.model.result.Build;
 import org.apache.ignite.tcservice.util.XmlUtil;
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/TcBotWebAppModule.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/TcBotWebAppModule.java
index e613ccf..bb1bbd5 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/TcBotWebAppModule.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/TcBotWebAppModule.java
@@ -26,15 +26,15 @@ import java.util.concurrent.TimeoutException;
 import javax.inject.Provider;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.ci.db.Ignite1Init;
-import org.apache.ignite.ci.issue.IssuesStorage;
 import org.apache.ignite.ci.tcbot.conf.LocalFilesBasedConfig;
-import org.apache.ignite.ci.tcbot.issue.IIssuesStorage;
 import org.apache.ignite.ci.tcbot.trends.MasterTrendsService;
 import org.apache.ignite.ci.tcbot.user.IUserStorage;
 import org.apache.ignite.ci.tcbot.user.UserAndSessionsStorage;
 import org.apache.ignite.tcbot.common.conf.IDataSourcesConfigSupplier;
 import org.apache.ignite.tcbot.engine.TcBotEngineModule;
 import org.apache.ignite.tcbot.engine.conf.ITcBotConfig;
+import org.apache.ignite.tcbot.engine.issue.IIssuesStorage;
+import org.apache.ignite.tcbot.engine.issue.IssuesStorage;
 import org.apache.ignite.tcbot.notify.TcBotNotificationsModule;
 import org.apache.ignite.tcbot.persistence.scheduler.SchedulerModule;
 import org.apache.ignite.githubignited.GitHubIgnitedModule;
@@ -93,7 +93,6 @@ public class TcBotWebAppModule extends AbstractModule {
         //todo remove duplication of instances for base and for overriden class
         bind(IDataSourcesConfigSupplier.class).to(LocalFilesBasedConfig.class).in(new SingletonScope());
         bind(IUserStorage.class).to(UserAndSessionsStorage.class).in(new SingletonScope());
-        bind(IIssuesStorage.class).to(IssuesStorage.class).in(new SingletonScope());
         bind(MasterTrendsService.class).in(new SingletonScope());
         bind(ITcBotBgAuth.class).to(TcBotBgAuthImpl.class).in(new SingletonScope());
     }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/issue/IssueDetector.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/issue/IssueDetector.java
index f6027c5..807b63d 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/issue/IssueDetector.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/issue/IssueDetector.java
@@ -35,7 +35,8 @@ import javax.inject.Inject;
 import javax.inject.Provider;
 import org.apache.ignite.ci.issue.Issue;
 import org.apache.ignite.ci.issue.IssueKey;
-import org.apache.ignite.ci.issue.IssueType;
+import org.apache.ignite.tcbot.engine.issue.IIssuesStorage;
+import org.apache.ignite.tcbot.engine.issue.IssueType;
 import org.apache.ignite.ci.jobs.CheckQueueJob;
 import org.apache.ignite.tcbot.engine.tracked.DisplayMode;
 import org.apache.ignite.tcbot.notify.ISlackSender;
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/user/UserAndSessionsStorage.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/user/UserAndSessionsStorage.java
index 2380f8c..43a7b7a 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/user/UserAndSessionsStorage.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/user/UserAndSessionsStorage.java
@@ -24,9 +24,9 @@ import javax.inject.Inject;
 import javax.inject.Provider;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteCache;
-import org.apache.ignite.ci.db.TcHelperDb;
 import org.apache.ignite.ci.user.TcHelperUser;
 import org.apache.ignite.ci.user.UserSession;
+import org.apache.ignite.tcbot.persistence.CacheConfigs;
 import org.jetbrains.annotations.Nullable;
 
 public class UserAndSessionsStorage implements IUserStorage {
@@ -38,7 +38,7 @@ public class UserAndSessionsStorage implements IUserStorage {
     private volatile Ignite ignite;
 
     public IgniteCache<String, TcHelperUser> users() {
-        return getIgnite().getOrCreateCache(TcHelperDb.getCacheV2TxConfig(USERS));
+        return getIgnite().getOrCreateCache(CacheConfigs.<String, TcHelperUser>getCacheV2TxConfig(USERS));
     }
 
     public Ignite getIgnite() {
@@ -56,7 +56,7 @@ public class UserAndSessionsStorage implements IUserStorage {
     }
 
     private IgniteCache<String, UserSession> sessions() {
-        return getIgnite().getOrCreateCache(TcHelperDb.getCacheV2TxConfig(USER_SESSIONS));
+        return getIgnite().getOrCreateCache(CacheConfigs.<String, UserSession>getCacheV2TxConfig(USER_SESSIONS));
     }
 
     /** {@inheritDoc} */
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/board/BoardRestService.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/board/BoardRestService.java
new file mode 100644
index 0000000..d8550a4
--- /dev/null
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/board/BoardRestService.java
@@ -0,0 +1,55 @@
+/*
+ * 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.web.rest.board;
+
+import com.google.inject.Injector;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import org.apache.ignite.ci.user.ITcBotUserCreds;
+import org.apache.ignite.ci.web.CtxListener;
+import org.apache.ignite.tcbot.engine.board.BoardService;
+import org.apache.ignite.tcbot.engine.ui.BoardSummaryUi;
+
+@Path(BoardRestService.BOARD)
+@Produces(MediaType.APPLICATION_JSON)
+public class BoardRestService {
+    public static final String BOARD = "board";
+
+    /** Servlet Context. */
+    @Context
+    private ServletContext ctx;
+
+    /** Current Request. */
+    @Context
+    private HttpServletRequest req;
+
+    @GET
+    @Path("summary")
+    public BoardSummaryUi getSummary() {
+
+        final ITcBotUserCreds creds = ITcBotUserCreds.get(req);
+        final Injector injector = CtxListener.getInjector(ctx);
+        final BoardService boardSvc = injector.getInstance(BoardService.class);
+
+        return boardSvc.summary(creds);
+    }
+}
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/issues/TcIssues.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/issues/TcIssues.java
index f62d867..4ed7b79 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/issues/TcIssues.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/issues/TcIssues.java
@@ -27,8 +27,8 @@ 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.issue.IssueList;
-import org.apache.ignite.ci.tcbot.issue.IIssuesStorage;
+import org.apache.ignite.tcbot.engine.ui.IssueListUi;
+import org.apache.ignite.tcbot.engine.issue.IIssuesStorage;
 import org.apache.ignite.ci.web.CtxListener;
 import org.apache.ignite.ci.web.model.SimpleResult;
 import org.apache.ignite.tcbot.engine.ui.UpdateInfo;
@@ -58,7 +58,7 @@ public class TcIssues {
 
     @GET
     @Path("list")
-    public IssueList listIssues(@Nullable @QueryParam("branch") String branchOpt,
+    public IssueListUi listIssues(@Nullable @QueryParam("branch") String branchOpt,
                                 @Nullable @QueryParam("count") Integer count,
                                 @Nullable @QueryParam("checkAllLogs") Boolean checkAllLogs) {
         Injector injector = CtxListener.getInjector(ctx);
@@ -67,7 +67,7 @@ public class TcIssues {
 
         IIssuesStorage issues = injector.getInstance(IIssuesStorage.class);
 
-        IssueList issueList = new IssueList(issues.allIssues().collect(Collectors.toList()));
+        IssueListUi issueList = new IssueListUi(issues.allIssues().collect(Collectors.toList()));
 
         issueList.branch = branch;
 
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/login/UserService.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/login/UserService.java
index dfaf340..fb6229a 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/login/UserService.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/login/UserService.java
@@ -140,7 +140,7 @@ public class UserService {
             tcHelperUserUi.data.add(credsUi);
         }
 
-        //todo if user is not current disable add creds
+        //todo if user is not current disable addBuild creds
         return tcHelperUserUi;
     }
 
diff --git a/ignite-tc-helper-web/src/main/webapp/board/index.html b/ignite-tc-helper-web/src/main/webapp/board/index.html
new file mode 100644
index 0000000..02e4f3d
--- /dev/null
+++ b/ignite-tc-helper-web/src/main/webapp/board/index.html
@@ -0,0 +1,204 @@
+<html>
+<head>
+    <title>Apache Ignite Teamcity Bot - Tracked branch - Detailed status of failures</title>
+    <link rel="icon" href="/img/leaf-icon-png-7066.png">
+
+    <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="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
+    <!-- production version, optimized for size and speed -->
+    <!--<script src="https://cdn.jsdelivr.net/npm/vue"></script>-->
+
+    <script src="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.js"></script>
+    <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
+
+    <link href="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.min.css" rel="stylesheet">
+    <link href="https://cdn.jsdelivr.net/npm/@mdi/font@3.x/css/materialdesignicons.min.css" rel="stylesheet">
+
+    <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
+    <link rel="stylesheet" href="/css/style-1.5.css">
+
+    <script src="/js/common-1.6.js"></script>
+</head>
+<body>
+<script>
+    var g_shownDataHashCodeHex = "";
+    let gVue, g_Loading, g_TcBotVersion;
+
+    $(document).ready(function() {
+        $.getScript("/js/testfails-2.2.js", function(data, textStatus, jqxhr){ });
+
+        $( document ).tooltip();
+        showQueryForm();
+
+        loadData();
+
+        setInterval(loadDataSilent, 3000);
+
+        $.ajax({ url: "/rest/branches/version",  success: showVersionInfo, error: showErrInLoadStatus });
+    });
+
+    function showQueryForm() {
+        gVue = new Vue({
+            el: '#vueQueryForm',
+            vuetify: new Vuetify(),
+            data: {
+                baseBranchSelected: '',
+                defects: [],
+                expanded: [],
+                headers: [
+                    {text: "Branch", value: 'branch'},
+                    {text: 'Tags', value: 'tags'},
+                    {text: 'Suites', value: 'suites'},
+                    {text: "Issues", value: 'cntissues'},
+                    {text: "Fixed", value: 'fixedissues'},
+                    {text: "Not Fixed", value: 'notfixedissues'}
+                ]
+            },
+            methods: {
+                formChanged: function () {
+                }
+            }
+        });
+    }
+
+
+    function parmsForRest() {
+        var curReqParms = "";
+        var branch = findGetParameter("branch");
+        if (branch != null) {
+            curReqParms += "?branch=" + branch;
+        }
+
+        if (gVue.$data.baseBranchSelected != null) {
+            curReqParms += "?baseBranch=" +  gVue.$data.baseBranchSelected;
+        }
+
+        return curReqParms;
+    }
+
+    function loadDataFromServer(silent) {
+        g_Loading = true;
+        if (!silent) $("#loadStatus").html("<img src='https://www.wallies.com/filebin/images/loading_apple.gif' width=20px height=20px> Please wait");
+
+        var curFailuresUrl = "/rest/board/summary" + parmsForRest();
+        $.ajax({
+            url: curFailuresUrl,
+            success: function (result) {
+                if (!silent) $("#loadStatus").html("");
+
+                showData(result);
+                g_shownDataHashCodeHex = isDefinedAndFilled(result.hashCodeHex) ? result.hashCodeHex : "";
+
+                g_Loading = false;
+            },
+            error: function (jqXHR, exception) {
+                g_Loading = false;
+                showErrInLoadStatus(jqXHR, exception);
+            }
+        });
+    }
+
+    function loadData() {
+        loadDataFromServer(false);
+    }
+
+    function validateVersionsConsistency() {
+        $.ajax({
+            url: "/rest/branches/version", success: function (result) {
+                if (g_TcBotVersion == null)
+                    g_TcBotVersion = result.version;
+                else if (g_TcBotVersion !== result.version)
+                    window.location.reload(true);
+
+            }, error: showErrInLoadStatus
+        });
+    }
+
+    function loadDataSilent() {
+        if(g_Loading)
+            return;
+
+        try {
+            loadDataFromServer(true);
+            validateVersionsConsistency();
+        } catch (e) {
+            console.log(e);
+        }
+    }
+
+    function showData(result) {
+        gVue.$data.defects = result.defects;
+    }
+
+</script>
+
+<div id="loadStatus"></div>
+<div id="vueQueryForm" class="h-25">
+    <v-app id="queryForm" class="h-25">
+        <!-- <select v-model="baseBranchSelected" @change="formChanged">
+            <option disabled value="">Please select one</option>
+            <option>A</option>
+            <option>B</option>
+            <option>C</option>
+        </select>
+        <span>Base branch: {{ baseBranchSelected }}</span> -->
+
+
+        <v-data-table
+                :headers="headers"
+                :items="defects"
+                itrackedBranchtem-key="id"
+                class="elevation-1"
+                group-by="branch"
+                :expanded.sync="expanded"
+                show-expand
+                dense
+        >
+            <!-- expand item/row -->
+            <template v-slot:expanded-item="{ headers, item }">
+                <td :colspan="headers.length">
+                    Branch: <a :href="'/current.html?branch=' + item.trackedBranch">{{item.trackedBranch}}</a>
+                    Commits from:
+                    <div v-for="(candidate) in item.blameCandidates">
+                        {{ candidate }}
+                    </div>
+
+                    <div v-for="(test) in item.testOrSuitesAffected">
+                        {{ test }}
+                    </div>
+                </td>
+            </template>
+
+            <template v-slot:item.tags="{ item }">
+                <span v-for="(tag) in item.tags">
+                    {{ tag }}
+                </span>
+            </template>
+
+            <template v-slot:item.suites="{ item }">
+                <span v-for="(suite) in item.suites">
+                    {{ suite }}
+                </span>
+            </template>
+
+            <template v-slot:item.cntIssues="{ item }">
+                {{ item.cntIssues }}
+            </template>
+            <template v-slot:item.fixedIssues="{ item }">
+                <span class='visaStage' style="background: #12AD5E">  {{ item.fixedIssues }} </span>
+            </template>
+            <template v-slot:item.notFixedIssues="{ item }">
+                <span class='visaStage' style="background: red">  {{ item.notFixedIssues }} </span>
+            </template>
+
+        </v-data-table>
+
+    </v-app>
+</div>
+
+<div id="version"></div>
+<div style="visibility:hidden;"><div id="triggerConfirm" title="Trigger Confirmation"></div><div id="triggerDialog" title="Trigger Result"></div></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/ignite-tc-helper-web/src/main/webapp/trends.html b/ignite-tc-helper-web/src/main/webapp/trends.html
index e89a61e..9b8e660 100644
--- a/ignite-tc-helper-web/src/main/webapp/trends.html
+++ b/ignite-tc-helper-web/src/main/webapp/trends.html
@@ -581,7 +581,7 @@
 
     /**
      *
-     * @param result list of {@link org.apache.ignite.ci.tcbot.conf.ChainAtServer} objects as JSON.
+     * @param result list of {@link org.apache.ignite.ci.tcbot.conf.ChainAtServer} testOrSuitesAffected as JSON.
      */
     function printSuites(result) {
         let selectHtml = "<select id='selectSuite'>";
diff --git a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/MockBasedTcBotModule.java b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/MockBasedTcBotModule.java
index 4db0af0..6f54189 100644
--- a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/MockBasedTcBotModule.java
+++ b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/MockBasedTcBotModule.java
@@ -39,7 +39,7 @@ import org.apache.ignite.tcbot.common.conf.ITcServerConfig;
 import org.apache.ignite.tcbot.engine.conf.ITrackedBranchesConfig;
 import org.apache.ignite.tcbot.engine.conf.NotificationsConfig;
 import org.apache.ignite.tcbot.engine.conf.TcServerConfig;
-import org.apache.ignite.ci.tcbot.issue.IIssuesStorage;
+import org.apache.ignite.tcbot.engine.issue.IIssuesStorage;
 import org.apache.ignite.ci.tcbot.user.IUserStorage;
 import org.apache.ignite.tcbot.notify.IEmailSender;
 import org.apache.ignite.tcbot.notify.ISlackSender;
diff --git a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/PrChainsProcessorTest.java b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/PrChainsProcessorTest.java
index f467daa..d166c50 100644
--- a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/PrChainsProcessorTest.java
+++ b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/PrChainsProcessorTest.java
@@ -255,7 +255,7 @@ public class PrChainsProcessorTest {
                         createTest(1L, TEST_RARE_FAILED_WITHOUT_CHANGES, !failNoChanges),
                         createTest(2L, TEST_RARE_FAILED_WITH_CHANGES, !failWithChanges)), null);
 
-            if (failWithChanges || i == 56) // add change to test status change after failure.
+            if (failWithChanges || i == 56) // addBuild change to test status change after failure.
                 fatBuild.changes(new int[] {1000000 + i, 1000020 + i});
 
             addBuildsToEmulatedStor(fatBuild);
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/ChangeUi.java b/tcbot-engine/src/main/java/org/apache/ignite/ci/issue/ChangeUi.java
similarity index 93%
rename from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/ChangeUi.java
rename to tcbot-engine/src/main/java/org/apache/ignite/ci/issue/ChangeUi.java
index 5b433b5..41b644e 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/ChangeUi.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/ci/issue/ChangeUi.java
@@ -18,7 +18,9 @@
 package org.apache.ignite.ci.issue;
 
 import com.google.common.base.MoreObjects;
+import org.apache.ignite.tcbot.persistence.Persisted;
 
+@Persisted
 public class ChangeUi {
     public final String username;
     public final String webUrl;
@@ -51,4 +53,8 @@ public class ChangeUi {
             .add("webUrl", webUrl)
             .toString();
     }
+
+    public String username() {
+        return username;
+    }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/Issue.java b/tcbot-engine/src/main/java/org/apache/ignite/ci/issue/Issue.java
similarity index 98%
rename from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/Issue.java
rename to tcbot-engine/src/main/java/org/apache/ignite/ci/issue/Issue.java
index 2e99c52..6923122 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/Issue.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/ci/issue/Issue.java
@@ -29,11 +29,13 @@ import java.util.Map;
 import java.util.TreeSet;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import org.apache.ignite.tcbot.engine.issue.IssueType;
 import org.apache.ignite.tcbot.persistence.Persisted;
 import org.apache.ignite.tcbot.common.util.TimeUtil;
 
 /**
  * Issue used both for saving into DB and in UI (in issue history).
+ * Issue is any detected failure of test or suite.
  */
 @SuppressWarnings({"WeakerAccess", "PublicField"})
 @Persisted
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueKey.java b/tcbot-engine/src/main/java/org/apache/ignite/ci/issue/IssueKey.java
similarity index 98%
copy from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueKey.java
copy to tcbot-engine/src/main/java/org/apache/ignite/ci/issue/IssueKey.java
index 348a623..28c0102 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueKey.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/ci/issue/IssueKey.java
@@ -44,6 +44,7 @@ public class IssueKey {
         return testOrBuildName;
     }
 
+    /** {@inheritDoc} */
     @Override public String toString() {
         return MoreObjects.toStringHelper(this)
             .add("server", server)
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/TcBotEngineModule.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/TcBotEngineModule.java
index 44a9465..948c002 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/TcBotEngineModule.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/TcBotEngineModule.java
@@ -21,8 +21,11 @@ import com.google.inject.AbstractModule;
 import com.google.inject.internal.SingletonScope;
 import org.apache.ignite.tcbot.common.TcBotCommonModule;
 import org.apache.ignite.tcbot.common.interceptor.MonitoredTaskInterceptorModule;
+import org.apache.ignite.tcbot.engine.board.BoardService;
 import org.apache.ignite.tcbot.engine.buildtime.BuildTimeService;
 import org.apache.ignite.tcbot.engine.chain.BuildChainProcessor;
+import org.apache.ignite.tcbot.engine.issue.IIssuesStorage;
+import org.apache.ignite.tcbot.engine.issue.IssuesStorage;
 import org.apache.ignite.tcbot.engine.tracked.IDetailedStatusForTrackedBranch;
 import org.apache.ignite.tcbot.engine.tracked.TrackedBranchChainsProcessor;
 
@@ -37,6 +40,10 @@ public class TcBotEngineModule extends AbstractModule {
 
         bind(BuildTimeService.class).in(new SingletonScope());
 
+        bind(IIssuesStorage.class).to(IssuesStorage.class).in(new SingletonScope());
+
+        bind(BoardService.class).in(new SingletonScope());
+
         install(new TcBotCommonModule());
     }
 }
\ No newline at end of file
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
new file mode 100644
index 0000000..d7ff93e
--- /dev/null
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/BoardService.java
@@ -0,0 +1,201 @@
+/*
+ * 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.tcbot.engine.board;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.inject.Inject;
+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.tcbot.common.conf.ITcServerConfig;
+import org.apache.ignite.tcbot.common.interceptor.MonitoredTask;
+import org.apache.ignite.tcbot.common.util.FutureUtil;
+import org.apache.ignite.tcbot.engine.chain.BuildChainProcessor;
+import org.apache.ignite.tcbot.engine.chain.SingleBuildRunCtx;
+import org.apache.ignite.tcbot.engine.defect.BlameCandidate;
+import org.apache.ignite.tcbot.engine.defect.DefectCompacted;
+import org.apache.ignite.tcbot.engine.defect.DefectFirstBuild;
+import org.apache.ignite.tcbot.engine.defect.DefectIssue;
+import org.apache.ignite.tcbot.engine.defect.DefectsStorage;
+import org.apache.ignite.tcbot.engine.issue.IIssuesStorage;
+import org.apache.ignite.tcbot.engine.issue.IssueType;
+import org.apache.ignite.tcbot.engine.ui.BoardDefectSummaryUi;
+import org.apache.ignite.tcbot.engine.ui.BoardSummaryUi;
+import org.apache.ignite.tcbot.persistence.IStringCompactor;
+import org.apache.ignite.tcbot.persistence.scheduler.IScheduler;
+import org.apache.ignite.tcignited.ITeamcityIgnited;
+import org.apache.ignite.tcignited.ITeamcityIgnitedProvider;
+import org.apache.ignite.tcignited.build.FatBuildDao;
+import org.apache.ignite.tcignited.build.ITest;
+import org.apache.ignite.tcignited.creds.ICredentialsProv;
+
+public class BoardService {
+    @Inject IIssuesStorage issuesStorage;
+    @Inject FatBuildDao fatBuildDao;
+    @Inject ChangeDao changeDao;
+    @Inject ITeamcityIgnitedProvider tcProv;
+    @Inject DefectsStorage defectStorage;
+    @Inject IScheduler scheduler;
+    @Inject IStringCompactor compactor;
+
+    @Inject BuildChainProcessor buildChainProcessor;
+
+    /**
+     * @param creds Credentials.
+     */
+    public BoardSummaryUi summary(ICredentialsProv creds) {
+        issuesToDefectsLater();
+
+        Map<Integer, Future<FatBuildCompacted>> allBuildsMap = new HashMap<>();
+
+        List<DefectCompacted> defects = defectStorage.loadAllDefects();
+
+        BoardSummaryUi res = new BoardSummaryUi();
+        for (DefectCompacted next : defects) {
+            BoardDefectSummaryUi defectUi = new BoardDefectSummaryUi(next, compactor);
+
+            String srvCode = next.tcSrvCode(compactor);
+
+            if(!creds.hasAccess(srvCode))
+                continue;
+
+            ITeamcityIgnited tcIgn = tcProv.server(srvCode, creds);
+
+            ITcServerConfig cfg = tcIgn.config();
+
+            List<BlameCandidate> candidates = next.blameCandidates();
+
+
+            Map<Integer, DefectFirstBuild> build = next.buildsInvolved();
+            for (DefectFirstBuild cause : build.values()) {
+                FatBuildCompacted firstBuild = cause.build();
+                defectUi.addTags(SingleBuildRunCtx.getBuildTagsFromParameters(cfg, compactor, firstBuild));
+                FatBuildCompacted fatBuild = fatBuildDao.getFatBuild(next.tcSrvId(), firstBuild.id());
+
+                List<Future<FatBuildCompacted>> futures = buildChainProcessor.replaceWithRecent(fatBuild, allBuildsMap, tcIgn);
+
+                Stream<FatBuildCompacted> results = FutureUtil.getResults(futures);
+                List<FatBuildCompacted> freshRebuild = results.collect(Collectors.toList());
+                if(!freshRebuild.isEmpty()) {
+                    FatBuildCompacted buildCompacted = freshRebuild.get(0);
+
+                    Set<DefectIssue> issues = cause.issues();
+                    for (DefectIssue issue : issues) {
+                        Optional<ITest> any = buildCompacted.getAllTests()
+                            .filter(t -> t.testName() == issue.testNameCid())
+                            .findAny();
+
+                        if(any.isPresent()) {
+                            boolean failed = any.get().isFailedTest(compactor);
+                            if(!failed)
+                                defectUi.addFixedIssue();
+                            else
+                                defectUi.addNotFixedIssue();
+                        }
+
+                        String testOrBuildName = compactor.getStringFromId(issue.testNameCid());
+                        defectUi.addIssue(testOrBuildName, "");
+                    }
+                }
+            }
+
+            defectUi.branch =  next.tcBranch(compactor);
+
+            res.addDefect(defectUi);
+        }
+
+        return res;
+    }
+
+    public void issuesToDefectsLater() {
+        scheduler.sheduleNamed("issuesToDefects", this::issuesToDefects, 15, TimeUnit.MINUTES);
+    }
+
+    @MonitoredTask(name = "Convert issues to defect")
+    protected void issuesToDefects() {
+        Stream<Issue> stream = issuesStorage.allIssues();
+
+        long minIssueTs = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(3);
+
+        //todo not so good to to call init() twice
+        fatBuildDao.init();
+        changeDao.init();
+
+        stream
+            .filter(issue -> {
+                long detected = issue.detectedTs == null ? 0 : issue.detectedTs;
+
+                return detected >= minIssueTs;
+            })
+            .filter(issue -> {
+                String type = issue.type;
+                return !IssueType.newContributedTestFailure.code().equals(type);
+            })
+            .forEach(issue -> {
+                IssueKey key = issue.issueKey;
+                String srvCode = key.getServer();
+                //just for init call
+
+                int srvId = ITeamcityIgnited.serverIdToInt(srvCode);
+                FatBuildCompacted fatBuild = fatBuildDao.getFatBuild(srvId, key.buildId);
+                if (fatBuild == null)
+                    return;
+
+                //todo non test failures
+                String testName = issue.issueKey().getTestOrBuildName();
+
+                int issueTypeCid = compactor.getStringId(issue.type);
+                Integer testNameCid = compactor.getStringIdIfPresent(testName);
+                int trackedBranchCid = compactor.getStringId(issue.trackedBranchName);
+
+                int tcSrvCodeCid = compactor.getStringId(srvCode);
+                defectStorage.merge(tcSrvCodeCid, srvId, fatBuild,
+                    (k, defect) -> {
+                        defect.trackedBranchCidSetIfEmpty(trackedBranchCid);
+
+                        defect.computeIfAbsent(fatBuild).addIssue(issueTypeCid, testNameCid);
+
+                        if(defect.blameCandidates().isEmpty()) {
+                            Map<Integer, ChangeCompacted> map = defect.changeMap();
+
+                            Collection<ChangeCompacted> values = map.values();
+                            for (ChangeCompacted next : values) {
+                                BlameCandidate candidate = new BlameCandidate();
+                                candidate.vcsUsername(next.vcsUsername());
+                                defect.addBlameCandidate(candidate);
+                            }
+                        }
+
+                        return defect;
+                    });
+
+            });
+
+    }
+}
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/buildtime/BuildTimeService.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/buildtime/BuildTimeService.java
index 3f71c4c..86c1dfd 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/buildtime/BuildTimeService.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/buildtime/BuildTimeService.java
@@ -25,7 +25,6 @@ import org.apache.ignite.tcbot.engine.ui.BuildTimeResultUi;
 import org.apache.ignite.tcbot.persistence.IStringCompactor;
 import org.apache.ignite.tcbot.persistence.scheduler.IScheduler;
 import org.apache.ignite.tcignited.ITeamcityIgnited;
-import org.apache.ignite.tcignited.ITeamcityIgnitedProvider;
 import org.apache.ignite.tcignited.build.FatBuildDao;
 import org.apache.ignite.tcignited.buildref.BuildRefDao;
 import org.apache.ignite.tcignited.buildtime.BuildTimeRecord;
@@ -42,9 +41,10 @@ import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
+/**
+ * Prepares overview related to build times of suites, tests, and branches
+ */
 public class BuildTimeService {
-    @Inject private ITeamcityIgnitedProvider tcProv;
-
     /** Config. */
     @Inject private ITcBotConfig cfg;
 
@@ -64,17 +64,17 @@ public class BuildTimeService {
         if (buildRefDao.buildRefsCache() == null)
             return new BuildTimeResultUi();
 
-        Collection<String> allServers = cfg.getServerIds();
+        Collection<String> allSrvs = cfg.getServerIds();
 
         scheduler.sheduleNamed("BuildTimeService.loadAnalytics",
                 this::loadAnalytics, 15, TimeUnit.MINUTES);
 
-        Set<Integer> availableServers = allServers.stream()
+        Set<Integer> availableSrvs = allSrvs.stream()
                 .filter(prov::hasAccess)
                 .map(ITeamcityIgnited::serverIdToInt)
                 .collect(Collectors.toSet());
 
-        BuildTimeResultUi resultUi = new BuildTimeResultUi();
+        BuildTimeResultUi resUi = new BuildTimeResultUi();
 
         long minDuration = Duration.ofMinutes(90).toMillis();
         long minDurationTimeout = Duration.ofMinutes(60).toMillis();
@@ -82,13 +82,13 @@ public class BuildTimeService {
         int cntToInclude = 50;
         BuildTimeResult res = lastRes1d;
 
-        res.topByBuildTypes(availableServers, minDuration, cntToInclude, totalDurationMs)
-                .stream().map(this::convertToUi).forEach(e -> resultUi.byBuildType.add(e));
+        res.topByBuildTypes(availableSrvs, minDuration, cntToInclude, totalDurationMs)
+                .stream().map(this::convertToUi).forEach(e -> resUi.byBuildType.add(e));
 
-        res.topTimeoutsByBuildTypes(availableServers, minDurationTimeout, cntToInclude, totalDurationMs)
-                .stream().map(this::convertToUi).forEach(e -> resultUi.timedOutByBuildType.add(e));
+        res.topTimeoutsByBuildTypes(availableSrvs, minDurationTimeout, cntToInclude, totalDurationMs)
+                .stream().map(this::convertToUi).forEach(e -> resUi.timedOutByBuildType.add(e));
 
-        return resultUi;
+        return resUi;
     }
 
     public BuildTimeRecordUi convertToUi(Map.Entry<Long, BuildTimeRecord> e) {
@@ -97,8 +97,12 @@ public class BuildTimeService {
         int btId = BuildTimeResult.cacheKeyToBuildType(key);
         buildTimeRecordUi.buildType = compactor.getStringFromId(btId);
 
-        buildTimeRecordUi.averageDuration = TimeUtil.millisToDurationPrintable(e.getValue().avgDuration());
-        buildTimeRecordUi.totalDuration =  TimeUtil.millisToDurationPrintable(e.getValue().totalDuration());
+        BuildTimeRecord val = e.getValue();
+        buildTimeRecordUi.averageDuration = TimeUtil.millisToDurationPrintable(val.avgDuration());
+        buildTimeRecordUi.totalDuration =  TimeUtil.millisToDurationPrintable(val.totalDuration());
+
+        buildTimeRecordUi.setCnt(val.count());
+
         return buildTimeRecordUi;
     }
 
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/chain/BuildChainProcessor.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/chain/BuildChainProcessor.java
index 09eb044..dca9293 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/chain/BuildChainProcessor.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/chain/BuildChainProcessor.java
@@ -21,6 +21,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.Futures;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
@@ -303,13 +304,23 @@ public class BuildChainProcessor {
 
         ctx.setChanges(tcIgnited.getAllChanges(buildCompacted.changes()));
 
-        ParametersCompacted parameters = buildCompacted.parameters();
-        if (parameters != null)
-            ctx.addTagsFromParameters(parameters, tcIgnited.config(), this.compactor);
+        ctx.addTags(SingleBuildRunCtx.getBuildTagsFromParameters(tcIgnited.config(), compactor, buildCompacted));
 
         return ctx;
     }
 
+
+    public List<Future<FatBuildCompacted>> replaceWithRecent(FatBuildCompacted build,
+        Map<Integer, Future<FatBuildCompacted>> allBuildsMap,
+        ITeamcityIgnited tcIgn) {
+
+        return replaceWithRecent(Collections.singletonList(build), 1,
+            LatestRebuildMode.LATEST,
+            allBuildsMap,
+            SyncMode.RELOAD_QUEUED,
+            tcIgn, null);
+    }
+
     @SuppressWarnings("WeakerAccess")
     @Nonnull
     @AutoProfiling
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/chain/SingleBuildRunCtx.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/chain/SingleBuildRunCtx.java
index fdd9f1f..77ddf63 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/chain/SingleBuildRunCtx.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/chain/SingleBuildRunCtx.java
@@ -33,7 +33,6 @@ import java.util.regex.Pattern;
 import java.util.stream.Stream;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
-
 import org.apache.ignite.ci.teamcity.ignited.buildtype.ParametersCompacted;
 import org.apache.ignite.ci.teamcity.ignited.change.ChangeCompacted;
 import org.apache.ignite.ci.teamcity.ignited.fatbuild.FatBuildCompacted;
@@ -268,13 +267,18 @@ public class SingleBuildRunCtx implements ISuiteResults {
         return tags;
     }
 
-    public void addTagsFromParameters(ParametersCompacted parameters, ITcServerConfig tcCfg,
-        IStringCompactor compactor) {
+    public static Set<String> getBuildTagsFromParameters(ITcServerConfig tcCfg,
+        IStringCompactor compactor, FatBuildCompacted fatBuildCompacted) {
+        ParametersCompacted parameters = fatBuildCompacted.parameters();
+        if (parameters == null)
+            return Collections.emptySet();
+
+        HashSet<String> tags = new HashSet<>();
         for (IBuildParameterSpec parm0 : tcCfg.filteringParameters()) {
             if (!parm0.isFilled())
                 continue;
 
-            String propVal = getPropertyOrSpecialValue(parameters, compactor, parm0.name());
+            String propVal = getPropertyOrSpecialValue(parameters, compactor, parm0.name(), fatBuildCompacted);
 
             if (Strings.isNullOrEmpty(propVal))
                 continue;
@@ -283,35 +287,37 @@ public class SingleBuildRunCtx implements ISuiteResults {
                 .filter(pvs -> {
                     String valRegExp = pvs.valueRegExp();
 
-                    if(!Strings.isNullOrEmpty(valRegExp))
+                    if (!Strings.isNullOrEmpty(valRegExp))
                         return Pattern.compile(valRegExp).matcher(propVal).find();
 
                     String exactVal = pvs.value();
 
-                    if(!Strings.isNullOrEmpty(exactVal))
+                    if (!Strings.isNullOrEmpty(exactVal))
                         return Objects.equals(exactVal, propVal);
 
                     return false;
                 })
                 .findAny()
-                .ifPresent(v -> addTag(v.label()));
+                .ifPresent(v -> tags.add(v.label()));
 
         }
+        return tags;
     }
 
     /**
      * @param parameters Parameters from build.
      * @param compactor Compactor.
      * @param parmKey Parmeters key.
+     * @param fatBuildCompacted
      */
-    public String getPropertyOrSpecialValue(ParametersCompacted parameters, IStringCompactor compactor,
-        String parmKey) {
+    public static String getPropertyOrSpecialValue(ParametersCompacted parameters, IStringCompactor compactor,
+        String parmKey, FatBuildCompacted fatBuildCompacted) {
 
         String propVal;
         if (ITeamcity.SUITE_ID_PROPERTY.equals(parmKey))
-            propVal = suiteId();
+            propVal = fatBuildCompacted.buildTypeId(compactor);
         else if (ITeamcity.SUITE_NAME_PROPERTY.equals(parmKey))
-            propVal = suiteName();
+            propVal = fatBuildCompacted.buildTypeName(compactor);
         else
             propVal = parameters.getProperty(compactor, parmKey);
 
@@ -338,4 +344,8 @@ public class SingleBuildRunCtx implements ISuiteResults {
     public int buildTypeIdId() {
         return buildCompacted.buildTypeId();
     }
+
+    public void addTags(Set<String> strings) {
+        this.tags.addAll(strings);
+    }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueList.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/BlameCandidate.java
similarity index 65%
copy from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueList.java
copy to tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/BlameCandidate.java
index d6e14a3..2b2feec 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueList.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/BlameCandidate.java
@@ -14,26 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.ignite.tcbot.engine.defect;
 
-package org.apache.ignite.ci.issue;
+import org.apache.ignite.tcbot.persistence.IStringCompactor;
+import org.apache.ignite.tcbot.persistence.Persisted;
 
-import java.util.List;
+@Persisted
+public class BlameCandidate {
+    private int vcsUsername = -1;
 
-public class IssueList {
-
-    public String branch;
-
-    private List<Issue> issues;
-
-    public IssueList(List<Issue> all) {
-        issues = all;
-    }
-
-    public List<Issue> getIssues() {
-        return issues;
+    public void vcsUsername(int username) {
+        vcsUsername = username;
     }
 
-    public void setIssues(List<Issue> issues) {
-        this.issues = issues;
+    public String vcsUsername(IStringCompactor compactor) {
+        return compactor.getStringFromId(vcsUsername);
     }
 }
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/CommitCompacted.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/CommitCompacted.java
new file mode 100644
index 0000000..7ab0cfc
--- /dev/null
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/CommitCompacted.java
@@ -0,0 +1,77 @@
+/*
+ * 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.tcbot.engine.defect;
+
+import java.util.Arrays;
+import org.apache.ignite.tcbot.persistence.Persisted;
+
+@Persisted
+public class CommitCompacted implements Comparable<CommitCompacted> {
+    /** Sha of the commit. */
+    private byte[] data;
+
+    public CommitCompacted(byte[] data) {
+        this.data = data;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        CommitCompacted commit = (CommitCompacted)o;
+        return Arrays.equals(data, commit.data);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        return Arrays.hashCode(data);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int compareTo(CommitCompacted o) {
+        return compare(data, o.data);
+    }
+
+    public static int compare(byte[] a, byte[] b) {
+        if (a == b)
+            return 0;
+        if (a == null || b == null)
+            return a == null ? -1 : 1;
+
+        int i = mismatch(a, b,
+            Math.min(a.length, b.length));
+        if (i >= 0)
+            return Byte.compare(a[i], b[i]);
+
+        return a.length - b.length;
+    }
+
+    public static int mismatch(byte[] a,
+        byte[] b,
+        int len) {
+
+        int i = 0;
+        for (; i < len; i++) {
+            if (a[i] != b[i])
+                return i;
+        }
+        return -1;
+    }
+
+}
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectCompacted.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectCompacted.java
new file mode 100644
index 0000000..a0c105b
--- /dev/null
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectCompacted.java
@@ -0,0 +1,164 @@
+/*
+ * 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.tcbot.engine.defect;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.ignite.ci.teamcity.ignited.change.ChangeCompacted;
+import org.apache.ignite.ci.teamcity.ignited.fatbuild.FatBuildCompacted;
+import org.apache.ignite.tcbot.persistence.IStringCompactor;
+
+public class DefectCompacted {
+    /** Syntetic Defect Id. */
+    private int id;
+
+    private int tcBranch = -1;
+
+    /** Tc server code hashcode. */
+    private int tcSrvId = -1;
+
+    /** Tc server code compactor string ID. */
+    private int tcSrvCodeCid = -1;
+
+    /** Tracked branch Compactor string ID. */
+    private int trackedBranchCid = -1;
+
+    /** Resolved by username id. */
+    private int resolvedByUsernameId = -1;
+    /** Commits hashes involved. */
+    private List<CommitCompacted> commits = new ArrayList<>();
+
+    /** Blame candidates. */
+    private List<BlameCandidate> blameCandidates = new ArrayList<>();
+
+    private Map<Integer, DefectFirstBuild> buildsInvolved = new HashMap<>();
+    private Map<Integer, ChangeCompacted> changes = new HashMap<>();
+
+    public DefectCompacted(int id) {
+        this.id = id;
+    }
+
+    public int resolvedByUsernameId() {
+        return resolvedByUsernameId;
+    }
+
+    /**
+     * @param collect Collected commits, should be sorted.
+     */
+    public boolean sameCommits(List<CommitCompacted> collect) {
+        return commits.equals(collect);
+    }
+
+    /**
+     * @param collect Collected commits, should be sorted.
+     */
+    public DefectCompacted commits(List<CommitCompacted> collect) {
+        commits.clear();
+        commits.addAll(collect);
+
+        return this;
+    }
+
+    public Map<Integer, DefectFirstBuild> buildsInvolved() {
+        return Collections.unmodifiableMap(buildsInvolved);
+    }
+
+    public DefectFirstBuild computeIfAbsent(FatBuildCompacted build) {
+        return buildsInvolved.computeIfAbsent(build.id(), k -> new DefectFirstBuild(build));
+    }
+
+    public int tcSrvId() {
+        return tcSrvId;
+    }
+
+    public void trackedBranchCidSetIfEmpty(int trackedBranchCid) {
+        if (this.trackedBranchCid <= 0)
+            this.trackedBranchCid = trackedBranchCid;
+
+    }
+
+    /** */
+    public String tcBranch(IStringCompactor compactor) {
+        return compactor.getStringFromId(tcBranch);
+    }
+
+    /** */
+    public String tcSrvCode(IStringCompactor compactor) {
+        return compactor.getStringFromId(tcSrvCodeCid);
+    }
+
+    /** */
+    public int id() {
+        return id;
+    }
+
+    /** */
+    public DefectCompacted tcBranch(int tcBranch) {
+        this.tcBranch = tcBranch;
+
+        return this;
+    }
+
+    /** */
+    public DefectCompacted tcSrvId(int srvId) {
+        this.tcSrvId = srvId;
+        return this;
+    }
+
+    /** */
+    public DefectCompacted tcSrvCodeCid(int cid) {
+        tcSrvCodeCid = cid;
+
+        return this;
+    }
+
+    public void id(int id) {
+        this.id = id;
+    }
+
+    public boolean hasBuild(int id) {
+        return buildsInvolved.containsKey(id);
+    }
+
+    public List<BlameCandidate> blameCandidates() {
+        if (blameCandidates == null)
+            return Collections.emptyList();
+
+        return Collections.unmodifiableList(blameCandidates);
+    }
+
+    public DefectCompacted changeMap(Map<Integer, ChangeCompacted> changes) {
+        this.changes = changes;
+
+        return this;
+    }
+
+    public Map<Integer, ChangeCompacted> changeMap() {
+        return changes;
+    }
+
+    public void addBlameCandidate(BlameCandidate candidate) {
+        blameCandidates.add(candidate);
+    }
+
+    public int trackedBranchCid() {
+        return trackedBranchCid;
+    }
+}
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueKey.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectFirstBuild.java
similarity index 52%
copy from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueKey.java
copy to tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectFirstBuild.java
index 348a623..2531034 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueKey.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectFirstBuild.java
@@ -14,41 +14,35 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.ignite.tcbot.engine.defect;
 
-package org.apache.ignite.ci.issue;
-
-import com.google.common.base.MoreObjects;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.ignite.ci.teamcity.ignited.fatbuild.FatBuildCompacted;
 import org.apache.ignite.tcbot.persistence.Persisted;
 
 @Persisted
-public class IssueKey {
-    public String server;
-    public Integer buildId;
-    public String testOrBuildName;
-
-    public IssueKey(String srv, Integer buildId, String testOrBuildName) {
-        this.server = srv;
-        this.buildId = buildId;
-        this.testOrBuildName = testOrBuildName;
-    }
+public class DefectFirstBuild {
+    private FatBuildCompacted build;
 
-    public String getServer() {
-        return server;
+    private Set<DefectIssue> issues = new HashSet<>();
+
+    public DefectFirstBuild(FatBuildCompacted build) {
+        this.build = build;
     }
 
-    public Integer getBuildId() {
-        return buildId;
+    public DefectFirstBuild addIssue(int typeCid, Integer testNameCid) {
+        issues.add(new DefectIssue(typeCid, testNameCid));
+
+        return this;
     }
 
-    public String getTestOrBuildName() {
-        return testOrBuildName;
+    public FatBuildCompacted build() {
+        return build;
     }
 
-    @Override public String toString() {
-        return MoreObjects.toStringHelper(this)
-            .add("server", server)
-            .add("buildId", buildId)
-            .add("testOrBuildName", testOrBuildName)
-            .toString();
+    public Set<DefectIssue> issues() {
+        return Collections.unmodifiableSet(issues);
     }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueKey.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectIssue.java
similarity index 51%
rename from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueKey.java
rename to tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectIssue.java
index 348a623..62304ec 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueKey.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectIssue.java
@@ -14,41 +14,38 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.ignite.tcbot.engine.defect;
 
-package org.apache.ignite.ci.issue;
-
-import com.google.common.base.MoreObjects;
+import java.util.Objects;
 import org.apache.ignite.tcbot.persistence.Persisted;
 
 @Persisted
-public class IssueKey {
-    public String server;
-    public Integer buildId;
-    public String testOrBuildName;
-
-    public IssueKey(String srv, Integer buildId, String testOrBuildName) {
-        this.server = srv;
-        this.buildId = buildId;
-        this.testOrBuildName = testOrBuildName;
-    }
+public class DefectIssue {
+    private int issueTypeCode;
+    private int testOrSuiteName;
 
-    public String getServer() {
-        return server;
+    public DefectIssue(int issueTypeCode, Integer testNameCid) {
+        this.issueTypeCode = issueTypeCode;
+        testOrSuiteName = testNameCid;
     }
 
-    public Integer getBuildId() {
-        return buildId;
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        DefectIssue issue = (DefectIssue)o;
+        return issueTypeCode == issue.issueTypeCode &&
+            testOrSuiteName == issue.testOrSuiteName;
     }
 
-    public String getTestOrBuildName() {
-        return testOrBuildName;
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        return Objects.hash(issueTypeCode, testOrSuiteName);
     }
 
-    @Override public String toString() {
-        return MoreObjects.toStringHelper(this)
-            .add("server", server)
-            .add("buildId", buildId)
-            .add("testOrBuildName", testOrBuildName)
-            .toString();
+    public int testNameCid() {
+        return testOrSuiteName;
     }
 }
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BuildTimeRecordUi.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectKey.java
similarity index 77%
copy from tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BuildTimeRecordUi.java
copy to tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectKey.java
index f411d83..cb8beb1 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BuildTimeRecordUi.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectKey.java
@@ -14,11 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.ignite.tcbot.engine.ui;
+package org.apache.ignite.tcbot.engine.defect;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefectKey {
+    private int srvId;
+    private int branchId;
+    private List<CommitCompacted> commits = new ArrayList<>();
 
-@SuppressWarnings({"WeakerAccess", "PublicField"})
-public class BuildTimeRecordUi {
-    public String buildType;
-    public String averageDuration;
-    public String totalDuration;
 }
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectsStorage.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectsStorage.java
new file mode 100644
index 0000000..2efb4e2
--- /dev/null
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/defect/DefectsStorage.java
@@ -0,0 +1,155 @@
+/*
+ * 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.tcbot.engine.defect;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+import javax.annotation.concurrent.NotThreadSafe;
+import javax.cache.Cache;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteAtomicSequence;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.QueryEntity;
+import org.apache.ignite.cache.query.QueryCursor;
+import org.apache.ignite.cache.query.ScanQuery;
+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.configuration.CacheConfiguration;
+import org.apache.ignite.tcbot.persistence.CacheConfigs;
+
+@NotThreadSafe
+public class DefectsStorage {
+    /** Bot detected defects. */
+    public static final String BOT_DETECTED_DEFECTS = "botDetectedDefects";
+    /** Bot detected defects sequence. */
+    public static final String BOT_DETECTED_DEFECTS_SEQ = "botDetectedDefectsSeq";
+
+    @Inject
+    private Provider<Ignite> igniteProvider;
+    @Inject
+    private ChangeDao changeDao;
+
+
+    public DefectsStorage() {
+    }
+
+    private IgniteAtomicSequence sequence() {
+        return getIgnite().atomicSequence(BOT_DETECTED_DEFECTS_SEQ, 0, true);
+    }
+
+    private IgniteCache<Integer, DefectCompacted> cache() {
+        return botDetectedIssuesCache(getIgnite());
+    }
+
+    private Ignite getIgnite() {
+        return igniteProvider.get();
+    }
+
+    public static IgniteCache<Integer, DefectCompacted> botDetectedIssuesCache(Ignite ignite) {
+        CacheConfiguration<Integer, DefectCompacted> ccfg = CacheConfigs.getCacheV2TxConfig(BOT_DETECTED_DEFECTS);
+
+        ccfg.setQueryEntities(Collections.singletonList(new QueryEntity(Integer.class, DefectCompacted.class)));
+
+        return ignite.getOrCreateCache(ccfg);
+    }
+
+    public DefectCompacted merge(
+        int tcSrvCodeCid,
+        final int srvId,
+        FatBuildCompacted fatBuild,
+        BiFunction<Integer, DefectCompacted, DefectCompacted> function) {
+
+        IgniteCache<Integer, DefectCompacted> cache = cache();
+
+        try (QueryCursor<Cache.Entry<Integer, DefectCompacted>> qry = cache.query(new ScanQuery<Integer, DefectCompacted>()
+            .setFilter((k, v) -> v.resolvedByUsernameId() < 1 && v.tcSrvId() == srvId))) {
+            for (Cache.Entry<Integer, DefectCompacted> next : qry) {
+                DefectCompacted openDefect = next.getValue();
+
+                if (openDefect.hasBuild(fatBuild.id()))
+                    return processExisting(function, cache, next.getKey(), openDefect);
+            }
+        }
+
+        int[] changes = fatBuild.changes();
+        Map<Integer, ChangeCompacted> changeList = changeDao.getAll(srvId, changes);
+
+        List<CommitCompacted> commitsToUse = changeList
+            .values()
+            .stream()
+            .map(ChangeCompacted::commitVersion)
+            .map(CommitCompacted::new)
+            .sorted(CommitCompacted::compareTo)
+            .collect(Collectors.toList());
+
+        try (QueryCursor<Cache.Entry<Integer, DefectCompacted>> qry = cache.query(new ScanQuery<Integer, DefectCompacted>()
+            .setFilter((k, v) -> v.resolvedByUsernameId() < 1 && v.tcSrvId() == srvId))) {
+            for (Cache.Entry<Integer, DefectCompacted> next : qry) {
+                DefectCompacted openDefect = next.getValue();
+
+                if (openDefect.sameCommits(commitsToUse))
+                    return processExisting(function, cache, next.getKey(), openDefect);
+            }
+        }
+
+        int id = (int)sequence().incrementAndGet();
+
+        DefectCompacted defect = new DefectCompacted(id)
+            .commits(commitsToUse)
+            .changeMap(changeList)
+            .tcBranch(fatBuild.branchName())
+            .tcSrvId(srvId)
+            .tcSrvCodeCid(tcSrvCodeCid);
+
+        DefectCompacted defectT = function.apply(id, defect);
+
+        boolean putSuccess = cache.putIfAbsent(id, defectT);
+
+        return defectT;
+    }
+
+    public DefectCompacted processExisting(BiFunction<Integer, DefectCompacted, DefectCompacted> function,
+        IgniteCache<Integer, DefectCompacted> cache, Integer id, DefectCompacted openDefect) {
+        DefectCompacted defect = function.apply(id, openDefect);
+
+        defect.id(id);
+
+        cache.put(id, defect);
+
+        return defect;
+    }
+
+    public List<DefectCompacted> loadAllDefects() {
+        List<DefectCompacted> res = new ArrayList<>();
+        try (QueryCursor<Cache.Entry<Integer, DefectCompacted>> qry = cache().query(new ScanQuery<Integer, DefectCompacted>()
+            .setFilter((k, v) -> v.resolvedByUsernameId() < 1))) {
+            for (Cache.Entry<Integer, DefectCompacted> next : qry) {
+                DefectCompacted openDefect = next.getValue();
+                openDefect.id(next.getKey());
+                res.add(openDefect);
+            }
+        }
+        return res;
+    }
+}
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/issue/IIssuesStorage.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IIssuesStorage.java
similarity index 97%
rename from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/issue/IIssuesStorage.java
rename to tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IIssuesStorage.java
index af7a992..df19ae7 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/issue/IIssuesStorage.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IIssuesStorage.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.ci.tcbot.issue;
+package org.apache.ignite.tcbot.engine.issue;
 
 import java.util.stream.Stream;
 import javax.annotation.Nullable;
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueType.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IssueType.java
similarity index 97%
rename from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueType.java
rename to tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IssueType.java
index a690614..b7b6cce 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueType.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IssueType.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.ci.issue;
+package org.apache.ignite.tcbot.engine.issue;
 
 /**
  * Type of Issue detectable by the Bot.
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssuesStorage.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IssuesStorage.java
similarity index 93%
rename from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssuesStorage.java
rename to tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IssuesStorage.java
index a18294d..1171db2 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssuesStorage.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IssuesStorage.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.ci.issue;
+package org.apache.ignite.tcbot.engine.issue;
 
 import java.util.HashMap;
 import java.util.stream.Stream;
@@ -26,8 +26,9 @@ import javax.inject.Inject;
 import javax.inject.Provider;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteCache;
-import org.apache.ignite.ci.db.TcHelperDb;
-import org.apache.ignite.ci.tcbot.issue.IIssuesStorage;
+import org.apache.ignite.ci.issue.Issue;
+import org.apache.ignite.ci.issue.IssueKey;
+import org.apache.ignite.tcbot.persistence.CacheConfigs;
 
 /**
  *
@@ -50,7 +51,7 @@ public class IssuesStorage implements IIssuesStorage {
     }
 
     public static IgniteCache<IssueKey, Issue> botDetectedIssuesCache(Ignite ignite) {
-        return ignite.getOrCreateCache(TcHelperDb.getCacheV2TxConfig(BOT_DETECTED_ISSUES));
+        return ignite.getOrCreateCache(CacheConfigs.getCacheV2TxConfig(BOT_DETECTED_ISSUES));
     }
 
     /** {@inheritDoc} */
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
new file mode 100644
index 0000000..1d4e0af
--- /dev/null
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectSummaryUi.java
@@ -0,0 +1,94 @@
+/*
+ * 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.tcbot.engine.ui;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.ignite.tcbot.engine.defect.DefectCompacted;
+import org.apache.ignite.tcbot.persistence.IStringCompactor;
+
+public class BoardDefectSummaryUi {
+    private final transient DefectCompacted defect;
+    private final transient IStringCompactor compactor;
+
+    public String branch;
+
+    public Integer cntIssues;
+    public Integer fixedIssues;
+    public Integer notFixedIssues;
+
+    public List<String> testOrSuitesAffected = new ArrayList<>();
+    public Set<String> tags = new HashSet<>();
+
+    public BoardDefectSummaryUi(DefectCompacted defect, IStringCompactor compactor) {
+        this.defect = defect;
+        this.compactor = compactor;
+    }
+
+    public Set<String> getTags() {
+        return tags;
+    }
+
+    public List<String> getSuites() {
+        return defect.buildsInvolved().values().stream().map(
+            b -> b.build().buildTypeName()
+        ).distinct().map(compactor::getStringFromId).collect(Collectors.toList());
+    }
+
+    public List<String> getBlameCandidates() {
+        return defect.blameCandidates().stream().map(c -> c.vcsUsername(compactor)).collect(Collectors.toList());
+    }
+
+    public String getTrackedBranch() {
+       return compactor.getStringFromId(defect.trackedBranchCid());
+    }
+
+    public int getId() {
+        return defect.id();
+    }
+
+    public void addIssue(String testOrBuildName, String trackedBranchName) {
+        if (cntIssues == null)
+            cntIssues = 0;
+
+        cntIssues++;
+
+        testOrSuitesAffected.add(testOrBuildName);
+
+    }
+
+    public void addFixedIssue() {
+        if (fixedIssues == null)
+            fixedIssues = 0;
+
+        fixedIssues++;
+    }
+
+    public void addNotFixedIssue() {
+        if (notFixedIssues == null)
+            notFixedIssues = 0;
+
+        notFixedIssues++;
+    }
+
+    public void addTags(Set<String> parameters) {
+        this.tags.addAll(parameters);
+    }
+}
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BuildTimeRecordUi.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardSummaryUi.java
similarity index 71%
copy from tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BuildTimeRecordUi.java
copy to tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardSummaryUi.java
index f411d83..8063935 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BuildTimeRecordUi.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardSummaryUi.java
@@ -16,9 +16,18 @@
  */
 package org.apache.ignite.tcbot.engine.ui;
 
-@SuppressWarnings({"WeakerAccess", "PublicField"})
-public class BuildTimeRecordUi {
-    public String buildType;
-    public String averageDuration;
-    public String totalDuration;
+import java.util.List;
+import java.util.ArrayList;
+
+public class BoardSummaryUi {
+    private List<BoardDefectSummaryUi> defects = new ArrayList<>();
+
+
+    public void addDefect(BoardDefectSummaryUi defect) {
+        this.defects.add(defect);
+    }
+
+    public List<BoardDefectSummaryUi> getDefects() {
+        return defects;
+    }
 }
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BuildTimeRecordUi.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BuildTimeRecordUi.java
index f411d83..3e5bb3d 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BuildTimeRecordUi.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BuildTimeRecordUi.java
@@ -21,4 +21,15 @@ public class BuildTimeRecordUi {
     public String buildType;
     public String averageDuration;
     public String totalDuration;
+
+    private Integer cnt;
+
+    /** */
+    public Integer getCnt() {
+        return cnt;
+    }
+
+    public void setCnt(Integer cnt) {
+        this.cnt = cnt;
+    }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueList.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/IssueListUi.java
similarity index 87%
rename from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueList.java
rename to tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/IssueListUi.java
index d6e14a3..2299187 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/issue/IssueList.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/IssueListUi.java
@@ -15,17 +15,17 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.ci.issue;
+package org.apache.ignite.tcbot.engine.ui;
 
 import java.util.List;
+import org.apache.ignite.ci.issue.Issue;
 
-public class IssueList {
-
+public class IssueListUi {
     public String branch;
 
     private List<Issue> issues;
 
-    public IssueList(List<Issue> all) {
+    public IssueListUi(List<Issue> all) {
         issues = all;
     }
 
diff --git a/tcbot-persistence/src/main/java/org/apache/ignite/tcbot/persistence/CacheConfigs.java b/tcbot-persistence/src/main/java/org/apache/ignite/tcbot/persistence/CacheConfigs.java
index 7b9d757..24b5ab3 100644
--- a/tcbot-persistence/src/main/java/org/apache/ignite/tcbot/persistence/CacheConfigs.java
+++ b/tcbot-persistence/src/main/java/org/apache/ignite/tcbot/persistence/CacheConfigs.java
@@ -16,6 +16,7 @@
  */
 package org.apache.ignite.tcbot.persistence;
 
+import org.apache.ignite.cache.CacheAtomicityMode;
 import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction;
 import org.apache.ignite.configuration.CacheConfiguration;
 
@@ -42,4 +43,10 @@ public class CacheConfigs {
 
         return ccfg;
     }
+
+    @Nonnull
+    public static <K, V> CacheConfiguration<K, V> getCacheV2TxConfig(String name) {
+        return CacheConfigs.<K, V>getCacheV2Config(name).setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL);
+    }
+
 }
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/change/ChangeCompacted.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/change/ChangeCompacted.java
index 45aafd8..37072de 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/change/ChangeCompacted.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/change/ChangeCompacted.java
@@ -143,9 +143,9 @@ public class ChangeCompacted implements IVersionedEntity {
 
     /** {@inheritDoc} */
     @Override public int hashCode() {
-        int result = Objects.hash(_ver, id, vcsUsername, tcUserId, tcUserUsername, tcUserFullname, date);
-        result = 31 * result + Arrays.hashCode(version);
-        return result;
+        int res = Objects.hash(_ver, id, vcsUsername, tcUserId, tcUserUsername, tcUserFullname, date);
+        res = 31 * res + Arrays.hashCode(version);
+        return res;
     }
 
     /**
@@ -154,4 +154,15 @@ public class ChangeCompacted implements IVersionedEntity {
     public String commitFullVersion() {
         return DatatypeConverter.printHexBinary(version).toLowerCase();
     }
+
+    /**
+     *
+     */
+    public byte[] commitVersion() {
+        return version;
+    }
+
+    public int vcsUsername() {
+        return vcsUsername;
+    }
 }
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/change/ChangeDao.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/change/ChangeDao.java
index b79e5fd..9ab12df 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/change/ChangeDao.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/change/ChangeDao.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.ci.teamcity.ignited.change;
 
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
@@ -24,10 +25,9 @@ import javax.inject.Inject;
 import javax.inject.Provider;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteCache;
+import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.tcbot.common.interceptor.AutoProfiling;
 import org.apache.ignite.tcbot.persistence.CacheConfigs;
-import org.apache.ignite.tcbot.persistence.IStringCompactor;
-import org.apache.ignite.configuration.CacheConfiguration;
 
 public class ChangeDao {
     /** Cache name */
@@ -91,12 +91,19 @@ public class ChangeDao {
     }
 
     @AutoProfiling
-    public Map<Long, ChangeCompacted> getAll(int srvIdMaskHigh, int[] changeIds) {
+    public Map<Integer, ChangeCompacted> getAll(int srvIdMaskHigh, int[] changeIds) {
         final Set<Long> collect = new HashSet<>();
 
         for (int changeId : changeIds)
             collect.add(changeIdToCacheKey(srvIdMaskHigh, changeId));
 
-        return changesCache.getAll(collect);
+        final Map<Integer, ChangeCompacted> changes = new HashMap<>();
+        changesCache.getAll(collect).forEach((k, v) -> {
+            final int changeId = ChangeDao.cacheKeyToChangeId(k);
+
+            changes.put(changeId, v);
+        });
+
+        return changes;
     }
 }
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildCompacted.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildCompacted.java
index 8b52eb1..42b36c4 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildCompacted.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/FatBuildCompacted.java
@@ -533,7 +533,11 @@ public class FatBuildCompacted extends BuildRefCompacted implements IVersionedEn
     }
 
     public String buildTypeName(IStringCompactor compactor) {
-        return compactor.getStringFromId(name);
+        return compactor.getStringFromId(buildTypeName());
+    }
+
+    public int buildTypeName() {
+        return name;
     }
 
     public String projectId(IStringCompactor compactor) {
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/TeamcityIgnitedImpl.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/TeamcityIgnitedImpl.java
index e6d0778..ad40e79 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/TeamcityIgnitedImpl.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/TeamcityIgnitedImpl.java
@@ -517,7 +517,7 @@ public class TeamcityIgnitedImpl implements ITeamcityIgnited {
 
         Stream<Integer> allIds = Stream.concat(Stream.of(build.getId()), deps.stream().map(BuildRef::getId));
 
-        //todo may add additional parameter: load builds into DB in sync/async fashion
+        //todo may addBuild additional parameter: load builds into DB in sync/async fashion
         buildRefSync.runActualizeBuildRefs(srvCode, BuildRefSync.SyncMode.ULTRAFAST, allIds.collect(Collectors.toSet()), conn);
 
         return build;
@@ -613,16 +613,7 @@ public class TeamcityIgnitedImpl implements ITeamcityIgnited {
     /** {@inheritDoc} */
     @AutoProfiling
     @Override public Collection<ChangeCompacted> getAllChanges(int[] changeIds) {
-        final Map<Long, ChangeCompacted> all = changesDao.getAll(srvIdMaskHigh, changeIds);
-
-        final Map<Integer, ChangeCompacted> changes = new HashMap<>();
-
-        //todo support change version upgrade
-        all.forEach((k, v) -> {
-            final int changeId = ChangeDao.cacheKeyToChangeId(k);
-
-            changes.put(changeId, v);
-        });
+        final Map<Integer, ChangeCompacted> changes = changesDao.getAll(srvIdMaskHigh, changeIds);
 
         for (int changeId : changeIds) {
             if (!changes.containsKey(changeId)) {
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/FatBuildDao.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/FatBuildDao.java
index 5b2220c..439adca 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/FatBuildDao.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/FatBuildDao.java
@@ -279,7 +279,7 @@ public class FatBuildDao {
                             int srvId = BuildRefDao.cacheKeyToSrvId(key);
                             boolean hasTimeout = build.hasBuildProblemType(timeoutProblemCode);
 
-                            res.add(srvId, buildTypeId, runningTime, hasTimeout);
+                            res.addBuild(srvId, buildTypeId, runningTime, hasTimeout);
                         }
                     });
                 }
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/ITest.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/ITest.java
index 63bd32a..508e41a 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/ITest.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/ITest.java
@@ -48,9 +48,15 @@ public interface ITest {
      * @param successStatus Success status code.
      */
     public default boolean isFailedButNotMuted(int successStatus) {
-        return successStatus != status() && !isMutedOrIgnored();
+        return isFailedTest(successStatus) && !isMutedOrIgnored();
     }
 
+    public default boolean isFailedTest(int successStatus) {
+        return successStatus != status();
+    }
+
+    public boolean isFailedTest(IStringCompactor compactor);
+
     public default boolean isMutedOrIgnored() {
         return isMutedTest() || isIgnoredTest();
     }
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/TestCompactedV2.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/TestCompactedV2.java
index 4531efc..15ec251 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/TestCompactedV2.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/build/TestCompactedV2.java
@@ -354,6 +354,10 @@ public class TestCompactedV2 implements ITest {
         return duration < 0 ? null : duration;
     }
 
+    @Override public boolean isFailedTest(IStringCompactor compactor) {
+        return isFailedTest(statusSuccess(compactor));
+    }
+
     /** {@inheritDoc} */
     @Override public String toString() {
         return MoreObjects.toStringHelper(this)
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildlog/LogMsgToWarn.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildlog/LogMsgToWarn.java
index 573d498..78bec57 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildlog/LogMsgToWarn.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildlog/LogMsgToWarn.java
@@ -25,7 +25,7 @@ import org.apache.ignite.tcservice.model.result.problems.ProblemOccurrence;
  */
 //todo make non static
 //todo include test name
-//todo add NPE
+//todo addBuild NPE
 public class LogMsgToWarn {
 
     private static final String JAVA_LEVEL_DEADLOCK_TXT = " Java-level deadlock:";
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildref/BuildRefDao.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildref/BuildRefDao.java
index 654b08c..d942c12 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildref/BuildRefDao.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildref/BuildRefDao.java
@@ -352,4 +352,8 @@ public class BuildRefDao {
     public IgniteCache<Long, BuildRefCompacted> buildRefsCache() {
         return buildRefsCache;
     }
+
+    public BuildRefCompacted get(int srvId, Integer buildId) {
+        return buildRefsCache.get(buildIdToCacheKey(srvId, buildId));
+    }
 }
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildtime/BuildTimeRecord.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildtime/BuildTimeRecord.java
index 63e8026..e88754b 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildtime/BuildTimeRecord.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildtime/BuildTimeRecord.java
@@ -35,4 +35,8 @@ public class BuildTimeRecord {
     public long totalDuration() {
         return totaltime;
     }
+
+    public int count() {
+        return cnt;
+    }
 }
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildtime/BuildTimeResult.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildtime/BuildTimeResult.java
index fcf6675..f48f8a8 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildtime/BuildTimeResult.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildtime/BuildTimeResult.java
@@ -26,10 +26,12 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 public class BuildTimeResult {
+    /** Build time summary by build type, map from (srvId||buildTypeId)->Invocations summary. */
     private Map<Long, BuildTimeRecord> btByBuildType = new HashMap<>();
+    /** Timed out builds: Build time summary by build type, map from (srvId||buildTypeId)->Invocations summary. */
     private Map<Long, BuildTimeRecord> timedOutByBuildType = new HashMap<>();
 
-    public void add(int srvId, int buildTypeId, long runningTime, boolean hasTimeout) {
+    public void addBuild(int srvId, int buildTypeId, long runningTime, boolean hasTimeout) {
         long cacheKey = buildTypeToCacheKey(srvId, buildTypeId);
         btByBuildType.computeIfAbsent(cacheKey, k -> new BuildTimeRecord()).addInvocation(runningTime);
 
@@ -77,14 +79,14 @@ public class BuildTimeResult {
 
     private Stream<Map.Entry<Long, BuildTimeRecord>> filtered(
         Map<Long, BuildTimeRecord> map,
-        Set<Integer> availableServers,
+        Set<Integer> availableSrvs,
         long minAvgDurationMs,
         long totalDurationMs) {
         return map.entrySet().stream()
                 .filter(e -> {
                     Long key = e.getKey();
                     int srvId = cacheKeyToSrvId(key);
-                    return availableServers.contains(srvId);
+                    return availableSrvs.contains(srvId);
                 })
             .filter(e -> e.getValue().avgDuration() > minAvgDurationMs)
             .filter(e -> e.getValue().totalDuration() > totalDurationMs);