You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sk...@apache.org on 2021/01/12 21:30:25 UTC

[ignite-teamcity-bot] branch master updated: Added mute issues from board. Fixes #181

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

sk0x50 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 0b493be  Added mute issues from board. Fixes #181
0b493be is described below

commit 0b493bee998a32e771cf2bf7096f2250ed40c13a
Author: sergeyuttsel <ut...@gmail.com>
AuthorDate: Wed Jan 13 00:29:47 2021 +0300

    Added mute issues from board. Fixes #181
    
    Signed-off-by: Slava Koptilin <sl...@gmail.com>
---
 .../ignite/ci/web/rest/board/BoardRestService.java |  57 +++++
 .../src/main/webapp/board/index.html               | 109 +++++++++-
 .../src/main/webapp/js/common-1.6.js               |   4 +-
 .../main/webapp/{board => mutedissues}/index.html  | 240 +++++++++++----------
 .../ignite/tcbot/engine/TcBotEngineModule.java     |   4 +-
 .../ignite/tcbot/engine/board/BoardService.java    | 148 ++++++++++++-
 .../tcbot/engine/board/IssueResolveStatus.java     |   2 +-
 .../tcbot/engine/boardmute/MutedIssueInfo.java     |  78 +++++++
 .../tcbot/engine/boardmute/MutedIssueKey.java      |  87 ++++++++
 .../tcbot/engine/boardmute/MutedIssuesDao.java     |  60 ++++++
 .../ignite/tcbot/engine/issue/IssueType.java       |  20 ++
 .../ignite/tcbot/engine/ui/BoardDefectIssueUi.java |  17 ++
 .../tcbot/engine/ui/BoardDefectSummaryUi.java      |  13 +-
 .../ignite/tcbot/engine/ui/MutedIssueUi.java       | 120 +++++++++++
 .../tcignited/buildlog/BuildLogProcessor.java      |   9 +-
 15 files changed, 834 insertions(+), 134 deletions(-)

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
index 362b06c..58a5650 100644
--- 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
@@ -16,9 +16,14 @@
  */
 package org.apache.ignite.ci.web.rest.board;
 
+import java.util.Collection;
 import javax.servlet.ServletContext;
 import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.FormParam;
 import javax.ws.rs.GET;
+import javax.ws.rs.PATCH;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
@@ -28,12 +33,18 @@ 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;
+import org.apache.ignite.tcbot.engine.ui.MutedIssueUi;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @Path(BoardRestService.BOARD)
 @Produces(MediaType.APPLICATION_JSON)
 public class BoardRestService {
     static final String BOARD = "board";
 
+    /** */
+    private static final Logger logger = LoggerFactory.getLogger(BoardRestService.class);
+
     /** Servlet Context. */
     @Context
     private ServletContext ctx;
@@ -49,4 +60,50 @@ public class BoardRestService {
 
         return CtxListener.getInjector(ctx).getInstance(BoardService.class).summary(creds, baseBranch);
     }
+
+    @PUT
+    @Path("muteIssue")
+    public void muteIssue(
+        @FormParam("tcSrvId") int tcSrvId,
+        @FormParam("nameId") int nameId,
+        @FormParam("branch") String branch,
+        @FormParam("trackedBranch") String trackedBranch,
+        @FormParam("issueType") String issueType,
+        @FormParam("jiraTicket") String jiraTicket,
+        @FormParam("comment") String comment,
+        @FormParam("userName") String userName,
+        @FormParam("webUrl") String webUrl) {
+        CtxListener.getInjector(ctx).getInstance(BoardService.class)
+            .muteIssue(tcSrvId, nameId, branch, trackedBranch, issueType, jiraTicket, comment, userName, webUrl);
+    }
+
+    @PATCH
+    @Path("updateIssue")
+    public void updateIssue(
+        @FormParam("tcSrvId") int tcSrvId,
+        @FormParam("nameId") int nameId,
+        @FormParam("branch") String branch,
+        @FormParam("issueType") String issueType,
+        @FormParam("jiraTicket") String jiraTicket,
+        @FormParam("comment") String comment) {
+        CtxListener.getInjector(ctx).getInstance(BoardService.class)
+            .updateIssue(tcSrvId, nameId, branch, issueType, jiraTicket, comment);
+    }
+
+    @DELETE
+    @Path("unmuteIssue")
+    public void unmuteIssue(
+        @FormParam("tcSrvId") int tcSrvId,
+        @FormParam("nameId") int nameId,
+        @FormParam("branch") String branch,
+        @FormParam("issueType") String issueType) {
+        CtxListener.getInjector(ctx).getInstance(BoardService.class)
+            .unmuteIssue(tcSrvId, nameId, branch, issueType);
+    }
+
+    @GET
+    @Path("mutedIssues")
+    public Collection<MutedIssueUi> getMutedIssues(@QueryParam("baseBranch") String baseBranch) {
+        return CtxListener.getInjector(ctx).getInstance(BoardService.class).getAllMutedIssues(baseBranch);
+    }
 }
diff --git a/ignite-tc-helper-web/src/main/webapp/board/index.html b/ignite-tc-helper-web/src/main/webapp/board/index.html
index 3db0e03..1469b8c 100644
--- a/ignite-tc-helper-web/src/main/webapp/board/index.html
+++ b/ignite-tc-helper-web/src/main/webapp/board/index.html
@@ -22,6 +22,9 @@
     <script src="/js/common-1.6.js"></script>
 
     <style>
+        input.solid {border-style: solid;}
+        textarea.solid {border-style: solid;}
+        select.solid {border-style: solid;}
         select#selBranch {
             border-style: solid;
             -webkit-appearance: auto;
@@ -31,6 +34,75 @@
     </style>
 </head>
 <body>
+
+<div id="message" title="Mute issue">
+    <label>Issue</label><br>
+    <textarea rows="2" cols="60" class="solid" id="issueName" readonly></textarea><br>
+    <label>Jira ticket</label><br>
+    <textarea maxlength = "100" rows="2" cols="60" class="solid" id="jiraTicket"></textarea><br>
+    <label>Comment</label><br>
+    <textarea maxlength = "500" rows="7" cols="60" class="solid" id="comment"></textarea><br>
+</div>
+
+<script>
+    $('#message').dialog({
+        autoOpen: false,
+        modal: true,
+        maxWidth:600,
+        maxHeight: 500,
+        width: 600,
+        height: 350,
+        show: {
+            effect: "fade",
+            duration: 1000
+            },
+        hide: {
+            effect: "blind",
+            duration: 500
+            },
+        open: function() {
+            $(this).parent().promise().done(function () {
+                document.getElementById('issueName').value = $("#message").data('issue').name;
+                document.getElementById('jiraTicket').value = '';
+                document.getElementById('comment').value = '';
+            });
+        },
+        close: function() {
+            $(this).parent().promise().done(function () {
+                document.getElementById('issueName').value = '';
+                document.getElementById('jiraTicket').value = '';
+                document.getElementById('comment').value = '';
+            });
+        },
+        buttons: {
+            Mute: function() {
+                document.getElementsByClassName('issue_' + $("#message").data('item').id + '_' + $("#message").data('index')).item(0).value = 'BotMuted';
+                    $.ajax({
+                        url: "/rest/board/muteIssue",
+                        type: 'PUT',
+                        data: { tcSrvId: $("#message").data('issue').tcSrvId,
+                                nameId: $("#message").data('issue').nameId,
+                                trackedBranch: $("#message").data('item').trackedBranch,
+                                branch: $("#message").data('item').branch,
+                                issueType: $("#message").data('issue').issueType,
+                                jiraTicket: document.getElementById('jiraTicket').value,
+                                comment: document.getElementById('comment').value,
+                                userName: document.getElementById('userName').text,
+                                webUrl: $("#message").data('issue').webUrl
+                              },
+                        error: function (jqXHR, exception) {
+                            showErrInLoadStatus(jqXHR, exception);
+                        }
+                    });
+                $(this).dialog("close");
+                },
+            Cancel: function() {
+                $(this).dialog("close");
+                }
+        }
+    });
+    </script>
+
 <script>
     var g_shownDataHashCodeHex = "";
     let gVue, g_Loading, g_TcBotVersion;
@@ -90,7 +162,14 @@
                         },
                         error: showErrInLoadStatus
                     });
-                }
+                },
+                muteModal: function (item, issue, index) {
+                    $('#message')
+                        .data('item', item)
+                        .data('issue', issue)
+                        .data('index', index)
+                        .dialog("open");
+                    },
             }
         });
     }
@@ -207,6 +286,7 @@
                 :expanded.sync="expanded"
                 show-expand
                 dense
+                multi-sort
         >
             <!-- expand item/row -->
             <template v-slot:expanded-item="{ headers, item }">
@@ -220,15 +300,17 @@
 
                     Tests affected:
                     <table>
-                        <tr v-for="(issue) in item.allIssues">
-                            <td v-if="issue.status === 'FIXED'">
-                                Fixed
-                            </td>
-                            <td v-else-if="issue.status === 'FAILING'">
-                                Still Failing
-                            </td>
-                            <td v-else>
-                                {{ issue.status }}
+                        <tr v-for="(issue, i) in item.allIssues">
+                            <td v-bind:class="'issue_' + item.id + '_' + i">
+                                <td v-if="issue.status === 'FIXED'">
+                                    Fixed
+                                </td>
+                                <td v-else-if="issue.status === 'FAILING'">
+                                    Still Failing
+                                </td>
+                                <td v-else>
+                                    {{ issue.status }}
+                                </td>
                             </td>
 
                             <td>{{ issue.issueType }}</td>
@@ -240,6 +322,11 @@
                                 {{ issue.name }}
                                 </span>
                             </td>
+                            <td>
+                                <template v-if="issue.status !== 'BOT_MUTED' && issue.status !== 'FIXED'">
+                                    <button class="muteModal" v-on:click="muteModal(item, issue, i)">Mute</button>
+                                </template>
+                            </td>
                         </tr>
                     </table>
 
@@ -293,4 +380,4 @@
 <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
+</html>
diff --git a/ignite-tc-helper-web/src/main/webapp/js/common-1.6.js b/ignite-tc-helper-web/src/main/webapp/js/common-1.6.js
index f80ad3f..8c96d23 100644
--- a/ignite-tc-helper-web/src/main/webapp/js/common-1.6.js
+++ b/ignite-tc-helper-web/src/main/webapp/js/common-1.6.js
@@ -174,6 +174,8 @@ function showMenu(menuData) {
         res += "<a href=\"/issues.html\" title='Detected issues list'>Issues history</a>";
         res += "<a href=\"/visas.html\" title='Issued TC Bot Visa history'>Visas history</a>";
         res += "<a href=\"/mutes.html\" title='Muted tests list'>Muted tests</a>";
+        res += "<a href=\"/mutedissues/index.html\" title='Muted issues list'>Muted issues</a>";
+        res += "<a href=\"/board/index.html\" title='Board'>Board</a>";
 
         res += "<div class='topnav-right'>";
 
@@ -183,7 +185,7 @@ function showMenu(menuData) {
 
         res += "<a href='/monitoring.html'>Server state</a>";
 
-        res += "<a href='/user.html'>" + userName + "</a>";
+        res += "<a id='userName' href='/user.html'>" + userName + "</a>";
         var logout = "/login.html" + "?exit=true&backref=" + encodeURIComponent(window.location.href);
         res += "<a href='" + logout + "'>Logout</a>";
 
diff --git a/ignite-tc-helper-web/src/main/webapp/board/index.html b/ignite-tc-helper-web/src/main/webapp/mutedissues/index.html
similarity index 50%
copy from ignite-tc-helper-web/src/main/webapp/board/index.html
copy to ignite-tc-helper-web/src/main/webapp/mutedissues/index.html
index 3db0e03..d190347 100644
--- a/ignite-tc-helper-web/src/main/webapp/board/index.html
+++ b/ignite-tc-helper-web/src/main/webapp/mutedissues/index.html
@@ -1,6 +1,6 @@
 <html>
 <head>
-    <title>Apache Ignite Teamcity Bot - Tracked branch - Detailed status of failures</title>
+    <title>Apache Ignite Teamcity Bot - Muted issues</title>
     <link rel="icon" href="/img/leaf-icon-png-7066.png">
 
     <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
@@ -22,6 +22,9 @@
     <script src="/js/common-1.6.js"></script>
 
     <style>
+        input.solid {border-style: solid;}
+        textarea.solid {border-style: solid;}
+        select.solid {border-style: solid;}
         select#selBranch {
             border-style: solid;
             -webkit-appearance: auto;
@@ -31,7 +34,82 @@
     </style>
 </head>
 <body>
+
+<div id="message" title="Edit issue">
+    <label>Issue</label><br>
+    <textarea rows="2" cols="60" class="solid" id="issueName" readonly></textarea><br>
+    <label>Jira ticket</label><br>
+    <textarea maxlength = "100" rows="2" cols="60" class="solid" id="jiraTicket"></textarea><br>
+    <label>Comment</label><br>
+    <textarea maxlength = "500" rows="7" cols="60" class="solid" id="comment"></textarea><br>
+</div>
+
 <script>
+    $('#message').dialog({
+        autoOpen: false,
+        modal: true,
+        maxWidth:600,
+        maxHeight: 500,
+        width: 600,
+        height: 350,
+        show: {
+            effect: "fade",
+            duration: 1000
+            },
+        hide: {
+            effect: "blind",
+            duration: 500
+            },
+        open: function() {
+            $(this).parent().promise().done(function () {
+                document.getElementById('issueName').value = $("#message").data('issue').name;
+                document.getElementById('jiraTicket').value = $("#message").data('issue').jiraTicket;
+                document.getElementById('comment').value = $("#message").data('issue').comment;
+            });
+        },
+        close: function() {
+            $(this).parent().promise().done(function () {
+                document.getElementById('issueName').value = '';
+                document.getElementById('jiraTicket').value = '';
+                document.getElementById('comment').value = '';
+            });
+        },
+        buttons: {
+            Save: function() {
+                    $.ajax({
+                        url: "/rest/board/updateIssue",
+                        type: 'PATCH',
+                        data: { tcSrvId: $("#message").data('issue').tcSrvId,
+                                nameId: $("#message").data('issue').nameId,
+                                branch: $("#message").data('issue').branch,
+                                issueType: $("#message").data('issue').issueType,
+                                jiraTicket: document.getElementById('jiraTicket').value,
+                                comment: document.getElementById('comment').value
+                              },
+                        error: function (jqXHR, exception) {
+                            showErrInLoadStatus(jqXHR, exception);
+                        }
+                    });
+                $(this).dialog("close");
+                },
+            Unmute: function() {
+                    $.ajax({
+                        url: "/rest/board/unmuteIssue",
+                        type: 'DELETE',
+                        data: { tcSrvId: $("#message").data('issue').tcSrvId,
+                                nameId: $("#message").data('issue').nameId,
+                                branch: $("#message").data('issue').branch,
+                                issueType: $("#message").data('issue').issueType
+                                }
+                    });
+                    $(this).dialog("close");
+                },
+            Cancel: function() {
+                $(this).dialog("close");
+                }
+        }
+    });
+
     var g_shownDataHashCodeHex = "";
     let gVue, g_Loading, g_TcBotVersion;
 
@@ -59,43 +137,31 @@
             data: {
                 baseBranchSelected: '',
                 baseBranches: new Set(),
-                defects: [],
+                issues: [],
                 expanded: [],
                 headers: [
-                    {text: "Branch", value: 'branch'},
-                    {text: 'Tags', value: 'tags'},
-                    {text: 'Suites', value: 'suitesSummary'},
-                    {text: "Commits", value: 'blameCandidateSummary'},
-                    {text: "Issues", value: 'cntissues'},
-                    {text: "Fixed", value: 'cntfixedissues'},
-                    {text: "Ignored", value: 'cntignoredissues'},
-                    {text: "Not Fixed", value: 'cntfailingissues'},
-                    {text: "Unclear", value: 'cntunclearissues'}
+                    {text: "Name", value: 'name'},
+                    {text: 'Issue type', value: 'issueType'},
+                    {text: 'Branch', value: 'trackedBranch'},
+                    {text: "Ticket", value: 'jiraTicket'},
+                    {text: "Comment", value: 'comment'},
+                    {text: "User", value: 'userName'},
+                    {text: "Edit", value: 'edit'},
                 ]
             },
             methods: {
                 formChanged: function () {
                     loadDataSilent();
                 },
-
-                onResolve: function (id, force) {
-                    $.ajax({
-                        url: "/rest/defect/resolve",
-                        type: 'POST',
-                        data: { id: id, forceResolve: force } ,
-                        success: function (res) {
-                            window.alert("Resolved defect [" + id + "]");
-
-                            loadData();
-                        },
-                        error: showErrInLoadStatus
-                    });
-                }
+                editModal: function (item) {
+                    $('#message')
+                        .data('issue', item)
+                        .dialog("open");
+                    },
             }
         });
     }
 
-
     function parmsForRest() {
         var curReqParms = "";
         var branch = findGetParameter("branch");
@@ -117,14 +183,13 @@
         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();
+        var curFailuresUrl = "/rest/board/mutedIssues" + parmsForRest();
         $.ajax({
             url: curFailuresUrl,
             success: function (result) {
                 $("#loadStatus").html("");
 
                 showData(result);
-                g_shownDataHashCodeHex = isDefinedAndFilled(result.hashCodeHex) ? result.hashCodeHex : "";
 
                 g_Loading = false;
             },
@@ -163,13 +228,13 @@
         }
     }
 
-    function showData(result) {
-        gVue.$data.defects = result.defects;
+    function showData(issues) {
+        gVue.$data.issues = issues;
 
         branches = [];
 
-        result.defects.forEach(function (defect, index) {
-            branches.push(defect.trackedBranch)
+        issues.forEach(function (issue, index) {
+            branches.push(issue.trackedBranch)
         });
 
         branches.sort();
@@ -182,7 +247,7 @@
 </script>
 
 <div id="loadStatus"></div>
-<div id="vueQueryForm">
+<div id="vueQueryForm" class="h-25">
 
     <div class="formgroup">
         <span>Branch:</span>
@@ -195,96 +260,47 @@
         </select>
     </div>
 
-    <v-app id="queryForm">
-
+    <v-app id="queryForm" class="h-25">
 
         <v-data-table
                 :headers="headers"
-                :items="defects"
-                itrackedBranchtem-key="id"
-                class="elevation-1"
-                sort-by="branch"
-                :expanded.sync="expanded"
-                show-expand
+                :items="issues"
+                sort-by="name"
                 dense
+                multi-sort
         >
-            <!-- 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>
-
-                    Tests affected:
-                    <table>
-                        <tr v-for="(issue) in item.allIssues">
-                            <td v-if="issue.status === 'FIXED'">
-                                Fixed
+            <template v-slot:item="{ headers, item }">
+                        <tr>
+                            <td style="width:20%">
+                                <a v-if="item.webUrl !== null && item.webUrl !== ''" :href="item.webUrl">
+                                    {{ item.name }}
+                                </a>
+                                <span v-else>
+                                {{ item.name }}
+                                </span>
                             </td>
-                            <td v-else-if="issue.status === 'FAILING'">
-                                Still Failing
+                            <td style="width:10%">
+                                {{ item.issueType }}
                             </td>
-                            <td v-else>
-                                {{ issue.status }}
+                            <td style="width:10%">
+                                {{ item.trackedBranch }}
                             </td>
-
-                            <td>{{ issue.issueType }}</td>
-                            <td>
-                                <a v-if="issue.webUrl != null" :href="issue.webUrl">
-                                    {{ issue.name }}
+                            <td style="width:15%">
+                                <a :href="item.jiraTicket">
+                                    {{ item.jiraTicket }}
                                 </a>
-                                <span v-else>
-                                {{ issue.name }}
-                                </span>
+                            </td>
+                            <td style="width:35%">
+                                {{ item.comment }}
+                            </td>
+                            <td style="width:5%">
+                                {{ item.userName }}
+                            </td>
+                            <td style="width:5%">
+                                <button class="editModal" v-on:click="editModal(item)">Edit</button>
                             </td>
                         </tr>
-                    </table>
-
-                    <button v-if="item.cntFailingIssues === 0" v-on:click="onResolve(item.id, false)">Resolve</button>
-                    <button v-if="item.forceResolveAllowed === true" v-on:click="onResolve(item.id, true)" class='disabledbtn'>Force resolve</button>
-                </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.cntFixedIssues="{ item }">
-                <span class='visaStage' style="background: #12AD5E"
-                      v-if="item.cntFixedIssues!=0"
-                      :title="item.summaryFixedIssues">  {{ item.cntFixedIssues }} </span>
-            </template>
-            <template v-slot:item.cntIgnoredIssues="{ item }">
-                <span class='visaStage' style="background: darkorange"
-                      v-if="item.cntIgnoredIssues!=0"
-                      :title="item.summaryIgnoredIssues"> {{ item.cntIgnoredIssues }} </span>
-            </template>
-            <template v-slot:item.cntFailingIssues="{ item }" >
-                <span class='visaStage' style="background: red"
-                      v-if="item.cntFailingIssues!=0"
-                      :title="item.summaryFailingIssues">  {{ item.cntFailingIssues }} </span>
-            </template>
-
-            <template v-slot:item.cntUnclearIssues="{ item }" >
-                <span class='visaStage' style="background: grey"
-                      v-if="item.cntUnclearIssues!=0"
-                      :title="item.summaryUnclearIssues" >  {{ item.cntUnclearIssues }} </span>
-            </template>
-
         </v-data-table>
 
     </v-app>
@@ -293,4 +309,4 @@
 <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
+</html>
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 657de21..cc0ed61 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,12 +21,12 @@ import com.google.inject.AbstractModule;
 import com.google.inject.internal.SingletonScope;
 import org.apache.ignite.tcbot.common.TcBotCommonModule;
 import org.apache.ignite.tcbot.engine.board.BoardService;
+import org.apache.ignite.tcbot.engine.boardmute.MutedIssuesDao;
 import org.apache.ignite.tcbot.engine.build.SingleBuildResultsService;
 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.newtests.NewTestsStorage;
 import org.apache.ignite.tcbot.engine.tracked.IDetailedStatusForTrackedBranch;
 import org.apache.ignite.tcbot.engine.tracked.TrackedBranchChainsProcessor;
 import org.apache.ignite.tcbot.engine.user.IUserStorage;
@@ -50,7 +50,7 @@ public class TcBotEngineModule extends AbstractModule {
 
         bind(IUserStorage.class).to(UserAndSessionsStorage.class).in(new SingletonScope());
 
-        bind(NewTestsStorage.class).in(new SingletonScope());
+        bind(MutedIssuesDao.class).in(new SingletonScope());
 
         install(new TcBotCommonModule());
     }
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/BoardService.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/BoardService.java
index 4e8001e..7144d7c 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/BoardService.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/BoardService.java
@@ -20,6 +20,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -29,7 +30,6 @@ import java.util.Set;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import javax.annotation.Nullable;
 import javax.inject.Inject;
@@ -43,6 +43,8 @@ import org.apache.ignite.ci.user.TcHelperUser;
 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.boardmute.MutedIssuesDao;
+import org.apache.ignite.tcbot.engine.boardmute.MutedIssueKey;
 import org.apache.ignite.tcbot.engine.chain.BuildChainProcessor;
 import org.apache.ignite.tcbot.engine.chain.SingleBuildRunCtx;
 import org.apache.ignite.tcbot.engine.conf.ITcBotConfig;
@@ -58,16 +60,22 @@ import org.apache.ignite.tcbot.engine.ui.BoardDefectSummaryUi;
 import org.apache.ignite.tcbot.engine.ui.BoardSummaryUi;
 import org.apache.ignite.tcbot.engine.ui.DsSuiteUi;
 import org.apache.ignite.tcbot.engine.ui.DsTestFailureUi;
+import org.apache.ignite.tcbot.engine.ui.MutedIssueUi;
 import org.apache.ignite.tcbot.engine.user.IUserStorage;
 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.tcbot.engine.boardmute.MutedIssueInfo;
 import org.apache.ignite.tcignited.build.FatBuildDao;
 import org.apache.ignite.tcignited.build.ITest;
 import org.apache.ignite.tcignited.creds.ICredentialsProv;
 import org.apache.ignite.tcignited.history.IRunHistory;
 
+import static java.util.stream.Collectors.toList;
+import static org.apache.ignite.tcbot.engine.board.IssueResolveStatus.BOT_MUTED;
+import static org.apache.ignite.tcbot.engine.boardmute.MutedIssueKey.parseName;
+import static org.apache.ignite.tcbot.engine.issue.IssueType.convertDisplayName;
 import static org.apache.ignite.tcignited.history.RunStatus.RES_MISSING;
 import static org.apache.ignite.tcignited.history.RunStatus.RES_OK;
 
@@ -75,6 +83,7 @@ public class BoardService {
     @Inject IIssuesStorage issuesStorage;
     @Inject FatBuildDao fatBuildDao;
     @Inject ChangeDao changeDao;
+    @Inject MutedIssuesDao mutedIssuesDao;
     @Inject ITeamcityIgnitedProvider tcProv;
     @Inject DefectsStorage defectStorage;
     @Inject IScheduler scheduler;
@@ -126,7 +135,7 @@ public class BoardService {
                 List<Future<FatBuildCompacted>> futures = buildChainProcessor.replaceWithRecent(fatBuild, allBuildsMap, tcIgn);
 
                 Stream<FatBuildCompacted> results = FutureUtil.getResults(futures);
-                List<FatBuildCompacted> freshRebuild = results.collect(Collectors.toList());
+                List<FatBuildCompacted> freshRebuild = results.collect(toList());
 
                 Optional<FatBuildCompacted> rebuild;
 
@@ -138,6 +147,17 @@ public class BoardService {
                     BoardDefectIssueUi issueUi = processIssue(tcIgn, rebuild, issue, firstBuild.buildTypeId());
                     if (issueUi.status() != IssueResolveStatus.FIXED)
                         defectUi.addTags(tags);
+
+                    issueUi.setTcSrvId(next.tcSrvId());
+
+                    MutedIssueKey issueKey = new MutedIssueKey(next.tcSrvId(), issue.testNameCid(),
+                        fatBuild.branchName(), IssueType.valueOf(compactor.getStringFromId(issue.issueTypeCode())));
+
+                    MutedIssueInfo mutedIssueInfo = mutedIssuesDao.getMutedIssue(issueKey);
+
+                    if (mutedIssueInfo != null)
+                        issueUi.setStatus(BOT_MUTED);
+
                     defectUi.addIssue(issueUi);
                 }
             }
@@ -192,8 +212,10 @@ public class BoardService {
             if (testResult.isPresent()) {
                 ITest test = testResult.get();
 
-                if (test.isIgnoredTest() || test.isMutedTest())
+                if (test.isIgnoredTest())
                     status = IssueResolveStatus.IGNORED;
+                else if (test.isMutedTest())
+                    status = IssueResolveStatus.TC_MUTED;
                 else if (IssueType.newTestWithHighFlakyRate.code().equals(issueType)) {
                     int fullSuiteNameAndFullTestName = issue.testNameCid();
 
@@ -377,4 +399,124 @@ public class BoardService {
 
         defectStorage.save(defect);
     }
+
+    public void muteIssue(
+        int tcSrvId,
+        int nameId,
+        String branch,
+        String trackedBranch,
+        String issueType,
+        String jiraTicket,
+        String comment,
+        String userName,
+        String webUrl) {
+
+        if (branch == null || trackedBranch == null || issueType == null|| jiraTicket == null||
+                comment == null || userName == null || webUrl == null ||
+                userName.isEmpty() ||
+                jiraTicket.length() > 100 || comment.length() > 500 ||
+                userName.length() > 100 || webUrl.length() > 250
+        )
+            throw new IllegalArgumentException(String.format("branch: %s, trackedBranch: %s, issueType: %s, jiraTicket: %s, " +
+                "comment: %s, userName: %s, webUrl: %s, ",
+                String.valueOf(branch), String.valueOf(trackedBranch), String.valueOf(issueType), String.valueOf(jiraTicket),
+                String.valueOf(comment), String.valueOf(userName), String.valueOf(webUrl)));
+
+        Integer branchId = compactor.getStringIdIfPresent(branch);
+
+        if (branchId <= 0)
+            throw new IllegalArgumentException("There is no id in the stringsCache for string: \"" + branch + "\"");
+
+        MutedIssueKey issueKey = new MutedIssueKey(tcSrvId, nameId, branchId, IssueType.valueOf(issueType));
+
+        if (mutedIssuesDao.getMutedIssue(issueKey) == null) {
+            Integer trackedBranchId = compactor.getStringIdIfPresent(trackedBranch);
+
+            if (trackedBranchId <= 0)
+                throw new IllegalArgumentException("There is no id in the stringsCache for string: \"" + trackedBranch + "\"");
+
+            MutedIssueInfo issueInfo = new MutedIssueInfo(trackedBranchId, userName, jiraTicket, comment, webUrl);
+
+            mutedIssuesDao.putIssue(issueKey, issueInfo);
+        }
+    }
+
+    public void updateIssue(
+        int tcSrvId,
+        int nameId,
+        String branch,
+        String issueType,
+        String jiraTicket,
+        String comment) {
+
+        if (branch == null || issueType == null|| jiraTicket == null|| comment == null ||
+            jiraTicket.length() > 100 || comment.length() > 500
+        )
+            throw new IllegalArgumentException(String.format("branch: %s, issueType: %s, jiraTicket: %s, comment: %s",
+                String.valueOf(branch), String.valueOf(issueType), String.valueOf(jiraTicket), String.valueOf(comment)));
+
+        Integer branchId = compactor.getStringIdIfPresent(branch);
+
+        if (branchId <= 0)
+            throw new IllegalArgumentException("There is no id in the stringsCache for string: \"" + branch + "\"");
+
+        MutedIssueKey issueKey = new MutedIssueKey(tcSrvId, nameId, branchId, convertDisplayName(issueType));
+
+        MutedIssueInfo issueInfo = mutedIssuesDao.getMutedIssue(issueKey);
+
+        if (issueInfo != null) {
+            issueInfo.setJiraTicket(jiraTicket);
+
+            issueInfo.setComment(comment);
+
+            mutedIssuesDao.putIssue(issueKey, issueInfo);
+        }
+    }
+
+    public void unmuteIssue(
+        int tcSrvId,
+        int nameId,
+        String branch,
+        String issueType) {
+        MutedIssueKey issueKey = new MutedIssueKey(tcSrvId, nameId, compactor.getStringId(branch), convertDisplayName(issueType));
+
+        mutedIssuesDao.removeIssue(issueKey);
+    }
+
+    public Collection<MutedIssueUi> getAllMutedIssues(String baseBranch) {
+        return mutedIssuesDao.getAllMutedIssues().entrySet().stream()
+            .map(entry -> {
+                MutedIssueKey key = entry.getKey();
+                MutedIssueInfo value = entry.getValue();
+
+                MutedIssueUi issueUi = new MutedIssueUi();
+
+                issueUi.tcSrvId = key.getTcSrvId();
+                issueUi.nameId = key.getNameId();
+                issueUi.branch = compactor.getStringFromId(key.branchNameId());
+                issueUi.trackedBranch = compactor.getStringFromId(value.getTrackedBranchId());
+                issueUi.issueType = key.getIssueType().displayName();
+                issueUi.userName = value.getUserName();
+                issueUi.jiraTicket = value.getJiraTicket();
+                issueUi.comment = value.getComment();
+                issueUi.webUrl = value.getWebUrl();
+
+                if (key.getIssueType() == IssueType.newCriticalFailure
+                        || key.getIssueType() == IssueType.newTrustedSuiteFailure)
+                    issueUi.name = compactor.getStringFromId(key.getNameId());
+                else
+                    issueUi.name = parseName(compactor.getStringFromId(key.getNameId()));
+
+                return issueUi;
+            })
+            .filter(issue -> {
+                if (baseBranch == null || baseBranch.equals("") ||  issue.trackedBranch.equals(baseBranch))
+                    return true;
+                else
+                    return false;
+
+            })
+            .sorted(Comparator.comparing(MutedIssueUi::getName).thenComparing(MutedIssueUi::getIssueType))
+            .collect(toList());
+    }
 }
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/IssueResolveStatus.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/IssueResolveStatus.java
index d76fac9..c7c4393 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/IssueResolveStatus.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/board/IssueResolveStatus.java
@@ -17,5 +17,5 @@
 package org.apache.ignite.tcbot.engine.board;
 
 public enum IssueResolveStatus {
-    FIXED, FAILING, IGNORED, UNKNOWN
+    FIXED, FAILING, IGNORED, TC_MUTED, BOT_MUTED, UNKNOWN
 }
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/boardmute/MutedIssueInfo.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/boardmute/MutedIssueInfo.java
new file mode 100644
index 0000000..d346dbd
--- /dev/null
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/boardmute/MutedIssueInfo.java
@@ -0,0 +1,78 @@
+/*
+ * 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.boardmute;
+
+public class MutedIssueInfo {
+    private int trackedBranchId;
+
+    private String userName;
+
+    private String jiraTicket;
+
+    private String comment;
+
+    private String webUrl;
+
+    public MutedIssueInfo(int trackedBranchId, String userName, String jiraTicket, String comment, String webUrl) {
+        this.trackedBranchId = trackedBranchId;
+        this.userName = userName;
+        this.jiraTicket = jiraTicket;
+        this.comment = comment;
+        this.webUrl = webUrl;
+    }
+
+    public int getTrackedBranchId() {
+        return trackedBranchId;
+    }
+
+    public void setTrackedBranchId(int trackedBranchId) {
+        this.trackedBranchId = trackedBranchId;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getJiraTicket() {
+        return jiraTicket;
+    }
+
+    public void setJiraTicket(String jiraTicket) {
+        this.jiraTicket = jiraTicket;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    public String getWebUrl() {
+        return webUrl;
+    }
+
+    public void setWebUrl(String webUrl) {
+        this.webUrl = webUrl;
+    }
+}
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/boardmute/MutedIssueKey.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/boardmute/MutedIssueKey.java
new file mode 100644
index 0000000..9c7085e
--- /dev/null
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/boardmute/MutedIssueKey.java
@@ -0,0 +1,87 @@
+/*
+ * 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.boardmute;
+
+import com.google.common.base.Strings;
+import org.apache.ignite.tcbot.engine.issue.IssueType;
+import org.apache.ignite.tcbot.engine.ui.ShortTestFailureUi;
+
+public class MutedIssueKey {
+    private int tcSrvId;
+
+    private int nameId;
+
+    private int branchId;
+
+    private IssueType issueType;
+
+    public MutedIssueKey(int tcSrvId, int nameId, int branchId, IssueType issueType) {
+        this.tcSrvId = tcSrvId;
+        this.nameId = nameId;
+        this.branchId = branchId;
+        this.issueType = issueType;
+    }
+
+    public int getTcSrvId() {
+        return tcSrvId;
+    }
+
+    public void setTcSrvId(int tcSrvId) {
+        this.tcSrvId = tcSrvId;
+    }
+
+    public int getNameId() {
+        return nameId;
+    }
+
+    public void setNameId(int nameId) {
+        this.nameId = nameId;
+    }
+
+    public int branchNameId() {
+        return branchId;
+    }
+
+    public void setBranchId(int branchId) {
+        this.branchId = branchId;
+    }
+
+    public IssueType getIssueType() {
+        return issueType;
+    }
+
+    public void setIssueType(IssueType issueType) {
+        this.issueType = issueType;
+    }
+
+    public static String parseName(String name) {
+        String suiteName = null, testName = null;
+
+        String[] split = Strings.nullToEmpty(name).split("\\:");
+        if (split.length >= 2) {
+            suiteName = ShortTestFailureUi.extractSuite(split[0]);
+            testName = ShortTestFailureUi.extractTest(split[1]);
+        }
+
+        if (testName != null && suiteName != null)
+            return suiteName + ":" + testName;
+
+        return name;
+
+    }
+}
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/boardmute/MutedIssuesDao.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/boardmute/MutedIssuesDao.java
new file mode 100644
index 0000000..9054c02
--- /dev/null
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/boardmute/MutedIssuesDao.java
@@ -0,0 +1,60 @@
+/*
+ * 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.boardmute;
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.tcbot.persistence.CacheConfigs;
+
+/**
+ *
+ */
+public class MutedIssuesDao {
+    /** Cache name. */
+    private static final String BOARD_MUTE_CACHE_NAME = "mutedIssues";
+
+    /** Ignite provider. */
+    @Inject private Provider<Ignite> igniteProvider;
+
+    /** */
+    public IgniteCache<MutedIssueKey, MutedIssueInfo> cache() {
+        return igniteProvider.get().getOrCreateCache(CacheConfigs.getCache8PartsConfig(BOARD_MUTE_CACHE_NAME));
+    }
+
+    public void putIssue(MutedIssueKey issueKey, MutedIssueInfo issueInfo) {
+        cache().put(issueKey, issueInfo);
+    }
+
+    public void removeIssue(MutedIssueKey issueKey) {
+        cache().remove(issueKey);
+    }
+
+    public MutedIssueInfo getMutedIssue(MutedIssueKey issueKey) {
+        return cache().get(issueKey);
+    }
+
+    public Map<MutedIssueKey, MutedIssueInfo> getAllMutedIssues() {
+        Map<MutedIssueKey, MutedIssueInfo> issues = new HashMap<>();
+        cache().forEach(e -> issues.put(e.getKey(), e.getValue()));
+        return issues;
+    }
+}
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IssueType.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IssueType.java
index 25171c4..7ba27b1 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IssueType.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/issue/IssueType.java
@@ -44,6 +44,25 @@ public enum IssueType {
     /** Display name. */
     private final String displayName;
 
+    static public IssueType convertDisplayName(String displayName) {
+        switch (displayName) {
+            case "New test failure":
+                return newFailure;
+            case "Recently contributed test failed":
+                return newContributedTestFailure;
+            case "New stable failure of a flaky test":
+                return newFailureForFlakyTest;
+            case "New Critical Failure":
+                return newCriticalFailure;
+            case "New Trusted Suite failure":
+                return newTrustedSuiteFailure;
+            case "Test with high flaky rate":
+                return newTestWithHighFlakyRate;
+        }
+
+        throw new IllegalArgumentException("Illegal issue type: " + displayName);
+    }
+
     /**
      * @param code Code.
      * @param displayName Display name.
@@ -66,4 +85,5 @@ public enum IssueType {
     public String displayName() {
         return displayName;
     }
+
 }
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectIssueUi.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectIssueUi.java
index ee05ba1..b005147 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectIssueUi.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectIssueUi.java
@@ -31,6 +31,7 @@ public class BoardDefectIssueUi {
     private transient IStringCompactor compactor;
     private transient DefectIssue issue;
     private boolean suiteProblem;
+    private int tcSrvId;
     @Nullable
     private String webUrl;
     private IssueResolveStatus status;
@@ -66,6 +67,10 @@ public class BoardDefectIssueUi {
 
     }
 
+    public int getNameId() {
+        return issue.testNameCid();
+    }
+
     public String getIssueType() {
         return compactor.getStringFromId(issue.issueTypeCode());
     }
@@ -78,8 +83,20 @@ public class BoardDefectIssueUi {
         return status;
     }
 
+    public void setStatus(IssueResolveStatus status) {
+        this.status = status;
+    }
+
     @Nullable
     public String getWebUrl() {
         return webUrl;
     }
+
+    public int getTcSrvId() {
+        return tcSrvId;
+    }
+
+    public void setTcSrvId(int tcSrvId) {
+        this.tcSrvId = tcSrvId;
+    }
 }
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectSummaryUi.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectSummaryUi.java
index 035aefe..7b99cbd 100644
--- a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectSummaryUi.java
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/BoardDefectSummaryUi.java
@@ -135,8 +135,15 @@ public class BoardDefectSummaryUi {
         return issuesList.size();
     }
 
-    private Stream<BoardDefectIssueUi> issues(IssueResolveStatus type) {
-        return issuesList.stream().filter(iss -> iss.status() == type);
+    private Stream<BoardDefectIssueUi> issues(IssueResolveStatus... types) {
+        return issuesList.stream().filter(iss -> {
+            for (IssueResolveStatus type : types) {
+                if (iss.status() == type)
+                    return true;
+            }
+
+            return false;
+        });
     }
 
     public List<BoardDefectIssueUi> getIgnoredIssues() {
@@ -148,7 +155,7 @@ public class BoardDefectSummaryUi {
     }
 
     public Integer getCntIgnoredIssues() {
-        return (int) issues(IssueResolveStatus.IGNORED).count();
+        return (int) issues(IssueResolveStatus.IGNORED, IssueResolveStatus.TC_MUTED, IssueResolveStatus.BOT_MUTED).count();
     }
 
     public List<BoardDefectIssueUi> getFailingIssues() {
diff --git a/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/MutedIssueUi.java b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/MutedIssueUi.java
new file mode 100644
index 0000000..dba6e76
--- /dev/null
+++ b/tcbot-engine/src/main/java/org/apache/ignite/tcbot/engine/ui/MutedIssueUi.java
@@ -0,0 +1,120 @@
+/*
+ * 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;
+
+public class MutedIssueUi {
+    public int tcSrvId;
+
+    public int nameId;
+
+    public String name;
+
+    public String branch;
+
+    public String trackedBranch;
+
+    public String issueType;
+
+    public String userName;
+
+    public String jiraTicket;
+
+    public String comment;
+
+    public String webUrl;
+
+    public int getTcSrvId() {
+        return tcSrvId;
+    }
+
+    public void setTcSrvId(int tcSrvId) {
+        this.tcSrvId = tcSrvId;
+    }
+
+    public int getNameId() {
+        return nameId;
+    }
+
+    public void setNameId(int nameId) {
+        this.nameId = nameId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getBranch() {
+        return branch;
+    }
+
+    public void setBranch(String branch) {
+        this.branch = branch;
+    }
+
+    public String getTrackedBranch() {
+        return trackedBranch;
+    }
+
+    public void setTrackedBranch(String trackedBranch) {
+        this.trackedBranch = trackedBranch;
+    }
+
+    public String getIssueType() {
+        return issueType;
+    }
+
+    public void setIssueType(String issueType) {
+        this.issueType = issueType;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getJiraTicket() {
+        return jiraTicket;
+    }
+
+    public void setJiraTicket(String jiraTicket) {
+        this.jiraTicket = jiraTicket;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    public String getWebUrl() {
+        return webUrl;
+    }
+
+    public void setWebUrl(String webUrl) {
+        this.webUrl = webUrl;
+    }
+}
diff --git a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildlog/BuildLogProcessor.java b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildlog/BuildLogProcessor.java
index 58dbeed..67bf01e 100644
--- a/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildlog/BuildLogProcessor.java
+++ b/tcbot-teamcity-ignited/src/main/java/org/apache/ignite/tcignited/buildlog/BuildLogProcessor.java
@@ -78,7 +78,14 @@ class BuildLogProcessor implements IBuildLogProcessor {
                     logCheckResultCompacted = new LogCheckResultCompacted();
                 }
 
-                logCheckResultDao.put(teamcity.serverCode(), buildId, logCheckResultCompacted);
+                try {
+                    logCheckResultDao.put(teamcity.serverCode(), buildId, logCheckResultCompacted);
+                }
+                catch (Exception ex) {
+                    logger.error("serverCode: " + teamcity.serverCode() + "; buildId: " + buildId +
+                        "; logCheck: " + logCheckResultCompacted.toString());
+                    throw ex;
+                }
 
                 return logCheckResultCompacted;
             });