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/02/02 18:19:08 UTC

[ignite-teamcity-bot] branch master updated: IGNITE-11105 Supported Teamcity servers aliases - Fixes #111.

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 aee540e  IGNITE-11105 Supported Teamcity servers aliases - Fixes #111.
aee540e is described below

commit aee540ec6ce9882051b2f9b77a967637c5341bd1
Author: Dmitriy Pavlov <dp...@apache.org>
AuthorDate: Sat Feb 2 21:18:55 2019 +0300

    IGNITE-11105 Supported Teamcity servers aliases - Fixes #111.
    
    Signed-off-by: Dmitriy Pavlov <dp...@apache.org>
---
 conf/branches.json                                 |   6 +
 .../java/org/apache/ignite/ci/HelperConfig.java    |  45 +--
 .../apache/ignite/ci/IgnitePersistentTeamcity.java |  17 --
 .../apache/ignite/ci/IgniteTeamcityConnection.java |  49 ++--
 .../org/apache/ignite/ci/analysis/RunStat.java     |   7 -
 .../org/apache/ignite/ci/conf/PasswordEncoder.java |  10 +-
 .../org/apache/ignite/ci/di/cache/GuavaCached.java |   2 +
 .../ignite/ci/di/cache/GuavaCachedInterceptor.java |   8 +-
 .../ci/github/ignited/GitHubConnIgnitedImpl.java   |  11 +
 .../ci/github/ignited/IGitHubConnIgnited.java      |  17 +-
 .../ci/github/pure/GitHubConnectionImpl.java       |  25 +-
 .../ignite/ci/github/pure/IGitHubConnection.java   |  16 +-
 .../java/org/apache/ignite/ci/jira/Tickets.java    |   1 +
 .../ignite/ci/jira/ignited/IJiraIgnited.java       |  17 +-
 .../apache/ignite/ci/jira/ignited/JiraIgnited.java |  20 +-
 .../ignite/ci/jira/ignited/JiraTicketDao.java      |  18 +-
 .../ignite/ci/jira/ignited/JiraTicketSync.java     |  24 +-
 .../ignite/ci/jira/ignited/TicketCompacted.java    |  43 ++-
 .../apache/ignite/ci/jira/{ => pure}/Fields.java   |  14 +-
 .../ignite/ci/jira/pure/IJiraIntegration.java      |  24 +-
 .../java/org/apache/ignite/ci/jira/pure/Jira.java  |  86 +++---
 .../apache/ignite/ci/jira/{ => pure}/Status.java   |   4 +-
 .../apache/ignite/ci/jira/{ => pure}/Ticket.java   |   9 +-
 .../org/apache/ignite/ci/jobs/CheckQueueJob.java   |  24 +-
 .../ignite/ci/tcbot/chain/PrChainsProcessor.java   |   5 +-
 .../ci/tcbot/common/StringFieldCompacted.java      | 160 +++++++++++
 .../ci/{ => tcbot}/conf/BranchesTracked.java       |  52 +++-
 .../apache/ignite/ci/tcbot/conf/GitHubConfig.java  |  83 ++++++
 .../Fields.java => tcbot/conf/IGitHubConfig.java}  |  20 +-
 .../{ITcBotConfig.java => IJiraServerConfig.java}  |  40 ++-
 .../apache/ignite/ci/tcbot/conf/ITcBotConfig.java  |  12 +-
 .../conf/ITcServerConfig.java}                     |  27 +-
 .../ignite/ci/tcbot/conf/JiraServerConfig.java     | 144 ++++++++++
 .../ci/tcbot/conf/LocalFilesBasedConfig.java       |  54 +++-
 .../ignite/ci/tcbot/conf/TcServerConfig.java       | 102 +++++++
 .../ignite/ci/tcbot/visa/BranchTicketMatcher.java  | 302 +++++++++++++++++++++
 .../tcbot/visa/TcBotTriggerAndSignOffService.java  | 228 ++++------------
 .../ci/teamcity/ignited/ITeamcityIgnited.java      |   3 -
 .../teamcity/ignited/ITeamcityIgnitedProvider.java |   8 +
 .../teamcity/ignited/TcIgnitedCachingProvider.java |  32 ++-
 .../ci/teamcity/ignited/TeamcityIgnitedImpl.java   |   5 -
 .../teamcity/ignited/fatbuild/TestCompacted.java   |  29 +-
 .../ignite/ci/teamcity/pure/ITeamcityConn.java     |  40 ++-
 .../model/current/ChainAtServerCurrentStatus.java  |  32 +--
 .../ignite/ci/web/model/current/UpdateInfo.java    |   2 +-
 .../org/apache/ignite/ci/web/rest/GetBuildLog.java |   3 +-
 .../ignite/ci/web/rest/GetTrackedBranches.java     |  27 +-
 .../apache/ignite/ci/web/rest/TriggerBuilds.java   |   2 +-
 .../exception/ServiceUnauthorizedException.java    |   4 +-
 .../org/apache/ignite/ci/web/rest/login/Login.java |   7 +-
 .../ignite/ci/web/rest/pr/GetPrTestFailures.java   |   3 +-
 .../rest/tracked/GetTrackedBranchTestResults.java  |   3 +-
 .../ignite/ci/web/rest/visa/TcBotVisaService.java  |  19 +-
 ignite-tc-helper-web/src/main/webapp/js/prs-1.1.js |   2 +-
 ignite-tc-helper-web/src/main/webapp/prs.html      |  13 +-
 .../ci/tcbot/chain/MockBasedTcBotModule.java       |  35 ++-
 .../ci/tcbot/chain/TrackedBranchProcessorTest.java |   2 +-
 .../ignite/ci/tcbot/issue/IssueDetectorTest.java   |   2 +-
 .../ci/teamcity/ignited/TeamcityIgnitedMock.java   |   2 +-
 .../ignited/TeamcityIgnitedProviderMock.java       |  11 +-
 60 files changed, 1421 insertions(+), 591 deletions(-)

diff --git a/conf/branches.json b/conf/branches.json
index 0b0d9bf..05087a4 100644
--- a/conf/branches.json
+++ b/conf/branches.json
@@ -1,4 +1,10 @@
 {
+  "primaryServerCode": "apache",
+  "tcServers": [
+  ],
+  "jiraServers": [
+    {}
+  ],
   "branches": [
     {
       "id": "master",
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/HelperConfig.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/HelperConfig.java
index ebdb919..b1e4c85 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/HelperConfig.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/HelperConfig.java
@@ -25,8 +25,8 @@ import java.io.FileWriter;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.util.Properties;
-import javax.ws.rs.QueryParam;
-import org.apache.ignite.ci.conf.BranchesTracked;
+
+import org.apache.ignite.ci.tcbot.conf.BranchesTracked;
 import org.apache.ignite.ci.conf.PasswordEncoder;
 import org.apache.ignite.ci.tcbot.TcBotSystemProperties;
 import org.apache.ignite.ci.util.Base64Util;
@@ -45,8 +45,6 @@ public class HelperConfig {
     public static final String MAIL_PROPS = "mail.auth.properties";
     public static final String HOST = "host";
     public static final String USERNAME = "username";
-    @Deprecated
-    private static final String PASSWORD = "password";
     public static final String ENCODED_PASSWORD = "encoded_password";
 
     /** GitHub authorization token property name. */
@@ -61,13 +59,11 @@ public class HelperConfig {
     /** JIRA authorization token property name. */
     public static final String GIT_API_URL = "git.api_url";
 
-    /** JIRA authorization token property name. */
-    public static final String JIRA_API_URL = "jira.api_url";
-
     /** JIRA URL to build links to tickets. */
     public static final String JIRA_URL = "jira.url";
 
     /** Prefix for JIRA ticket names. */
+    @Deprecated
     public static final String JIRA_TICKET_TEMPLATE = "jira.ticket_template";
 
     /** Slack authorization token property name. */
@@ -94,7 +90,7 @@ public class HelperConfig {
                 writer.write(USERNAME + "=" + ENDL);
                 writer.write(ENCODED_PASSWORD + "=" + ENDL);
             }
-            throw new IllegalStateException("Please setup username and encodedPassword in config file [" +
+            throw new IllegalStateException("Please setup parameters for service in config file [" +
                 file.getCanonicalPath() + "]");
         }
         return loadProps(file);
@@ -178,26 +174,19 @@ public class HelperConfig {
      * Extract TeamCity authorization token from properties.
      *
      * @param props Properties, where token is placed.
-     * @param configName Configuration name.
+     * @param cfgName Configuration name.
      * @return Null or decoded auth token for Github.
      */
-    @Nullable static String prepareBasicHttpAuthToken(Properties props, String configName) {
-        final String user = getMandatoryProperty(props, USERNAME, configName);
-        String pwd = props.getProperty(PASSWORD);
-        boolean filled = !isNullOrEmpty(pwd);
+    @Nullable static String prepareBasicHttpAuthToken(Properties props, String cfgName) {
+        final String user = getMandatoryProperty(props, USERNAME, cfgName);
 
-        if (!filled) {
-            String enc = props.getProperty(ENCODED_PASSWORD);
+        String enc = props.getProperty(ENCODED_PASSWORD);
 
-            if(!isNullOrEmpty(enc)) {
-                pwd = PasswordEncoder.decode(enc);
-                filled = true;
-            }
-        }
-
-        if (!filled)
+        if (isNullOrEmpty(enc))
             return null;
 
+        String pwd = PasswordEncoder.decode(enc);
+
         return userPwdToToken(user, pwd);
     }
 
@@ -205,9 +194,9 @@ public class HelperConfig {
         return Base64Util.encodeUtf8String(user + ":" + pwd);
     }
 
-    public static String getMandatoryProperty(Properties props, String key, String configName) {
+    public static String getMandatoryProperty(Properties props, String key, String cfgName) {
         final String user = props.getProperty(key);
-        Preconditions.checkState(!isNullOrEmpty(user), key + " property should be filled in " + configName);
+        Preconditions.checkState(!isNullOrEmpty(user), key + " property should be filled in " + cfgName);
         return user;
     }
 
@@ -235,14 +224,6 @@ public class HelperConfig {
         }
     }
 
-
-    @NotNull public static File getLogsDirForServer(@QueryParam("serverId") String serverId) {
-        final File workDir = resolveWorkDir();
-        final String configName = prepareConfigName(serverId);
-        final Properties props = loadAuthProperties(workDir, configName);
-        return resolveLogs(workDir, props);
-    }
-
     @NotNull static File resolveLogs(File workDir, Properties props) {
         final String logsProp = props.getProperty(LOGS, "logs");
         final File logsDirFileConfigured = new File(logsProp);
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java
index 3d53e45..b40f24a 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java
@@ -79,10 +79,6 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea
     @Nullable
     private String serverId;
 
-    //todo: remove triggering dependency from getTrackedBranch processing, use TC Bot DB data.
-    @Deprecated
-    private static long lastTriggerMs = System.currentTimeMillis();
-
     @Override public void init(ITeamcity conn) {
         this.teamcity = conn;
         this.serverId = conn.serverId();
@@ -125,12 +121,6 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea
         return serverId;
     }
 
-    public static int getTriggerRelCacheValidSecs(int defaultSecs) {
-        long msSinceTrigger = System.currentTimeMillis() - lastTriggerMs;
-        long secondsSinceTrigger = TimeUnit.MILLISECONDS.toSeconds(msSinceTrigger);
-        return Math.min((int) secondsSinceTrigger, defaultSecs);
-    }
-
     @NotNull private String ignCacheNme(String cache) {
         return ignCacheNme(cache, serverId);
     }
@@ -226,8 +216,6 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea
     /** {@inheritDoc} */
     @AutoProfiling
     @Override public Build triggerBuild(String buildTypeId, @NotNull String branchName, boolean cleanRebuild, boolean queueAtTop) {
-        lastTriggerMs = System.currentTimeMillis();
-
         return teamcity.triggerBuild(buildTypeId, branchName, cleanRebuild, queueAtTop);
     }
 
@@ -241,11 +229,6 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea
         return teamcity.getProjects();
     }
 
-    /** {@inheritDoc} */
-    @Override public String gitBranchPrefix() {
-        return teamcity.gitBranchPrefix();
-    }
-
     @Override public Statistics getStatistics(int buildId) {
         return teamcity.getStatistics(buildId);
     }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityConnection.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityConnection.java
index c667d66..241d5d0 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityConnection.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityConnection.java
@@ -27,6 +27,8 @@ import org.apache.ignite.ci.analysis.LogCheckTask;
 import org.apache.ignite.ci.analysis.SingleBuildRunCtx;
 import org.apache.ignite.ci.di.AutoProfiling;
 import org.apache.ignite.ci.logs.BuildLogStreamChecker;
+import org.apache.ignite.ci.tcbot.conf.ITcBotConfig;
+import org.apache.ignite.ci.tcbot.conf.ITcServerConfig;
 import org.apache.ignite.ci.tcmodel.agent.Agent;
 import org.apache.ignite.ci.tcmodel.agent.AgentsRef;
 import org.apache.ignite.ci.tcmodel.changes.Change;
@@ -99,44 +101,35 @@ public class IgniteTeamcityConnection implements ITeamcity {
     /** Teamcity http connection. */
     @Inject private ITeamcityHttpConnection teamcityHttpConn;
 
+    @Inject private ITcBotConfig config;
 
-    private String configName; //main properties file name
     private String tcName;
 
     /** Build logger processing running. */
     private ConcurrentHashMap<Integer, CompletableFuture<LogCheckTask>> buildLogProcessingRunning = new ConcurrentHashMap<>();
 
-    /** Git branch prefix for seach TC runs in PR-less contributions. */
-    @NotNull
-    private String gitBranchPrefix;
-
     public Executor getExecutor() {
         return executor;
     }
 
     /** {@inheritDoc} */
-    public void init(@Nullable String tcName) {
+    @Override public void init(@Nullable String tcName) {
         this.tcName = tcName;
-        final File workDir = HelperConfig.resolveWorkDir();
 
-        this.configName = HelperConfig.prepareConfigName(tcName);
+        ITcServerConfig tcCfg = this.config.getTeamcityConfig(tcName);
+        final Properties props = tcCfg.properties();
 
-        final Properties props = HelperConfig.loadAuthProperties(workDir, configName);
-        final String hostConf = props.getProperty(HelperConfig.HOST, "https://ci.ignite.apache.org/");
-
-        this.host = hostConf.trim() + (hostConf.endsWith("/") ? "" : "/");
+        this.host = tcCfg.host();
 
         try {
             if (!Strings.isNullOrEmpty(props.getProperty(HelperConfig.USERNAME))
                     && props.getProperty(HelperConfig.ENCODED_PASSWORD) != null)
-                setAuthToken(HelperConfig.prepareBasicHttpAuthToken(props, configName));
+                setAuthToken(HelperConfig.prepareBasicHttpAuthToken(props, "TC Config"));
         } catch (Exception e) {
             e.printStackTrace();
             logger.error("Failed to set credentials", e);
         }
-
-        this.gitBranchPrefix = props.getProperty(HelperConfig.GIT_BRANCH_PREFIX, "ignite-");
-
+        final File workDir = HelperConfig.resolveWorkDir();
         final File logsDirFile = HelperConfig.resolveLogs(workDir, props);
 
         logsDir = ensureDirExist(logsDirFile);
@@ -154,7 +147,6 @@ public class IgniteTeamcityConnection implements ITeamcity {
         return basicAuthTok != null;
     }
 
-
     /** {@inheritDoc} */
     @AutoProfiling
     @Override public List<Agent> agents(boolean connected, boolean authorized) {
@@ -181,7 +173,7 @@ public class IgniteTeamcityConnection implements ITeamcity {
 
                 return file;
             }
-            String url = host + "downloadBuildLog.html" + "?buildId=" + buildIdStr + "&archived=true";
+            String url = host() + "downloadBuildLog.html" + "?buildId=" + buildIdStr + "&archived=true";
 
             try {
                 HttpUtil.sendGetCopyToFile(basicAuthTok, url, file);
@@ -249,7 +241,7 @@ public class IgniteTeamcityConnection implements ITeamcity {
             "    </properties>\n" +
             "</build>";
 
-        String url = host + "app/rest/buildQueue";
+        String url = host() + "app/rest/buildQueue";
 
         try {
             logger.info("Triggering build: buildTypeId={}, branchName={}, cleanRebuild={}, queueAtTop={}",
@@ -321,17 +313,12 @@ public class IgniteTeamcityConnection implements ITeamcity {
 
     /** {@inheritDoc} */
     @Override public List<Project> getProjects() {
-        return sendGetXmlParseJaxb(host + "app/rest/latest/projects", ProjectsList.class).projects();
-    }
-
-    /** {@inheritDoc} */
-    @Override public String gitBranchPrefix() {
-        return gitBranchPrefix;
+        return sendGetXmlParseJaxb(host() + "app/rest/latest/projects", ProjectsList.class).projects();
     }
 
     /** {@inheritDoc} */
     @Override public List<BuildType> getBuildTypes(String projectId) {
-        return sendGetXmlParseJaxb(host + "app/rest/latest/projects/" + projectId, Project.class)
+        return sendGetXmlParseJaxb(host() + "app/rest/latest/projects/" + projectId, Project.class)
             .getBuildTypesNonNull();
     }
 
@@ -360,7 +347,7 @@ public class IgniteTeamcityConnection implements ITeamcity {
     /** {@inheritDoc} */
     @AutoProfiling
     @Override public BuildTypeFull getBuildType(String buildTypeId) {
-        return sendGetXmlParseJaxb(host + "app/rest/latest/buildTypes/id:" +
+        return sendGetXmlParseJaxb(host() + "app/rest/latest/buildTypes/id:" +
             buildTypeId, BuildTypeFull.class);
     }
 
@@ -375,7 +362,7 @@ public class IgniteTeamcityConnection implements ITeamcity {
      * @param elem Element class.
      */
     private <T> T getJaxbUsingHref(String href, Class<T> elem) {
-        return sendGetXmlParseJaxb(host + (href.startsWith("/") ? href.substring(1) : href), elem);
+        return sendGetXmlParseJaxb(host() + (href.startsWith("/") ? href.substring(1) : href), elem);
     }
 
     /** {@inheritDoc} */
@@ -451,7 +438,7 @@ public class IgniteTeamcityConnection implements ITeamcity {
     @Override public List<BuildRef> getBuildRefsPage(String fullUrl, AtomicReference<String> outNextPage) {
         String relPath = "app/rest/latest/builds?locator=defaultFilter:false";
         String relPathSelected = Strings.isNullOrEmpty(fullUrl) ? relPath : fullUrl;
-        String url = host + (relPathSelected.startsWith("/") ? relPathSelected.substring(1) : relPathSelected);
+        String url = host() + (relPathSelected.startsWith("/") ? relPathSelected.substring(1) : relPathSelected);
         Builds builds = sendGetXmlParseJaxb(url, Builds.class);
 
         outNextPage.set(Strings.emptyToNull(builds.nextHref()));
@@ -463,7 +450,7 @@ public class IgniteTeamcityConnection implements ITeamcity {
     @Override public SortedSet<MuteInfo> getMutesPage(String buildTypeId, String fullUrl, AtomicReference<String> nextPage) {
         String relPath = "app/rest/mutes?locator=project:(id:" + buildTypeId + ')';
         String relPathSelected = Strings.isNullOrEmpty(fullUrl) ? relPath : fullUrl;
-        String url = host + (relPathSelected.startsWith("/") ? relPathSelected.substring(1) : relPathSelected);
+        String url = host() + (relPathSelected.startsWith("/") ? relPathSelected.substring(1) : relPathSelected);
 
         Mutes mutes = sendGetXmlParseJaxb(url, Mutes.class);
 
@@ -476,7 +463,7 @@ public class IgniteTeamcityConnection implements ITeamcity {
     @AutoProfiling
     @Override public TestOccurrencesFull getTestsPage(int buildId, @Nullable String href, boolean testDtls) {
         String relPathSelected = Strings.isNullOrEmpty(href) ? testsStartHref(buildId, testDtls) : href;
-        String url = host + (relPathSelected.startsWith("/") ? relPathSelected.substring(1) : relPathSelected);
+        String url = host() + (relPathSelected.startsWith("/") ? relPathSelected.substring(1) : relPathSelected);
         return sendGetXmlParseJaxb(url, TestOccurrencesFull.class);
     }
 
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/RunStat.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/RunStat.java
index ed035eb..60187e6 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/RunStat.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/analysis/RunStat.java
@@ -73,13 +73,6 @@ public class RunStat implements IRunHistory {
         this.name = name;
     }
 
-    private ChangesState changesStatus(Boolean changesExist) {
-        if (changesExist == null)
-            return ChangesState.UNKNOWN;
-
-        return changesExist ? ChangesState.EXIST : ChangesState.NONE;
-    }
-
     public void addTestRunToLatest(TestOccurrence testOccurrence, ChangesState changesState) {
         TestId id = extractFullId(testOccurrence.getId());
         if (id == null) {
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/conf/PasswordEncoder.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/conf/PasswordEncoder.java
index 97788d9..c1769b1 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/conf/PasswordEncoder.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/conf/PasswordEncoder.java
@@ -89,7 +89,7 @@ public class PasswordEncoder {
         return parseHexBinary(Strings.padStart(Integer.toHexString(length), 2, '0'));
     }
 
-    public static void main(String[] args) {
+    public static void main0(String[] args) {
         String pass = "324aadfe23....";
         String encode = encode(pass);
         System.err.println("Encoded: " +  HelperConfig.GITHUB_AUTH_TOKEN + "=" + encode);
@@ -105,8 +105,12 @@ public class PasswordEncoder {
         Preconditions.checkState(decode.equals(pass));
     }
 
-    public static void main3(String[] args) {
-        String tok = HelperConfig.userPwdToToken("ignitetcbot", "222");
+    public static void main(String[] args) {
+        encodeJiraTok("ignitetcbot", "21313");
+    }
+
+    public static void encodeJiraTok(String user, String pwd) {
+        String tok = HelperConfig.userPwdToToken(user, pwd);
         String encode = encode(tok);
         System.err.println("Encoded: " +  HelperConfig.JIRA_AUTH_TOKEN + "=" + encode);
         String decode = decode(encode);
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/di/cache/GuavaCached.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/di/cache/GuavaCached.java
index 628d3fe..4913f60 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/di/cache/GuavaCached.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/di/cache/GuavaCached.java
@@ -45,4 +45,6 @@ public @interface GuavaCached {
     boolean cacheNegativeNumbersRval() default true;
 
     long expireAfterAccessSecs() default -1;
+
+    long expireAfterWriteSecs() default -1;
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/di/cache/GuavaCachedInterceptor.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/di/cache/GuavaCachedInterceptor.java
index adca7fc..5acac88 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/di/cache/GuavaCachedInterceptor.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/di/cache/GuavaCachedInterceptor.java
@@ -45,12 +45,16 @@ public class GuavaCachedInterceptor implements MethodInterceptor {
             if(annotation.softValues())
                 builder = builder.softValues();
 
-            if(annotation.maximumSize()>0)
+            if(annotation.maximumSize() > 0)
                 builder = builder.maximumSize(annotation.maximumSize());
 
-            if(annotation.expireAfterAccessSecs()>0)
+            if (annotation.expireAfterAccessSecs() > 0)
                 builder.expireAfterAccess(annotation.expireAfterAccessSecs(), TimeUnit.SECONDS);
 
+            if (annotation.expireAfterWriteSecs() > 0)
+                builder.expireAfterWrite(annotation.expireAfterWriteSecs(), TimeUnit.SECONDS);
+
+
             return builder.build();
         });
 
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/ignited/GitHubConnIgnitedImpl.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/ignited/GitHubConnIgnitedImpl.java
index dcbf1f4..769cb3e 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/ignited/GitHubConnIgnitedImpl.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/ignited/GitHubConnIgnitedImpl.java
@@ -36,6 +36,7 @@ import org.apache.ignite.ci.di.MonitoredTask;
 import org.apache.ignite.ci.di.scheduler.IScheduler;
 import org.apache.ignite.ci.github.PullRequest;
 import org.apache.ignite.ci.github.pure.IGitHubConnection;
+import org.apache.ignite.ci.tcbot.conf.IGitHubConfig;
 import org.jetbrains.annotations.NotNull;
 
 /**
@@ -85,6 +86,16 @@ class GitHubConnIgnitedImpl implements IGitHubConnIgnited {
     }
 
     /** {@inheritDoc} */
+    @Override public String gitBranchPrefix() {
+        return config().gitBranchPrefix();
+    }
+    /** {@inheritDoc} */
+    @Override
+    public IGitHubConfig config() {
+        return conn.config();
+    }
+
+    /** {@inheritDoc} */
     @AutoProfiling
     @Override public List<PullRequest> getPullRequests() {
         scheduler.sheduleNamed(taskName("actualizePrs"), this::actualizePrs, 2, TimeUnit.MINUTES);
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/ignited/IGitHubConnIgnited.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/ignited/IGitHubConnIgnited.java
index c61619b..ae405f8 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/ignited/IGitHubConnIgnited.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/ignited/IGitHubConnIgnited.java
@@ -16,17 +16,17 @@
  */
 package org.apache.ignite.ci.github.ignited;
 
-import com.google.common.base.Preconditions;
 import java.util.List;
-import javax.annotation.Nullable;
-import org.apache.ignite.ci.di.AutoProfiling;
 import org.apache.ignite.ci.github.PullRequest;
-import org.apache.ignite.ci.github.pure.IGitHubConnection;
+import org.apache.ignite.ci.tcbot.conf.IGitHubConfig;
+import org.apache.ignite.ci.tcbot.conf.IJiraServerConfig;
 
 /**
  *
  */
 public interface IGitHubConnIgnited {
+    IGitHubConfig config();
+
     /**
      * @return list of open pull requests
      */
@@ -40,7 +40,12 @@ public interface IGitHubConnIgnited {
      *
      * @param url Url.
      * @param body Request body.
-     * @return {@code True} - if GitHub was notified. {@code False} - otherwise.
      */
-    void notifyGit(String url, String body);
+    public void notifyGit(String url, String body);
+
+    /**
+     * Prefix to be added to git branch instead of {@link IJiraServerConfig#branchNumPrefix()}.
+     * Usually it is a lower case of JIRA branch mention, e.. JIRA branch num is 'IGNITE-', and git is 'ignite-'
+     */
+    public String gitBranchPrefix();
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/pure/GitHubConnectionImpl.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/pure/GitHubConnectionImpl.java
index f3f177c..da392dc 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/pure/GitHubConnectionImpl.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/pure/GitHubConnectionImpl.java
@@ -34,11 +34,15 @@ import java.util.concurrent.atomic.AtomicReference;
 import org.apache.ignite.ci.HelperConfig;
 import org.apache.ignite.ci.di.AutoProfiling;
 import org.apache.ignite.ci.github.PullRequest;
+import org.apache.ignite.ci.tcbot.conf.IGitHubConfig;
+import org.apache.ignite.ci.tcbot.conf.ITcBotConfig;
 import org.apache.ignite.ci.util.HttpUtil;
 import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
+
 import static com.google.common.base.Strings.isNullOrEmpty;
 
 class GitHubConnectionImpl implements IGitHubConnection {
@@ -51,6 +55,11 @@ class GitHubConnectionImpl implements IGitHubConnection {
     /** GitHub authorization token. */
     private String gitAuthTok;
 
+    @Inject
+    ITcBotConfig config;
+
+    private String srvCode;
+
     @Nullable public static String parseNextLinkFromLinkRspHeader(String s) {
         String nextLink = null;
         StringTokenizer tokenizer = new StringTokenizer(s, ",");
@@ -81,15 +90,18 @@ class GitHubConnectionImpl implements IGitHubConnection {
     }
 
     /** {@inheritDoc} */
-    @Override public void init(String srvId) {
+    @Override
+    public void init(String srvCode) {
+        this.srvCode = srvCode;
         final File workDir = HelperConfig.resolveWorkDir();
 
-        String cfgName = HelperConfig.prepareConfigName(srvId);
+        String cfgName = HelperConfig.prepareConfigName(srvCode);
 
         final Properties props = HelperConfig.loadAuthProperties(workDir, cfgName);
 
-        gitAuthTok = (HelperConfig.prepareGithubHttpAuthToken(props));
-        gitApiUrl = (props.getProperty(HelperConfig.GIT_API_URL));
+        gitAuthTok = HelperConfig.prepareGithubHttpAuthToken(props);
+        gitApiUrl = props.getProperty(HelperConfig.GIT_API_URL);
+
     }
 
     /** {@inheritDoc} */
@@ -171,4 +183,9 @@ class GitHubConnectionImpl implements IGitHubConnection {
             throw new UncheckedIOException(e);
         }
     }
+
+    /** {@inheritDoc} */
+    @Override  public IGitHubConfig config() {
+        return config.getGitConfig(srvCode);
+    }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/pure/IGitHubConnection.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/pure/IGitHubConnection.java
index b2ab22a..733c057 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/pure/IGitHubConnection.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/pure/IGitHubConnection.java
@@ -19,14 +19,17 @@ package org.apache.ignite.ci.github.pure;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
+
+import com.google.common.base.Strings;
 import org.apache.ignite.ci.github.PullRequest;
+import org.apache.ignite.ci.tcbot.conf.IGitHubConfig;
 import org.jetbrains.annotations.Nullable;
 
 /**
  *
  */
 public interface IGitHubConnection {
-    void init(String srvId);
+    void init(String srvCode);
 
     /** */
     PullRequest getPullRequest(Integer id);
@@ -55,7 +58,7 @@ public interface IGitHubConnection {
     /**
      * @return PR id from string "pull/XXXX/head"
      */
-    @Nullable public static Integer convertBranchToId(String branchForTc) {
+    @Nullable public static Integer convertBranchToPrId(String branchForTc) {
         Integer res = null;
 
         if (Objects.isNull(branchForTc))
@@ -73,11 +76,8 @@ public interface IGitHubConnection {
             }
         }
 
-        try {
-            res = Integer.parseInt(id);
-        }
-        finally {
-            return res;
-        }
+        return Strings.isNullOrEmpty(id) ? null : Integer.parseInt(id);
     }
+
+    IGitHubConfig config();
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Tickets.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Tickets.java
index 930a1bf..a6e3758 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Tickets.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Tickets.java
@@ -19,6 +19,7 @@ package org.apache.ignite.ci.jira;
 
 import java.util.Collection;
 import java.util.Collections;
+import org.apache.ignite.ci.jira.pure.Ticket;
 
 /**
  * See example of GSON here
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/IJiraIgnited.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/IJiraIgnited.java
index be1011a..6288d43 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/IJiraIgnited.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/IJiraIgnited.java
@@ -18,25 +18,14 @@ package org.apache.ignite.ci.jira.ignited;
 
 import java.io.IOException;
 import java.util.Set;
-import org.apache.ignite.ci.jira.Ticket;
-import org.jetbrains.annotations.NotNull;
+import org.apache.ignite.ci.jira.pure.Ticket;
+import org.apache.ignite.ci.tcbot.conf.IJiraServerConfig;
 
 /**
  *
  */
 public interface IJiraIgnited {
     /**
-     *
-     */
-    @NotNull public String ticketPrefix();
-
-    /**
-     *
-     */
-    @NotNull public String projectName();
-
-
-    /**
      * @return Jira tickets.
      */
     public Set<Ticket> getTickets();
@@ -62,4 +51,6 @@ public interface IJiraIgnited {
      * @throws IllegalStateException If can't find URL to the JIRA.
      */
     public String postJiraComment(String ticket, String comment) throws IOException;
+
+    public IJiraServerConfig config();
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/JiraIgnited.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/JiraIgnited.java
index 1ffe5ec..fd3d7b9 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/JiraIgnited.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/JiraIgnited.java
@@ -19,10 +19,10 @@ package org.apache.ignite.ci.jira.ignited;
 import java.io.IOException;
 import java.util.Set;
 import javax.inject.Inject;
-import org.apache.ignite.ci.jira.Ticket;
+import org.apache.ignite.ci.jira.pure.Ticket;
 import org.apache.ignite.ci.jira.pure.IJiraIntegration;
+import org.apache.ignite.ci.tcbot.conf.IJiraServerConfig;
 import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnited;
-import org.jetbrains.annotations.NotNull;
 
 /**
  *
@@ -56,21 +56,12 @@ class JiraIgnited implements IJiraIgnited {
         jiraTicketDao.init();
     }
 
-    /** {@inheritDoc} */
-    @Override public String ticketPrefix() {
-        return jira.ticketPrefix();
-    }
-
-    /** {@inheritDoc} */
-    @NotNull @Override public String projectName() {
-        return jira.projectName();
-    }
 
     /** {@inheritDoc} */
     @Override public Set<Ticket> getTickets() {
         jiraTicketSync.ensureActualizeJiraTickets(srvId);
 
-        return jiraTicketDao.getTickets(srvIdMaskHigh, ticketPrefix());
+        return jiraTicketDao.getTickets(srvIdMaskHigh, jira.config().projectCodeForVisa());
     }
 
     /** {@inheritDoc} */
@@ -87,4 +78,9 @@ class JiraIgnited implements IJiraIgnited {
     @Override public String postJiraComment(String ticket, String comment) throws IOException {
         return jira.postJiraComment(ticket, comment);
     }
+
+    /** {@inheritDoc} */
+    @Override public IJiraServerConfig config() {
+        return jira.config();
+    }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/JiraTicketDao.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/JiraTicketDao.java
index 30def78..2250e38 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/JiraTicketDao.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/JiraTicketDao.java
@@ -30,7 +30,8 @@ import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteCache;
 import org.apache.ignite.ci.db.TcHelperDb;
 import org.apache.ignite.ci.di.AutoProfiling;
-import org.apache.ignite.ci.jira.Ticket;
+import org.apache.ignite.ci.di.cache.GuavaCached;
+import org.apache.ignite.ci.jira.pure.Ticket;
 import org.apache.ignite.ci.teamcity.ignited.IStringCompactor;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.U;
@@ -60,10 +61,11 @@ public class JiraTicketDao {
 
     /**
      * @param srvIdMaskHigh Server id mask high.
-     * @param ticketPrefix Fixed prefix for JIRA tickets.
+     * @param projectCode project code. WIth delim gives Fixed prefix for JIRA tickets.
      * @return Jira tickets.
      */
-    public Set<Ticket> getTickets(int srvIdMaskHigh, String ticketPrefix) {
+    @GuavaCached(expireAfterWriteSecs = 60, softValues = true)
+    public Set<Ticket> getTickets(int srvIdMaskHigh, String projectCode) {
         Preconditions.checkNotNull(jiraCache, "init() was not called");
         long srvId = (long) srvIdMaskHigh << 32;
 
@@ -71,7 +73,7 @@ public class JiraTicketDao {
 
         for (Cache.Entry<Long, TicketCompacted> entry : jiraCache) {
             if ((entry.getKey() & srvId) == srvId)
-                res.add(entry.getValue().toTicket(compactor, ticketPrefix));
+                res.add(entry.getValue().toTicket(compactor, projectCode));
         }
 
         return res;
@@ -92,11 +94,11 @@ public class JiraTicketDao {
      * Save small part of loaded mutes.
      * @param srvIdMaskHigh Server id mask high.
      * @param chunk Chunk.
-     * @param ticketPrefix Ticket name template.
+     * @param projectCode Project code for contributions listing and for comments.
      * @return number of tickets totally saved.
      */
     @AutoProfiling
-    public int saveChunk(int srvIdMaskHigh, Collection<Ticket> chunk, String ticketPrefix) {
+    public int saveChunk(int srvIdMaskHigh, Collection<Ticket> chunk, String projectCode) {
         Preconditions.checkNotNull(jiraCache, "init() was not called");
 
         if (F.isEmpty(chunk))
@@ -105,8 +107,8 @@ public class JiraTicketDao {
         Map<Long, TicketCompacted> compactedTickets = new HashMap<>(U.capacity(chunk.size()));
 
         for (Ticket ticket : chunk) {
-            long key = ticketToCacheKey(srvIdMaskHigh, ticket.igniteId(ticketPrefix));
-            TicketCompacted val = new TicketCompacted(ticket, compactor, ticketPrefix);
+            long key = ticketToCacheKey(srvIdMaskHigh, ticket.keyWithoutProject(projectCode));
+            TicketCompacted val = new TicketCompacted(ticket, compactor, projectCode);
 
             compactedTickets.put(key, val);
         }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/JiraTicketSync.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/JiraTicketSync.java
index 0b97084..dca8033 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/JiraTicketSync.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/JiraTicketSync.java
@@ -17,15 +17,19 @@
 
 package org.apache.ignite.ci.jira.ignited;
 
+import java.lang.reflect.Field;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 import javax.inject.Inject;
 import org.apache.ignite.ci.di.MonitoredTask;
 import org.apache.ignite.ci.di.scheduler.IScheduler;
+import org.apache.ignite.ci.jira.Tickets;
+import org.apache.ignite.ci.jira.pure.Fields;
 import org.apache.ignite.ci.jira.pure.IJiraIntegration;
 import org.apache.ignite.ci.jira.pure.IJiraIntegrationProvider;
-import org.apache.ignite.ci.jira.Ticket;
-import org.apache.ignite.ci.jira.Tickets;
+import org.apache.ignite.ci.jira.pure.Ticket;
 import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnited;
 import org.apache.ignite.internal.util.typedef.F;
 import org.jetbrains.annotations.NotNull;
@@ -86,9 +90,15 @@ public class JiraTicketSync {
         int srvIdMaskHigh = ITeamcityIgnited.serverIdToInt(srvId);
         IJiraIntegration jira = jiraIntegrationProvider.server(srvId);
 
-        String projectName = jira.projectName();
-        String baseUrl = "search?jql=" + escape("project=" + projectName + " order by updated DESC")
-            + "&fields=status&maxResults=100";
+        String reqFields = Arrays.stream(Fields.class.getDeclaredFields())
+            .map(Field::getName)
+            .collect(Collectors.joining(","));
+
+        String projectCode = jira.config().projectCodeForVisa();
+        String baseUrl = "search?jql=" + escape("project=" + projectCode + " order by updated DESC")
+            + "&" +
+            "fields=" + reqFields +
+            "&maxResults=100";
 
         String url = baseUrl;
         Tickets tickets = jira.getTicketsPage(url);
@@ -97,7 +107,7 @@ public class JiraTicketSync {
         if (F.isEmpty(page))
             return "Something went wrong - no tickets found. Check jira availability.";
 
-        int ticketsSaved = jiraDao.saveChunk(srvIdMaskHigh, page, jira.ticketPrefix());
+        int ticketsSaved = jiraDao.saveChunk(srvIdMaskHigh, page, projectCode);
 
         int ticketsProcessed = page.size();
 
@@ -112,7 +122,7 @@ public class JiraTicketSync {
                 if (F.isEmpty(page))
                     break;
 
-                int savedNow = jiraDao.saveChunk(srvIdMaskHigh, page, jira.ticketPrefix());
+                int savedNow = jiraDao.saveChunk(srvIdMaskHigh, page, projectCode);
 
                 ticketsSaved += savedNow;
                 ticketsProcessed += page.size();
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/TicketCompacted.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/TicketCompacted.java
index 4eb942e..f009a8f 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/TicketCompacted.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/ignited/TicketCompacted.java
@@ -18,15 +18,18 @@
 package org.apache.ignite.ci.jira.ignited;
 
 import com.google.common.base.Objects;
-import org.apache.ignite.ci.jira.Fields;
-import org.apache.ignite.ci.jira.Status;
-import org.apache.ignite.ci.jira.Ticket;
+import org.apache.ignite.ci.jira.pure.Fields;
+import org.apache.ignite.ci.jira.pure.Status;
+import org.apache.ignite.ci.jira.pure.Ticket;
+import org.apache.ignite.ci.tcbot.common.StringFieldCompacted;
 import org.apache.ignite.ci.teamcity.ignited.IStringCompactor;
+import org.jetbrains.annotations.Nullable;
 
 /**
  *
  */
 public class TicketCompacted {
+    public static final String PROJECT_DELIM = "-";
     /** Id. */
     public long id;
 
@@ -36,28 +39,43 @@ public class TicketCompacted {
     /** Id of string: Fields/status/name, value compacted. */
     public int status;
 
+    /** Summary, nullable because of older entries. */
+    @Nullable private StringFieldCompacted summary = new StringFieldCompacted();
+
+    /** Custom field, to be queriable after persisting in Ignite, nullable because of older entry versions.  */
+    @Nullable private StringFieldCompacted customfield_11050 = new StringFieldCompacted();
+
+    /** Full description, nullable because of older entry versions.  */
+    @Nullable private StringFieldCompacted description = new StringFieldCompacted();
+
     /**
      * @param ticket Jira ticket.
      * @param comp Compactor.
-     * @param ticketTemplate Ticket name template.
+     * @param projectCode project name for commenting jira.
      */
-    public TicketCompacted(Ticket ticket, IStringCompactor comp, String ticketTemplate) {
+    public TicketCompacted(Ticket ticket, IStringCompactor comp, String projectCode) {
         id = ticket.id;
-        igniteId = Integer.valueOf(ticket.key.substring(ticketTemplate.length()));
+        igniteId = ticket.keyWithoutProject(projectCode);
         status = comp.getStringId(ticket.fields.status.name);
+        summary.setValue(ticket.fields.summary);
+        customfield_11050.setValue(ticket.fields.customfield_11050);
+        description.setValue(ticket.fields.description);
     }
 
     /**
      * @param comp Compactor.
-     * @param ticketPrefix Ticket name fixed prefix for the project.
+     * @param projectCode project code for VISA and filtering tasks.
      */
-    public Ticket toTicket(IStringCompactor comp, String ticketPrefix) {
+    public Ticket toTicket(IStringCompactor comp, String projectCode) {
         Ticket ticket = new Ticket();
 
         ticket.id = id;
-        ticket.key = ticketPrefix + igniteId;
+        ticket.key = projectCode + PROJECT_DELIM + igniteId;
         ticket.fields = new Fields();
         ticket.fields.status = new Status(comp.getStringFromId(status));
+        ticket.fields.summary = summary != null ? summary.getValue() : null;
+        ticket.fields.customfield_11050 = customfield_11050 != null ? customfield_11050.getValue() : null;
+        ticket.fields.description = description != null ? description.getValue() : null;
 
         return ticket;
     }
@@ -74,11 +92,14 @@ public class TicketCompacted {
 
         return id == compacted.id &&
             igniteId == compacted.igniteId &&
-            status == compacted.status;
+            status == compacted.status &&
+            Objects.equal(summary, compacted.summary) &&
+            Objects.equal(customfield_11050, compacted.customfield_11050) &&
+            Objects.equal(description, compacted.description);
     }
 
     /** {@inheritDoc} */
     @Override public int hashCode() {
-        return Objects.hashCode(id, igniteId, status);
+        return Objects.hashCode(id, igniteId, status, summary, customfield_11050, description);
     }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Fields.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Fields.java
similarity index 78%
copy from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Fields.java
copy to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Fields.java
index 2f6e271..02ed0b2 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Fields.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Fields.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.ci.jira;
+package org.apache.ignite.ci.jira.pure;
 
 import com.google.common.base.MoreObjects;
 
@@ -26,10 +26,20 @@ public class Fields {
     /** Ticket status. */
     public Status status;
 
-    /** {@inheritDoc} */
+    /** Summary. */
+    public String summary;
+
+    /** Customfield 11050. */
+    public String customfield_11050;
+
+    /** Description. */
+    public String description;
+
     @Override public String toString() {
         return MoreObjects.toStringHelper(this)
             .add("status", status)
+            .add("summary", summary)
+            .add("customfield_11050", customfield_11050)
             .toString();
     }
 }
\ No newline at end of file
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/IJiraIntegration.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/IJiraIntegration.java
index 9410350..210478f 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/IJiraIntegration.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/IJiraIntegration.java
@@ -19,6 +19,7 @@ package org.apache.ignite.ci.jira.pure;
 
 import java.io.IOException;
 import org.apache.ignite.ci.jira.Tickets;
+import org.apache.ignite.ci.tcbot.conf.IJiraServerConfig;
 import org.jetbrains.annotations.NotNull;
 
 /**
@@ -26,16 +27,6 @@ import org.jetbrains.annotations.NotNull;
  */
 public interface IJiraIntegration {
 
-    /** @return JIRA ticket prefix. */
-    @NotNull public String ticketPrefix();
-
-    /**
-     *
-     */
-    @NotNull public default String projectName() {
-        return ticketPrefix().replaceAll("-", "");
-    }
-
     /**
      * @return Internal identified service ID.
      */
@@ -58,14 +49,11 @@ public interface IJiraIntegration {
     /**
      * Produce wrapper for collection of Jira tickets for given server.
      *
-     * @param url Tickets loading URL and parameters.
+     * @param url Tickets loading URL and parameters, URL is relative, should not contain any start slashes.
      * @return Jira tickets.
      */
     public Tickets getTicketsPage(String url);
 
-    /** */
-    public String jiraUrl();
-
     /**
      * @param ticketFullName Ticket full name (e.g IGNITE-8331)
      * @return URL which is used as link to Jira ticket with specified name.
@@ -78,10 +66,8 @@ public interface IJiraIntegration {
      */
     public String generateCommentUrl(String ticketFullName, int commentId);
 
-    String getJiraApiUrl();
+    String restApiUrl();
 
-    /**
-     * @return {@code True} if JIRA authorization token is available.
-     */
-    boolean isJiraTokenAvailable();
+
+    public IJiraServerConfig config();
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Jira.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Jira.java
index c11cfcc..f43ea72 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Jira.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Jira.java
@@ -18,15 +18,15 @@
 package org.apache.ignite.ci.jira.pure;
 
 import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
 import com.google.gson.Gson;
-import java.io.File;
 import java.io.IOException;
-import java.util.Properties;
-import org.apache.ignite.ci.HelperConfig;
+import javax.inject.Inject;
 import org.apache.ignite.ci.di.AutoProfiling;
 import org.apache.ignite.ci.jira.Tickets;
+import org.apache.ignite.ci.tcbot.conf.IJiraServerConfig;
+import org.apache.ignite.ci.tcbot.conf.ITcBotConfig;
 import org.apache.ignite.ci.util.HttpUtil;
+import org.checkerframework.checker.nullness.qual.Nullable;
 import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -40,46 +40,16 @@ class Jira implements IJiraIntegration {
     /** Logger. */
     private static final Logger logger = LoggerFactory.getLogger(Jira.class);
 
-    /** */
-    private String jiraUrl;
-
-    /** JIRA ticket prefix. */
-    @NotNull private String jiraTicketPrefix;
-
-    /** JIRA authorization token. */
-    private String jiraBasicAuthTok;
-
-    /** URL for JIRA integration. */
-    private String jiraApiUrl;
-
     /** Server id. */
     private String srvId;
 
+    /** Config. */
+    @Inject ITcBotConfig cfg;
+
     /** {@inheritDoc} */
     @Override public void init(String srvId) {
         this.srvId = srvId;
-        final File workDir = HelperConfig.resolveWorkDir();
-
-        final String cfgName = HelperConfig.prepareConfigName(srvId);
-
-        final Properties props = HelperConfig.loadAuthProperties(workDir, cfgName);
-
-        jiraUrl = props.getProperty(HelperConfig.JIRA_URL);
 
-        jiraTicketPrefix = props.getProperty(HelperConfig.JIRA_TICKET_TEMPLATE, "IGNITE-");
-
-        jiraBasicAuthTok = HelperConfig.prepareJiraHttpAuthToken(props);
-        jiraApiUrl = props.getProperty(HelperConfig.JIRA_API_URL);
-    }
-
-    /** {@inheritDoc} */
-    @Override public String jiraUrl() {
-        return jiraUrl;
-    }
-
-    /** {@inheritDoc} */
-    @Override public String ticketPrefix() {
-        return jiraTicketPrefix;
     }
 
     /** {@inheritDoc} */
@@ -99,6 +69,8 @@ class Jira implements IJiraIntegration {
 
     /** {@inheritDoc} */
     @Override public String generateTicketUrl(String ticketFullName) {
+        @Nullable String jiraUrl = config().getUrl();
+
         Preconditions.checkState(!isNullOrEmpty(jiraUrl), "Jira URL is not configured for this server.");
 
         return jiraUrl + "browse/" + ticketFullName;
@@ -113,38 +85,48 @@ class Jira implements IJiraIntegration {
     }
 
     /** {@inheritDoc} */
-    @Override public String getJiraApiUrl() {
-        return jiraApiUrl;
+    @Override public IJiraServerConfig config() {
+        return cfg.getJiraConfig(srvId);
     }
 
     /** {@inheritDoc} */
-    @Override public boolean isJiraTokenAvailable() {
-        return !Strings.isNullOrEmpty(jiraBasicAuthTok);
+    @AutoProfiling
+    @Override public String postJiraComment(String ticket, String comment) throws IOException {
+        String jiraApiUrl = restApiUrl();
+
+        String url = jiraApiUrl + "issue/" + ticket + "/comment";
+
+        return HttpUtil.sendPostAsStringToJira(config().decodedHttpAuthToken(), url, "{\"body\": \"" + comment + "\"}");
     }
 
     /** {@inheritDoc} */
-    @AutoProfiling
-    @Override public String postJiraComment(String ticket, String comment) throws IOException {
-        if (isNullOrEmpty(jiraApiUrl))
+    @Override @NotNull public String restApiUrl() {
+        String jiraUrl = config().getUrl();
+
+        if (isNullOrEmpty(jiraUrl))
             throw new IllegalStateException("JIRA API URL is not configured for this server.");
 
-        String url = jiraApiUrl + "issue/" + ticket + "/comment";
+        StringBuilder apiUrl = new StringBuilder();
+
+        apiUrl.append(jiraUrl);
+        if (!jiraUrl.endsWith("/"))
+            apiUrl.append("/");
+
+        apiUrl.append("rest/api/2/");
 
-        return HttpUtil.sendPostAsStringToJira(jiraBasicAuthTok, url, "{\"body\": \"" + comment + "\"}");
+        return apiUrl.toString();
     }
 
     /**
-     * @param url Url.
+     * @param url Url, relative, should not contain any start slashes.
      * @return Response as gson string.
      */
     public String sendGetToJira(String url) throws IOException {
-        if (isNullOrEmpty(jiraApiUrl))
-            throw new IllegalStateException("JIRA API URL is not configured for this server.");
-
-        return HttpUtil.sendGetToJira(jiraBasicAuthTok, jiraApiUrl + url);
+        return HttpUtil.sendGetToJira(config().decodedHttpAuthToken(), restApiUrl() + url);
     }
 
-    public String getServiceId() {
+    /** {@inheritDoc} */
+    @Override public String getServiceId() {
         return srvId;
     }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Status.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Status.java
similarity index 94%
rename from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Status.java
rename to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Status.java
index 94d2131..1821c5f 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Status.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Status.java
@@ -15,9 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.ci.jira;
-
-import com.google.common.base.MoreObjects;
+package org.apache.ignite.ci.jira.pure;
 
 /**
  * Status for Jira ticket.
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Ticket.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Ticket.java
similarity index 86%
rename from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Ticket.java
rename to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Ticket.java
index dc8806f..4d5c514 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Ticket.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/pure/Ticket.java
@@ -15,9 +15,10 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.ci.jira;
+package org.apache.ignite.ci.jira.pure;
 
 import com.google.common.base.MoreObjects;
+import org.apache.ignite.ci.jira.ignited.TicketCompacted;
 
 /**
  * See example of GSON here
@@ -34,10 +35,12 @@ public class Ticket {
     public Fields fields;
 
     /**
-     * @param ticketPrefix Ticket name fixed prefix.
+     * @param projectCode JIRA project code to be removed from ticket key.
      * @return Ignite ticket Number ignoring project code (like 123 in IGNITE-123).
      */
-    public int igniteId(String ticketPrefix) {
+    public int keyWithoutProject(String projectCode) {
+        String ticketPrefix = projectCode + TicketCompacted.PROJECT_DELIM;
+
         return Integer.valueOf(key.substring(ticketPrefix.length()));
     }
 
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jobs/CheckQueueJob.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jobs/CheckQueueJob.java
index 0b54c5b..41bd9f4 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jobs/CheckQueueJob.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jobs/CheckQueueJob.java
@@ -17,12 +17,21 @@
 
 package org.apache.ignite.ci.jobs;
 
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import javax.inject.Inject;
 import jersey.repackaged.com.google.common.base.Throwables;
-import org.apache.ignite.ci.HelperConfig;
 import org.apache.ignite.ci.conf.BranchTracked;
 import org.apache.ignite.ci.conf.ChainAtServerTracked;
 import org.apache.ignite.ci.di.AutoProfiling;
 import org.apache.ignite.ci.di.MonitoredTask;
+import org.apache.ignite.ci.tcbot.conf.ITcBotConfig;
 import org.apache.ignite.ci.tcmodel.agent.Agent;
 import org.apache.ignite.ci.tcmodel.result.Build;
 import org.apache.ignite.ci.tcmodel.result.Triggered;
@@ -36,12 +45,6 @@ import org.apache.ignite.ci.user.ICredentialsProv;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.inject.Inject;
-import java.text.MessageFormat;
-import java.util.*;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-
 /**
  * Trigger build if half of agents are available and there is no self-triggered builds in build queue.
  */
@@ -66,6 +69,9 @@ public class CheckQueueJob implements Runnable {
     @Inject private IStringCompactor compactor;
 
     /** */
+    @Inject private ITcBotConfig cfg;
+
+    /** */
     private final Map<ChainAtServerTracked, Long> startTimes = new HashMap<>();
 
     /**
@@ -92,13 +98,13 @@ public class CheckQueueJob implements Runnable {
     @AutoProfiling
     @MonitoredTask(name = "Check Queue")
     protected String runEx() {
-        if (Boolean.valueOf(System.getProperty(CheckQueueJob.AUTO_TRIGGERING_BUILD_DISABLED))) {
+        if (Boolean.valueOf(System.getProperty(AUTO_TRIGGERING_BUILD_DISABLED))) {
             final String msg = "Automatic build triggering was disabled.";
             logger.info(msg);
             return msg;
         }
 
-        List<BranchTracked> tracked = HelperConfig.getTrackedBranches().getBranches();
+        List<BranchTracked> tracked = cfg.getTrackedBranches().getBranches();
 
         if (tracked == null || tracked.isEmpty()) {
             final String msg = "Background check queue skipped - no config set for tracked branches.";
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/chain/PrChainsProcessor.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/chain/PrChainsProcessor.java
index f94501e..adf2508 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/chain/PrChainsProcessor.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/chain/PrChainsProcessor.java
@@ -26,6 +26,7 @@ import org.apache.ignite.ci.github.ignited.IGitHubConnIgnited;
 import org.apache.ignite.ci.github.ignited.IGitHubConnIgnitedProvider;
 import org.apache.ignite.ci.jira.pure.IJiraIntegration;
 import org.apache.ignite.ci.jira.pure.IJiraIntegrationProvider;
+import org.apache.ignite.ci.tcbot.visa.BranchTicketMatcher;
 import org.apache.ignite.ci.tcmodel.result.problems.ProblemOccurrence;
 import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnited;
 import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnitedProvider;
@@ -72,6 +73,8 @@ public class PrChainsProcessor {
     /** */
     @Inject IJiraIntegrationProvider jiraIntegrationProvider;
 
+    @Inject private BranchTicketMatcher ticketMatcher;
+
     /**
      * @param creds Credentials.
      * @param srvId Server id.
@@ -160,7 +163,7 @@ public class PrChainsProcessor {
             //fail rate reference is always default (master)
             chainStatus.initFromContext(tcIgnited, ctx, baseBranch);
 
-            chainStatus.initJiraAndGitInfo(tcIgnited, jiraIntegration, gitHubConnIgnited);
+            chainStatus.initJiraAndGitInfo(ticketMatcher, jiraIntegration, gitHubConnIgnited);
         }
 
         res.addChainOnServer(chainStatus);
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/common/StringFieldCompacted.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/common/StringFieldCompacted.java
new file mode 100644
index 0000000..f5f9085
--- /dev/null
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/common/StringFieldCompacted.java
@@ -0,0 +1,160 @@
+/*
+ * 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.tcbot.common;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xerial.snappy.Snappy;
+
+public class StringFieldCompacted {
+    /** Logger. */
+    private static final Logger logger = LoggerFactory.getLogger(StringFieldCompacted.class);
+    public static final int FLAG_UNCOMPRESSED = 0;
+    public static final int FLAG_SNAPPY = 1;
+    public static final int FLAG_GZIP = 2;
+    byte flag;
+    byte data[];
+
+    public String getValue() {
+        if (data == null)
+            return "";
+
+        if (flag == FLAG_SNAPPY) {
+            try {
+                return new String(Snappy.uncompress(data), StandardCharsets.UTF_8);
+            }
+            catch (IOException e) {
+                logger.error("Snappy.uncompress failed: " + e.getMessage(), e);
+                return null;
+            }
+        }
+        else if (flag == FLAG_UNCOMPRESSED)
+            return new String(data, StandardCharsets.UTF_8);
+        else if (flag == FLAG_GZIP) {
+            try {
+                return unzipToString(data);
+            }
+            catch (Exception e) {
+                logger.error("GZip.uncompress failed: " + e.getMessage(), e);
+                return null;
+            }
+        }
+        else
+            return null;
+    }
+
+    @NotNull public static String unzipToString(byte[] data) throws IOException {
+        final ByteArrayInputStream in = new ByteArrayInputStream(data);
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        try (final GZIPInputStream gzi = new GZIPInputStream(in)) {
+            byte[] outbuf = new byte[data.length];
+            int len;
+            while ((len = gzi.read(outbuf, 0, outbuf.length)) != -1)
+                bos.write(outbuf, 0, len);
+        }
+
+        return new String(bos.toByteArray(), StandardCharsets.UTF_8);
+    }
+
+    public void setValue(String str) {
+        if (Strings.isNullOrEmpty(str)) {
+            this.data = null;
+            return;
+        }
+
+        byte[] uncompressed;
+        byte[] snappy = null;
+        byte[] gzip = null;
+        try {
+            uncompressed = str.getBytes(StandardCharsets.UTF_8);
+        }
+        catch (Exception e) {
+            logger.error("Set details failed: " + e.getMessage(), e);
+            return;
+        }
+
+        try {
+            snappy = Snappy.compress(uncompressed);
+        }
+        catch (Exception e) {
+            logger.error("Snappy.compress failed: " + e.getMessage(), e);
+        }
+
+        try {
+            gzip = zipBytes(uncompressed);
+        }
+        catch (Exception e) {
+            logger.error("Snappy.compress failed: " + e.getMessage(), e);
+        }
+
+        final int snappyLen = snappy != null ? snappy.length : -1;
+        final int gzipLen = gzip != null ? gzip.length : -1;
+
+        flag = FLAG_UNCOMPRESSED;
+        //uncompressed
+        data = uncompressed;
+
+        if (snappyLen > 0 && snappyLen < data.length) {
+            flag = FLAG_SNAPPY;
+            data = snappy;
+        }
+
+        if (gzipLen > 0 && gzipLen < data.length) {
+            flag = FLAG_GZIP;
+            data = gzip;
+        }
+
+        logger.info("U " + uncompressed.length + " S " + snappyLen + " Z " + gzipLen + ": F (" +
+            flag + ")");
+    }
+
+    public static byte[] zipBytes(byte[] uncompressed ) throws IOException {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try (final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(out)) {
+            gzipOutputStream.write(uncompressed);
+        }
+
+        return out.toByteArray();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        StringFieldCompacted compacted = (StringFieldCompacted)o;
+        return flag == compacted.flag &&
+            Arrays.equals(data, compacted.data);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        return Objects.hashCode(flag, data);
+    }
+}
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/conf/BranchesTracked.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/BranchesTracked.java
similarity index 55%
rename from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/conf/BranchesTracked.java
rename to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/BranchesTracked.java
index 8dc92d0..3a30609 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/conf/BranchesTracked.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/BranchesTracked.java
@@ -15,10 +15,15 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.ci.conf;
+package org.apache.ignite.ci.tcbot.conf;
 
 import java.util.*;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
+
+import org.apache.ignite.ci.conf.BranchTracked;
+import org.apache.ignite.ci.conf.ChainAtServer;
 
 /**
  * Config file for tracked branches.
@@ -27,6 +32,18 @@ public class BranchesTracked {
     /** Branches. */
     private List<BranchTracked> branches = new ArrayList<>();
 
+    /** Primary server ID. */
+    @Nullable private String primaryServerCode;
+
+    /** Additional list Servers to be used for validation of PRs, but not for tracking any branches. */
+    private List<TcServerConfig> tcServers = new ArrayList<>();
+
+    /** JIRA config to be used . */
+    private List<JiraServerConfig> jiraServers = new ArrayList<>();
+
+    /** JIRA config to be used . */
+    private List<GitHubConfig> gitHubConfigs = new ArrayList<>();
+
     /**
      * @return list of internal identifiers of branch.
      */
@@ -34,6 +51,9 @@ public class BranchesTracked {
         return branches.stream().map(BranchTracked::getId).collect(Collectors.toList());
     }
 
+    /**
+     * Get Unique suites involved into tracked branches
+     */
     public Set<ChainAtServer> getSuitesUnique() {
         return branches.stream()
             .flatMap(BranchTracked::getChainsStream)
@@ -49,8 +69,17 @@ public class BranchesTracked {
         return get(branch).orElseThrow(() -> new RuntimeException("Branch not found: " + branch));
     }
 
+    /**
+     *
+     */
     public Set<String> getServerIds() {
-        return branches.stream().flatMap(BranchTracked::getChainsStream).map(ChainAtServer::getServerId).collect(Collectors.toSet());
+        Stream<String> srvsInTracked = branches.stream()
+            .flatMap(BranchTracked::getChainsStream)
+            .map(ChainAtServer::getServerId);
+
+        return Stream.concat(srvsInTracked,
+            tcServers.stream().map(TcServerConfig::getCode))
+            .collect(Collectors.toSet());
     }
 
     public List<BranchTracked> getBranches() {
@@ -60,4 +89,23 @@ public class BranchesTracked {
     public void addBranch(BranchTracked branch) {
         branches.add(branch);
     }
+
+    /**
+     * @return Primary server code.
+     */
+    @Nullable public String primaryServerCode() {
+        return primaryServerCode;
+    }
+
+    Optional<TcServerConfig> getTcConfig(String code) {
+        return tcServers.stream().filter(s -> code.equals(s.getCode())).findAny();
+    }
+
+    Optional<JiraServerConfig> getJiraConfig(String code) {
+        return jiraServers.stream().filter(s -> code.equals(s.getCode())).findAny();
+    }
+
+    public Optional<GitHubConfig> getGitHubConfig(String code) {
+        return gitHubConfigs.stream().filter(s -> code.equals(s.getCode())).findAny();
+    }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/GitHubConfig.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/GitHubConfig.java
new file mode 100644
index 0000000..9b21df3
--- /dev/null
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/GitHubConfig.java
@@ -0,0 +1,83 @@
+/*
+ * 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.tcbot.conf;
+
+import com.google.common.base.Strings;
+import org.apache.ignite.ci.HelperConfig;
+
+import java.util.Properties;
+
+/**
+ *
+ */
+public class GitHubConfig implements IGitHubConfig {
+    public static final String DEFAULT_BRANCH_PREFIX = "ignite-";
+    /**
+     * Service (server) Name.
+     */
+    private String code;
+
+    private String apiUrl;
+
+    private String branchPrefix;
+
+
+    private Properties props;
+
+
+    public GitHubConfig() {
+    }
+
+    public GitHubConfig(String code, Properties props) {
+        this.code = code;
+        this.props = props;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * @param props Properties.
+     */
+    public GitHubConfig properties(Properties props) {
+        this.props = props;
+
+        return this;
+    }
+
+    /**
+     * @param code Name.
+     */
+    public GitHubConfig code(String code) {
+        this.code = code;
+
+        return this;
+    }
+
+    @Override
+    public String gitBranchPrefix() {
+        if (Strings.isNullOrEmpty(branchPrefix)) {
+            if (props != null) {
+                return props.getProperty(HelperConfig.GIT_BRANCH_PREFIX, DEFAULT_BRANCH_PREFIX);
+            } else {
+                return DEFAULT_BRANCH_PREFIX;
+            }
+        }
+        return branchPrefix;
+    }
+}
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Fields.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/IGitHubConfig.java
similarity index 70%
copy from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Fields.java
copy to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/IGitHubConfig.java
index 2f6e271..6dbcb4c 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Fields.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/IGitHubConfig.java
@@ -14,22 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package org.apache.ignite.ci.jira;
-
-import com.google.common.base.MoreObjects;
+package org.apache.ignite.ci.tcbot.conf;
 
 /**
  *
  */
-public class Fields {
-    /** Ticket status. */
-    public Status status;
-
-    /** {@inheritDoc} */
-    @Override public String toString() {
-        return MoreObjects.toStringHelper(this)
-            .add("status", status)
-            .toString();
-    }
-}
\ No newline at end of file
+public interface IGitHubConfig {
+    /** Git branch prefix for search TC runs in PR-less contributions. */
+    public String gitBranchPrefix();
+}
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/ITcBotConfig.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/IJiraServerConfig.java
similarity index 53%
copy from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/ITcBotConfig.java
copy to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/IJiraServerConfig.java
index 95948da..7e87a88 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/ITcBotConfig.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/IJiraServerConfig.java
@@ -16,38 +16,36 @@
  */
 package org.apache.ignite.ci.tcbot.conf;
 
-import java.util.Collection;
-import java.util.List;
-import org.apache.ignite.ci.conf.BranchesTracked;
+import com.google.common.base.Strings;
+import org.jetbrains.annotations.Nullable;
 
 /**
- * Teamcity Bot configuration access inteface.
+ * Abstract JIRA server config.
  */
-public interface ITcBotConfig {
-    /** Default server id. */
-    String DEFAULT_SERVER_ID = "apache";
-
-
-    public String primaryServerId();
-
+public interface IJiraServerConfig {
     /**
-     * @return Tracked branches configuration for TC Bot.
+     * Return JIRA URL, e.g. https://issues.apache.org/jira/
      */
-    public BranchesTracked getTrackedBranches();
+    public String getUrl();
 
     /**
-     * @return list of internal TC Bot identifiers of all tracked branches.
+     * JIRA project code for filtering out tickets and for adding VISA (JIRA comments).
      */
-    public default List<String> getTrackedBranchesIds() {
-        return getTrackedBranches().getIds();
-    }
+    public String projectCodeForVisa();
 
     /**
-     * @return list of servers (services) identifiers involved into tracked branhes processing.
+     * @return PR name and branch name matching number prefix
      */
-    public default Collection<String> getServerIds() {
-        return getTrackedBranches().getServerIds();
-    }
+    @Nullable
+    public String branchNumPrefix();
 
+    @Nullable
+    public String decodedHttpAuthToken();
 
+    /**
+     * @return {@code True} if JIRA authorization token is available.
+     */
+    public default boolean isJiraTokenAvailable() {
+        return !Strings.isNullOrEmpty(decodedHttpAuthToken());
+    }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/ITcBotConfig.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/ITcBotConfig.java
index 95948da..213835f 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/ITcBotConfig.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/ITcBotConfig.java
@@ -18,17 +18,15 @@ package org.apache.ignite.ci.tcbot.conf;
 
 import java.util.Collection;
 import java.util.List;
-import org.apache.ignite.ci.conf.BranchesTracked;
 
 /**
  * Teamcity Bot configuration access inteface.
  */
 public interface ITcBotConfig {
-    /** Default server id. */
-    String DEFAULT_SERVER_ID = "apache";
+    /** Default server code. */
+    String DEFAULT_SERVER_CODE = "apache";
 
-
-    public String primaryServerId();
+    public String primaryServerCode();
 
     /**
      * @return Tracked branches configuration for TC Bot.
@@ -49,5 +47,9 @@ public interface ITcBotConfig {
         return getTrackedBranches().getServerIds();
     }
 
+    public ITcServerConfig getTeamcityConfig(String srvCode);
+
+    public IJiraServerConfig getJiraConfig(String srvCode);
 
+    public IGitHubConfig getGitConfig(String srvCode);
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Fields.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/ITcServerConfig.java
similarity index 68%
rename from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Fields.java
rename to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/ITcServerConfig.java
index 2f6e271..36243fb 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/Fields.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/ITcServerConfig.java
@@ -14,22 +14,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.ignite.ci.tcbot.conf;
 
-package org.apache.ignite.ci.jira;
-
-import com.google.common.base.MoreObjects;
+import java.util.Properties;
 
 /**
- *
+ * Teamcity Server configuration.
  */
-public class Fields {
-    /** Ticket status. */
-    public Status status;
+public interface ITcServerConfig {
+    @Deprecated
+    public Properties properties();
+
+    /**
+     * @return Another TC Server (service) config name to use settings from. Filled only for server aliases.
+     */
+    public String reference();
 
-    /** {@inheritDoc} */
-    @Override public String toString() {
-        return MoreObjects.toStringHelper(this)
-            .add("status", status)
-            .toString();
-    }
-}
\ No newline at end of file
+    public String host();
+}
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/JiraServerConfig.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/JiraServerConfig.java
new file mode 100644
index 0000000..66a457a
--- /dev/null
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/JiraServerConfig.java
@@ -0,0 +1,144 @@
+/*
+ * 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.tcbot.conf;
+
+import com.google.common.base.Strings;
+import java.util.ArrayList;
+import java.util.Properties;
+import org.apache.ignite.ci.HelperConfig;
+import org.apache.ignite.ci.conf.PasswordEncoder;
+import org.apache.ignite.ci.jira.pure.Fields;
+import org.jetbrains.annotations.Nullable;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+/**
+ *
+ */
+public class JiraServerConfig implements IJiraServerConfig {
+    /** Service (server) Name. */
+    private String code;
+
+    /**
+     * Tickets for commenting in JIRA and finding out PA tickets. Default project is "IGNITE".
+     */
+    private String projectCode;
+
+    /**
+     * Branch number prefix. Optional, if not present {@link #projectCode}-NNNNN is searched.
+     * But if branch has different enumeration, this prefix will be searched instead.
+     * If specified, that meant tickets maching branches have another identification.
+     * For exaple some ticket having ID {@link #projectCode}-N1 will be commented, but a branch will be identified using
+     * {@link #branchNumPrefix}N2 with another number.
+     *
+     * Search of branches will be performed using data in JIRA ticket fields for
+     * {@link #projectCode}-N1, fields are listed in {@link #branchNumPrefixSearchFields}.
+     */
+    private String branchNumPrefix;
+
+    /**
+     * Branch ticket template search fields, list of JIRA fields IDs to be checked for finding out branch.
+     * Available fields are field names from {@link Fields} class.
+     */
+    private ArrayList<String> branchNumPrefixSearchFields;
+
+    private Properties props;
+
+    /**
+     * JIRA Auth token to access, use {@link org.apache.ignite.ci.conf.PasswordEncoder#encodeJiraTok(String, String)}
+     */
+    private String authTok;
+
+    /**
+     * JIRA Server URL. HTTPs is highly recommended.
+     */
+    private String url;
+
+    public JiraServerConfig() {
+    }
+
+    public JiraServerConfig(String code, Properties props) {
+        this.code = code;
+        this.props = props;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    /**
+     * @param props Properties.
+     */
+    public JiraServerConfig properties(Properties props) {
+        this.props = props;
+
+        return this;
+    }
+
+    /**
+     * @param code Name.
+     */
+    public JiraServerConfig code(String code) {
+        this.code = code;
+
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getUrl() {
+        if (Strings.isNullOrEmpty(url) && props != null)
+            return props.getProperty(HelperConfig.JIRA_URL);
+
+        return url;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String projectCodeForVisa() {
+        if(Strings.isNullOrEmpty(projectCode) && props!=null) {
+            String ticketPref = props.getProperty(HelperConfig.JIRA_TICKET_TEMPLATE, "IGNITE-");
+
+            return ticketPref.replaceAll("-", "");
+        }
+
+        return projectCode;
+    }
+
+    /** {@inheritDoc} */
+    @Nullable @Override public String branchNumPrefix() {
+        return Strings.emptyToNull(branchNumPrefix);
+    }
+
+    /**
+     * Extracts JIRA basic authorization token from properties.
+     *
+     * @return Null or decoded auth token for Github.
+     */
+    @Nullable
+    @Override
+    public String decodedHttpAuthToken() {
+        String tok;
+        if (Strings.isNullOrEmpty(authTok) && props != null)
+            tok = props.getProperty(HelperConfig.JIRA_AUTH_TOKEN);
+        else
+            tok = authTok;
+
+        if (isNullOrEmpty(tok))
+            return null;
+
+        return PasswordEncoder.decode(tok);
+    }
+}
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/LocalFilesBasedConfig.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/LocalFilesBasedConfig.java
index f510061..440f121 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/LocalFilesBasedConfig.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/LocalFilesBasedConfig.java
@@ -16,8 +16,10 @@
  */
 package org.apache.ignite.ci.tcbot.conf;
 
+import com.google.common.base.Strings;
+import java.io.File;
+import java.util.Properties;
 import org.apache.ignite.ci.HelperConfig;
-import org.apache.ignite.ci.conf.BranchesTracked;
 import org.apache.ignite.ci.di.cache.GuavaCached;
 
 /**
@@ -25,12 +27,56 @@ import org.apache.ignite.ci.di.cache.GuavaCached;
  */
 public class LocalFilesBasedConfig implements ITcBotConfig {
     /** {@inheritDoc} */
-    @GuavaCached(softValues = true, expireAfterAccessSecs = 3 * 60)
+    @GuavaCached(softValues = true, expireAfterWriteSecs = 3 * 60)
     @Override public BranchesTracked getTrackedBranches() {
         return HelperConfig.getTrackedBranches();
     }
 
-    @Override public String primaryServerId() {
-        return ITcBotConfig.DEFAULT_SERVER_ID;
+    /** {@inheritDoc} */
+    @Override public ITcServerConfig getTeamcityConfig(String srvCode) {
+        return getTrackedBranches().getTcConfig(srvCode)
+            .orElseGet(() -> {
+                TcServerConfig tcCfg = new TcServerConfig();
+
+                tcCfg.code(srvCode);
+
+                Properties props = loadOldAuthProps(srvCode);
+
+                tcCfg.properties(props);
+
+                return tcCfg;
+            });
     }
+
+    @Override public IJiraServerConfig getJiraConfig(String srvCode) {
+        return getTrackedBranches().getJiraConfig(srvCode)
+            .orElseGet(() -> new JiraServerConfig()
+                    .code(srvCode)
+                    .properties(loadOldAuthProps(srvCode)));
+    }
+
+    @Override
+    public IGitHubConfig getGitConfig(String srvCode) {
+        return getTrackedBranches().getGitHubConfig(srvCode)
+                .orElseGet(() -> new GitHubConfig()
+                        .code(srvCode)
+                        .properties(loadOldAuthProps(srvCode)));
+    }
+
+    /** {@inheritDoc} */
+    @Override public String primaryServerCode() {
+        String srvCode = getTrackedBranches().primaryServerCode();
+
+        return Strings.isNullOrEmpty(srvCode) ? ITcBotConfig.DEFAULT_SERVER_CODE : srvCode;
+    }
+
+
+    private Properties loadOldAuthProps(String srvCode) {
+        File workDir = HelperConfig.resolveWorkDir();
+
+        String cfgName = HelperConfig.prepareConfigName(srvCode);
+
+        return HelperConfig.loadAuthProperties(workDir, cfgName);
+    }
+
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/TcServerConfig.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/TcServerConfig.java
new file mode 100644
index 0000000..49756ec
--- /dev/null
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/conf/TcServerConfig.java
@@ -0,0 +1,102 @@
+/*
+ * 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.tcbot.conf;
+
+import com.google.common.base.Strings;
+import java.util.Properties;
+import javax.annotation.Nonnull;
+import org.apache.ignite.ci.HelperConfig;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Teamcity connection configuration or reference to another config.
+ */
+public class TcServerConfig implements ITcServerConfig {
+    private static final String DEFAULT_HOST = "https://ci.ignite.apache.org/";
+
+    /** TC server name. */
+    @Nonnull private String code;
+
+    /** Name of server this config points to. This config is just an alias for TC Server. */
+    @Nullable
+    private String reference;
+
+    @Nullable private Properties props;
+
+    /** Host. */
+    @Nullable private String host;
+
+    public TcServerConfig() {
+
+    }
+
+    public TcServerConfig(String code, Properties properties) {
+        this.code = code;
+        this.props = properties;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Properties properties() {
+        if (props == null)
+            return new Properties();
+
+        return props;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String reference() {
+        return reference;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String host() {
+        final String hostConf = hostConfigured().trim();
+
+        return hostConf + (hostConf.endsWith("/") ? "" : "/");
+    }
+
+    /**
+     * Configured value for host.
+     */
+    @NotNull
+    private String hostConfigured() {
+        if (Strings.isNullOrEmpty(host)) {
+            if (props != null)
+                return props.getProperty(HelperConfig.HOST, DEFAULT_HOST);
+        } else {
+            return DEFAULT_HOST;
+        }
+
+        return host;
+    }
+
+    /**
+     * @param props Properties.
+     */
+    public void properties(Properties props) {
+        this.props = props;
+    }
+
+    public void code(String srvCode) {
+        this.code = srvCode;
+    }
+}
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/BranchTicketMatcher.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/BranchTicketMatcher.java
new file mode 100644
index 0000000..6cd746e
--- /dev/null
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/BranchTicketMatcher.java
@@ -0,0 +1,302 @@
+/*
+ * 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.tcbot.visa;
+
+import com.google.common.base.Strings;
+import org.apache.ignite.ci.di.cache.GuavaCached;
+import org.apache.ignite.ci.github.PullRequest;
+import org.apache.ignite.ci.github.ignited.IGitHubConnIgnitedProvider;
+import org.apache.ignite.ci.github.pure.IGitHubConnection;
+import org.apache.ignite.ci.jira.ignited.IJiraIgnitedProvider;
+import org.apache.ignite.ci.jira.ignited.TicketCompacted;
+import org.apache.ignite.ci.jira.pure.Ticket;
+import org.apache.ignite.ci.tcbot.conf.IGitHubConfig;
+import org.apache.ignite.ci.tcbot.conf.IJiraServerConfig;
+import org.apache.ignite.ci.tcbot.conf.ITcBotConfig;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.inject.Inject;
+import javax.ws.rs.QueryParam;
+import java.util.Collection;
+import java.util.Objects;
+
+public class BranchTicketMatcher {
+    /** Config. */
+    @Inject private ITcBotConfig cfg;
+
+    /** GitHub connection ignited provider. */
+    @Inject private IGitHubConnIgnitedProvider gitHubProvider;
+
+    /** JIRA provider */
+    @Inject private IJiraIgnitedProvider jiraIgnProv;
+
+    @Nullable
+    public String resolveTcBranchForPrLess(Ticket ticket,
+                                           IJiraServerConfig jiraCfg,
+                                           IGitHubConfig gitHubConfig) {
+        String branchNumPrefix = jiraCfg.branchNumPrefix();
+
+        if (Strings.isNullOrEmpty(branchNumPrefix)) {
+            //an easy way, no special branch and ticket mappings specified, use project code.
+            int ticketId = ticket.keyWithoutProject(jiraCfg.projectCodeForVisa());
+
+            return gitHubConfig.gitBranchPrefix() + ticketId;
+        }
+
+        String branchJiraIdentification = findFixPrefixedNoInValues(branchNumPrefix,
+                ticket.key,
+                ticket.fields.summary,
+                ticket.fields.customfield_11050);
+
+        return convertJiraToGit(branchJiraIdentification, branchNumPrefix, gitHubConfig);
+
+    }
+
+    /**
+     * Converts JIRA notation branch name to actual git branch name. Usually it is just lower-casing, but any mapping may be configured.
+     * @param branchJiraIdentification Branch jira identification.
+     * @param branchNumPrefix Branch number prefix.
+     * @param gitHubConfig GH connection config.
+     */
+    private String convertJiraToGit(String branchJiraIdentification,
+                                    String branchNumPrefix,
+                                    IGitHubConfig gitHubConfig) {
+        if (Strings.isNullOrEmpty(branchJiraIdentification))
+            return null;
+
+        return gitHubConfig.gitBranchPrefix() + branchJiraIdentification.substring(branchNumPrefix.length());
+    }
+
+    /**
+     * @param tickets Tickets.
+     * @param pr Pr.
+     * @param jiraCfg Jira config.
+     */
+    @Nullable public String resolveTicketIdForPrBasedContrib(Collection<Ticket> tickets, PullRequest pr, IJiraServerConfig jiraCfg) {
+        String branchNumPrefix = jiraCfg.branchNumPrefix();
+
+        if (Strings.isNullOrEmpty(branchNumPrefix)) {
+            //an easy way, no special branch and ticket mappings specified, use project code.
+            String jiraPrefix = jiraCfg.projectCodeForVisa() + TicketCompacted.PROJECT_DELIM;
+
+            return findFixPrefixedNumber(pr.getTitle(), jiraPrefix);
+        }
+
+        String prTitle = pr.getTitle();
+
+        String branchNum = findFixPrefixedNumber(prTitle, branchNumPrefix);
+
+        if (branchNum == null) // PR does not mention
+            return null;
+
+        return findTicketMentions(tickets, branchNum);
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @GuavaCached(maximumSize = 3000, expireAfterWriteSecs = 60, cacheNullRval = true)
+    protected String findTicketMentions(String srvCode, @Nullable String branchNum) {
+        return findTicketMentions(jiraIgnProv.server(srvCode).getTickets(), branchNum);
+    }
+
+    @Nullable private String findTicketMentions(Collection<Ticket> tickets, @Nullable String branchNum) {
+        if (Strings.isNullOrEmpty(branchNum))
+            return null;
+
+        return tickets.stream()
+                .map(t -> t.key)
+                .filter(k -> Objects.equals(k, branchNum))
+                .findFirst()
+                .orElseGet(() -> findTicketMentionsInSupplementaryFields(tickets, branchNum));
+    }
+
+    @Nullable private String findTicketMentionsInSupplementaryFields(Collection<Ticket> tickets, String branchNum) {
+        if (Strings.isNullOrEmpty(branchNum))
+            return null;
+
+        return tickets.stream()
+                .filter(t -> mentionsBranch(branchNum, t))
+                .findFirst()
+                .map(t -> t.key)
+                .orElse(null);
+    }
+
+    /**
+     * @param branchName Full branch name in jira.
+     * @param ticket Ticket.
+     */
+    private boolean mentionsBranch(String branchName, Ticket ticket) {
+        String summary = ticket.fields.summary;
+        if (summary != null && summary.contains(branchName))
+            return true;
+
+        String val = ticket.fields.customfield_11050;
+        if (val != null && val.contains(branchName))
+            return true;
+
+        return false;
+    }
+
+
+    @Nullable
+    private String findFixPrefixedNoInValues(@NotNull String prefix, String... values) {
+        for (String value : values) {
+            String fixPrefixedNumber = findFixPrefixedNumber(value, prefix);
+
+            if (fixPrefixedNumber != null)
+                return fixPrefixedNumber;
+        }
+        return null;
+    }
+    /**
+     * @param value Pull Request/Ticket title prefix or other text to find constant-prefix text.
+     * @param prefix Ticket prefix.
+     * @return Branch number or null.
+     */
+    @Nullable
+    private String findFixPrefixedNumber(@Nullable String value, @NotNull String prefix) {
+        if(Strings.isNullOrEmpty(value))
+            return null;
+
+        int idxOfBranchNum = value.toUpperCase().indexOf(prefix.toUpperCase());
+
+        if (idxOfBranchNum < 0)
+            return null;
+
+        int beginIdx = prefix.length() + idxOfBranchNum;
+        int endIdx = beginIdx;
+
+        while (endIdx < value.length() && Character.isDigit(value.charAt(endIdx)))
+            endIdx++;
+
+        if (endIdx == beginIdx)
+            return null;
+
+        return prefix + value.substring(beginIdx, endIdx);
+    }
+
+    public static class TicketNotFoundException extends Exception {
+        TicketNotFoundException(String msg) {
+            super(msg);
+        }
+
+        TicketNotFoundException(String msg, Exception e) {
+            super(msg, e);
+        }
+    }
+
+
+    public String resolveTicketFromBranch(String srvCode, String ticketFullName, String branchForTc) throws TicketNotFoundException {
+        if(!Strings.isNullOrEmpty(ticketFullName))
+            return ticketFullName; //old code probably not needed now; ticketFullName = ticketFullName.toUpperCase().startsWith(prefix) ? ticketFullName : prefix + ticketFullName;
+
+        IJiraServerConfig jiraCfg = cfg.getJiraConfig(srvCode);
+        IGitHubConfig gitConfig = cfg.getGitConfig(srvCode);
+
+        PullRequest pr; // filled only when special PR found
+
+        String ticketPrefix;
+        try {
+            String branchNumPrefix = jiraCfg.branchNumPrefix();
+
+            ticketPrefix = Strings.isNullOrEmpty(branchNumPrefix)
+                    ? jiraCfg.projectCodeForVisa() + TicketCompacted.PROJECT_DELIM
+                    : branchNumPrefix;
+
+            String prLessTicket = prLessTicket(branchForTc, ticketPrefix, gitConfig);
+            if (!Strings.isNullOrEmpty(prLessTicket)) {
+                if (Strings.isNullOrEmpty(branchNumPrefix)) {
+                    //Default, simple case
+
+                    return prLessTicket; //find out PRless ticket,
+                } else {
+                    // PR less ticket only mentioned in real ticket
+                    String ticket = findTicketMentions(srvCode, prLessTicket);
+
+                    if (!Strings.isNullOrEmpty(ticket))
+                        return ticket; // found real JIRA ticket for comment
+                }
+            }
+
+            pr = findPrForBranch(srvCode, branchForTc);
+            if (pr != null) {
+                String jiraPrefixInPr = findFixPrefixedNumber(pr.getTitle(), ticketPrefix);
+
+                String ticketFromPr;
+                if (Strings.isNullOrEmpty(branchNumPrefix)) {
+                    //Default, simple case, branch name matching gives us a ticket
+                    ticketFromPr = jiraPrefixInPr;
+                } else {
+                    ticketFromPr = Strings.isNullOrEmpty(jiraPrefixInPr)
+                            ? null
+                            : findTicketMentions(srvCode, jiraPrefixInPr);
+                }
+
+                if (!Strings.isNullOrEmpty(ticketFromPr))
+                    return ticketFromPr; // found real JIRA ticket for comment
+            }
+        } catch (Exception e) {
+            throw new TicketNotFoundException("Exception happened when server tried to get ticket ID from Pull Request - " + e.getMessage(), e);
+        }
+
+        throw new TicketNotFoundException("JIRA ticket can't be found - " +
+                "PR title \"" + (pr == null ? "" : pr.getTitle()) + "\" should starts with \"" + ticketPrefix + "NNNNN\"." +
+                " Please, rename PR according to the" +
+                " <a href='https://cwiki.apache.org/confluence/display/IGNITE/How+to+Contribute" +
+                "#HowtoContribute-1.CreateGitHubpull-request'>contributing guide</a>" +
+                " or use branch name according ticket name.");
+    }
+
+    @Nullable
+    private PullRequest findPrForBranch(
+            @Nullable @QueryParam("serverId") String srvId,
+            @Nullable @QueryParam("branchName") String branchForTc) {
+        Integer prId = IGitHubConnection.convertBranchToPrId(branchForTc);
+
+        if (prId == null)
+            return null;
+
+        return gitHubProvider.server(srvId).getPullRequest(prId);
+    }
+
+
+
+    /**
+     * @param branchForTc Branch for tc.
+     * @param ticketPrefix JIRA Ticket prefix.
+     * @param gitHubIgn GitHub connection ign.
+     */
+    @Nullable
+    private static String prLessTicket(String branchForTc,
+                                       String ticketPrefix,
+                                       IGitHubConfig gitHubIgn) {
+        String branchPrefix = gitHubIgn.gitBranchPrefix();
+
+        if (!branchForTc.startsWith(branchPrefix))
+            return null;
+
+        try {
+            int ticketNum = Integer.parseInt(branchForTc.substring(branchPrefix.length()));
+
+            return ticketPrefix + ticketNum;
+        }
+        catch (NumberFormatException ignored) {
+        }
+        return null;
+    }
+}
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/TcBotTriggerAndSignOffService.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/TcBotTriggerAndSignOffService.java
index 05031c7..44e3bb1 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/TcBotTriggerAndSignOffService.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/TcBotTriggerAndSignOffService.java
@@ -35,22 +35,21 @@ import java.util.stream.Stream;
 import javax.annotation.Nonnull;
 import javax.inject.Inject;
 import javax.ws.rs.QueryParam;
-import org.apache.ignite.ci.HelperConfig;
 import org.apache.ignite.ci.ITeamcity;
 import org.apache.ignite.ci.github.GitHubBranch;
 import org.apache.ignite.ci.github.GitHubUser;
 import org.apache.ignite.ci.github.PullRequest;
 import org.apache.ignite.ci.github.ignited.IGitHubConnIgnited;
 import org.apache.ignite.ci.github.ignited.IGitHubConnIgnitedProvider;
-import org.apache.ignite.ci.github.pure.IGitHubConnection;
-import org.apache.ignite.ci.jira.Ticket;
+import org.apache.ignite.ci.jira.pure.Ticket;
 import org.apache.ignite.ci.jira.ignited.IJiraIgnited;
 import org.apache.ignite.ci.jira.ignited.IJiraIgnitedProvider;
-import org.apache.ignite.ci.jira.pure.IJiraIntegrationProvider;
 import org.apache.ignite.ci.observer.BuildObserver;
 import org.apache.ignite.ci.observer.BuildsInfo;
 import org.apache.ignite.ci.tcbot.ITcBotBgAuth;
 import org.apache.ignite.ci.tcbot.chain.PrChainsProcessor;
+import org.apache.ignite.ci.tcbot.conf.IJiraServerConfig;
+import org.apache.ignite.ci.tcbot.conf.ITcBotConfig;
 import org.apache.ignite.ci.tcmodel.mute.MuteInfo;
 import org.apache.ignite.ci.tcmodel.result.Build;
 import org.apache.ignite.ci.teamcity.ignited.BuildRefCompacted;
@@ -109,11 +108,8 @@ public class TcBotTriggerAndSignOffService {
     /** TC ignited provider. */
     @Inject ITeamcityIgnitedProvider tcIgnitedProv;
 
-    /** Direct connection to JIRA provider */
-    @Inject IJiraIgnitedProvider jiraIgnProv;
-
-    /** Direct connection to JIRA provider */
-    @Inject IJiraIntegrationProvider jiraPureProvider;
+    /** JIRA provider */
+    @Inject private IJiraIgnitedProvider jiraIgnProv;
 
     /** */
     @Inject private VisasHistoryStorage visasHistStorage;
@@ -129,6 +125,11 @@ public class TcBotTriggerAndSignOffService {
 
     @Inject PrChainsProcessor prChainsProcessor;
 
+    /** Config. */
+    @Inject ITcBotConfig cfg;
+
+    @Inject BranchTicketMatcher ticketMatcher;
+
     /** Jackson serializer. */
     private final ObjectMapper objMapper = new ObjectMapper();
 
@@ -241,25 +242,7 @@ public class TcBotTriggerAndSignOffService {
         }
     }
 
-    /**
-     * @param pr Pull Request.
-     * @param prefix Ticket prefix.
-     * @return JIRA ticket full name or empty string.
-     */
-    @NotNull public static String getTicketFullName(PullRequest pr, @NotNull String prefix) {
-        String ticketId = "";
-        if (pr.getTitle().toUpperCase().startsWith(prefix)) {
-            int beginIdx = prefix.length();
-            int endIdx = prefix.length();
 
-            while (endIdx < pr.getTitle().length() && Character.isDigit(pr.getTitle().charAt(endIdx)))
-                endIdx++;
-
-            ticketId = prefix + pr.getTitle().substring(beginIdx, endIdx);
-        }
-
-        return ticketId;
-    }
 
     @NotNull public String triggerBuildsAndObserve(
         @Nullable String srvId,
@@ -303,40 +286,13 @@ public class TcBotTriggerAndSignOffService {
         String parentSuiteId,
         Build... builds
     ) {
-        IJiraIgnited jiraIntegration = jiraIgnProv.server(srvId);
-
-        String prefix = jiraIntegration.ticketPrefix();
-
-        if (F.isEmpty(ticketFullName)) {
-            try {
-                ticketFullName = prLessTicket(srvId, branchForTc, prov, prefix);
-
-                PullRequest pr = null;
-
-                if (Strings.isNullOrEmpty(ticketFullName)) {
-                    pr = findPrForBranch(srvId, branchForTc);
-
-                    if (pr != null)
-                        ticketFullName = getTicketFullName(pr, prefix);
-                }
-
-                if (Strings.isNullOrEmpty(ticketFullName)) {
-                    return "JIRA ticket will not be notified - " +
-                        "PR title \"" + (pr == null ? "" : pr.getTitle()) + "\" should starts with \"" + prefix + "NNNNN\"." +
-                        " Please, rename PR according to the" +
-                        " <a href='https://cwiki.apache.org/confluence/display/IGNITE/How+to+Contribute" +
-                        "#HowtoContribute-1.CreateGitHubpull-request'>contributing guide</a>" +
-                        " or use branch name according ticket name.";
-                }
-            }
-            catch (Exception e) {
-                return "JIRA ticket will not be notified after the tests are completed - " +
-                    "exception happened when server tried to get ticket ID from Pull Request [errMsg=" +
-                    e.getMessage() + ']';
-            }
-        } else {
-            //todo remove once every ticket is with Ignite prefix
-            ticketFullName = ticketFullName.toUpperCase().startsWith(prefix) ? ticketFullName : prefix + ticketFullName;
+        try {
+            ticketFullName = ticketMatcher.resolveTicketFromBranch(srvId, ticketFullName, branchForTc);
+        } catch (BranchTicketMatcher.TicketNotFoundException e) {
+            logger.info("", e);
+            return "JIRA ticket will not be notified after the tests are completed - " +
+                    "exception happened when server tried to get ticket ID from Pull Request [errMsg="
+                    + e.getMessage();
         }
 
         buildObserverProvider.get().observe(srvId, prov, ticketFullName, branchForTc, parentSuiteId, builds);
@@ -361,103 +317,28 @@ public class TcBotTriggerAndSignOffService {
         @QueryParam("suiteId") @Nullable String suiteId,
         @QueryParam("ticketId") @Nullable String ticketFullName,
         ICredentialsProv prov) {
-        String jiraRes = "";
-
-        String prefix = jiraIgnProv.server(srvId).ticketPrefix();
 
-        if (Strings.isNullOrEmpty(ticketFullName)) {
-            try {
-                ticketFullName = prLessTicket(srvId, branchForTc, prov, prefix);
-
-                PullRequest pr = null;
-
-                if (Strings.isNullOrEmpty(ticketFullName)) {
-                    pr = findPrForBranch(srvId, branchForTc);
-
-                    if (pr != null)
-                        ticketFullName = getTicketFullName(pr, prefix);
-                }
-
-                if (Strings.isNullOrEmpty(ticketFullName)) {
-                    jiraRes = "JIRA ticket can't be commented - " +
-                        "PR title \"" + (pr == null ? "" : pr.getTitle()) + "\" should starts with \"" + prefix + "NNNNN\"." +
-                        " Please, rename PR according to the" +
-                        " <a href='https://cwiki.apache.org/confluence/display/IGNITE/How+to+Contribute" +
-                        "#HowtoContribute-1.CreateGitHubpull-request'>contributing guide</a>" +
-                        " or use branch name according ticket name.";
-                }
-            }
-            catch (RuntimeException e) {
-                jiraRes = "Exception happened when server tried to get ticket ID from Pull Request - " + e.getMessage();
-            }
-        } else {
-            //todo remove once every ticket is with IGnite prefix
-            ticketFullName = ticketFullName.toUpperCase().startsWith(prefix) ? ticketFullName : prefix + ticketFullName;
+        try {
+            ticketFullName = ticketMatcher.resolveTicketFromBranch(srvId, ticketFullName, branchForTc);
+        } catch (BranchTicketMatcher.TicketNotFoundException e) {
+            logger.info("", e);
+            return new SimpleResult("JIRA wasn't commented.<br>" + e.getMessage());
         }
 
-        if (!Strings.isNullOrEmpty(ticketFullName)) {
-            BuildsInfo buildsInfo = new BuildsInfo(srvId, prov, ticketFullName, branchForTc, suiteId);
+        BuildsInfo buildsInfo = new BuildsInfo(srvId, prov, ticketFullName, branchForTc, suiteId);
 
-            VisaRequest lastVisaReq = visasHistStorage.getLastVisaRequest(buildsInfo.getContributionKey());
+        VisaRequest lastVisaReq = visasHistStorage.getLastVisaRequest(buildsInfo.getContributionKey());
 
-            if (Objects.nonNull(lastVisaReq) && lastVisaReq.isObserving())
-                return new SimpleResult("Jira wasn't commented." +
+        if (Objects.nonNull(lastVisaReq) && lastVisaReq.isObserving())
+            return new SimpleResult("Jira wasn't commented." +
                     " \"Re-run possible blockers & Comment JIRA\" was triggered for current branch." +
                     " Wait for the end or cancel exsiting observing.");
 
-            Visa visa = notifyJira(srvId, prov, suiteId, branchForTc, ticketFullName);
+        Visa visa = notifyJira(srvId, prov, suiteId, branchForTc, ticketFullName);
 
-            visasHistStorage.put(new VisaRequest(buildsInfo).setResult(visa));
+        visasHistStorage.put(new VisaRequest(buildsInfo).setResult(visa));
 
-            return new SimpleResult(visa.status);
-        }
-        else
-            return new SimpleResult("JIRA wasn't commented." + (!jiraRes.isEmpty() ? "<br>" + jiraRes : ""));
-    }
-
-    @Nullable public PullRequest findPrForBranch(
-        @Nullable @QueryParam("serverId") String srvId,
-        @Nullable @QueryParam("branchName") String branchForTc) {
-        Integer prId = IGitHubConnection.convertBranchToId(branchForTc);
-
-        if (prId == null)
-            return null;
-
-        IGitHubConnIgnited gh = gitHubConnIgnitedProvider.server(srvId);
-
-        return gh.getPullRequest(prId);
-    }
-
-    /**
-     * @param srvId Server id.
-     * @param branchForTc Branch for tc.
-     * @param prov Credentials Prov.
-     * @param ticketPrefix Ticket prefix.
-     */
-    @Nullable public String prLessTicket(@Nullable @QueryParam("serverId") String srvId,
-        String branchForTc, ICredentialsProv prov, String ticketPrefix) {
-        return prLessTicket(branchForTc, ticketPrefix, tcIgnitedProv.server(srvId, prov));
-    }
-
-    /**
-     * @param branchForTc Branch for tc.
-     * @param ticketPrefix Ticket prefix.
-     * @param tcIgn Tc ign.
-     */
-    @Nullable public static String prLessTicket(String branchForTc, String ticketPrefix, ITeamcityIgnited tcIgn) {
-        String branchPrefix = tcIgn.gitBranchPrefix();
-
-        if (!branchForTc.startsWith(branchPrefix))
-            return null;
-
-        try {
-            int ticketNum = Integer.parseInt(branchForTc.substring(branchPrefix.length()));
-
-            return ticketPrefix + ticketNum;
-        }
-        catch (NumberFormatException ignored) {
-        }
-        return null;
+        return new SimpleResult(visa.status);
     }
 
     /**
@@ -468,10 +349,14 @@ public class TcBotTriggerAndSignOffService {
         ICredentialsProv credsProv) {
         IJiraIgnited jiraIntegration = jiraIgnProv.server(srvId);
 
-        List<PullRequest> requests = gitHubConnIgnitedProvider.server(srvId).getPullRequests();
+        IGitHubConnIgnited gitHubConnIgnited = gitHubConnIgnitedProvider.server(srvId);
+        List<PullRequest> requests = gitHubConnIgnited.getPullRequests();
         if (requests == null)
             return null;
 
+        Set<Ticket> tickets = jiraIntegration.getTickets();
+
+        List<Ticket> paTickets = tickets.stream().filter(Ticket::isActiveContribution).collect(Collectors.toList());
 
         List<ContributionToCheck> contribsList = requests.stream().map(pr -> {
             ContributionToCheck check = new ContributionToCheck();
@@ -490,8 +375,9 @@ public class TcBotTriggerAndSignOffService {
                 check.prAuthorAvatarUrl = "";
             }
 
-            String prefix = jiraIntegration.ticketPrefix();
-            check.jiraIssueId = Strings.emptyToNull(getTicketFullName(pr, prefix));
+            IJiraServerConfig jiraCfg = jiraIntegration.config();
+
+            check.jiraIssueId = ticketMatcher.resolveTicketIdForPrBasedContrib(tickets, pr, jiraCfg);
 
             if (!Strings.isNullOrEmpty(check.jiraIssueId))
                 check.jiraIssueUrl = jiraIntegration.generateTicketUrl(check.jiraIssueId);
@@ -499,20 +385,19 @@ public class TcBotTriggerAndSignOffService {
             return check;
         }).collect(Collectors.toList());
 
-
-        Set<Ticket> tickets = jiraIntegration.getTickets();
-
-        List<Ticket> paTickets = tickets.stream().filter(Ticket::isActiveContribution).collect(Collectors.toList());
-
         ITeamcityIgnited tcIgn = tcIgnitedProv.server(srvId, credsProv);
 
         paTickets.forEach(ticket -> {
-            int ticketId = ticket.igniteId(jiraIntegration.ticketPrefix());
-            String branch = tcIgn.gitBranchPrefix() + ticketId;
+            String branch = ticketMatcher.resolveTcBranchForPrLess(ticket,
+                    jiraIntegration.config(),
+                    gitHubConnIgnited.config());
 
-            String defBtForMaster = findDefaultBranchBuildType(srvId);
+            if (branch == null)
+                return; // nothing to do if branch was not resolved
 
-            if(tcIgn.getAllBuildsCompacted(defBtForMaster, branch).isEmpty())
+            String defBtForMaster = findDefaultBranchBuildType(tcIgn.serverId());
+
+            if (tcIgn.getAllBuildsCompacted(defBtForMaster, branch).isEmpty())
                 return; //Skipping contributions without builds
 
             ContributionToCheck contribution = new ContributionToCheck();
@@ -521,8 +406,8 @@ public class TcBotTriggerAndSignOffService {
             contribution.jiraIssueUrl = jiraIntegration.generateTicketUrl( ticket.key);
             contribution.tcBranchName = branch;
 
-            contribution.prNumber = -ticketId;
-            contribution.prTitle = ""; //todo ticket title
+            contribution.prNumber = -ticket.keyWithoutProject(jiraIntegration.config().projectCodeForVisa());
+            contribution.prTitle = ticket.fields.summary;
             contribution.prHtmlUrl = "";
             contribution.prTimeUpdate = ""; //todo ticket updateTime
 
@@ -538,7 +423,7 @@ public class TcBotTriggerAndSignOffService {
     @Nonnull private List<BuildRefCompacted> findBuildsForPr(String suiteId, String prId,
         IGitHubConnIgnited ghConn, ITeamcityIgnited srv) {
 
-        List<BuildRefCompacted> buildHist = srv.getAllBuildsCompacted(suiteId, branchForTcDefault(prId, srv));
+        List<BuildRefCompacted> buildHist = srv.getAllBuildsCompacted(suiteId, branchForTcDefault(prId, ghConn));
 
         if (!buildHist.isEmpty())
             return buildHist;
@@ -570,7 +455,7 @@ public class TcBotTriggerAndSignOffService {
         return Collections.emptyList();
     }
 
-    private String branchForTcDefault(String prId, ITeamcityIgnited srv) {
+    private String branchForTcDefault(String prId, IGitHubConnIgnited srv) {
         Integer prNum = Integer.valueOf(prId);
         if (prNum < 0)
             return srv.gitBranchPrefix() + (-prNum); // Checking "ignite-10930" builds only
@@ -599,7 +484,7 @@ public class TcBotTriggerAndSignOffService {
 
         IGitHubConnIgnited ghConn = gitHubConnIgnitedProvider.server(srvId);
 
-        String defBtForMaster = findDefaultBranchBuildType(srvId);
+        String defBtForMaster = findDefaultBranchBuildType(teamcity.serverId());
 
         BuildTypeCompacted buildType = Strings.isNullOrEmpty(defBtForMaster)
             ? null
@@ -629,8 +514,8 @@ public class TcBotTriggerAndSignOffService {
             List<BuildRefCompacted> compBuilds = findBuildsForPr(btId, prId, ghConn, teamcity);
 
             statuses.add(compBuilds.isEmpty()
-                ? new ContributionCheckStatus(btId, branchForTcDefault(prId, teamcity))
-                : contributionStatus(srvId, btId, compBuilds, teamcity, prId));
+                ? new ContributionCheckStatus(btId, branchForTcDefault(prId, ghConn))
+                : contributionStatus(srvId, btId, compBuilds, teamcity, ghConn, prId));
         }
 
         return statuses;
@@ -639,7 +524,8 @@ public class TcBotTriggerAndSignOffService {
     @NotNull public String findDefaultBranchBuildType(String srvId) {
         StringBuilder buildTypeId = new StringBuilder();
 
-        HelperConfig.getTrackedBranches().get(DEFAULT_TRACKED_BRANCH_NAME)
+        cfg.getTrackedBranches()
+            .get(DEFAULT_TRACKED_BRANCH_NAME)
             .ifPresent(
                 b -> b.getChainsStream()
                     .filter(c -> Objects.equals(srvId, c.serverId))
@@ -654,9 +540,10 @@ public class TcBotTriggerAndSignOffService {
      * @param srvId Server id.
      * @param suiteId Suite id.
      * @param builds Build references.
+     * @param ghConn
      */
     public ContributionCheckStatus contributionStatus(String srvId, String suiteId, List<BuildRefCompacted> builds,
-        ITeamcityIgnited teamcity, String prId) {
+        ITeamcityIgnited teamcity, IGitHubConnIgnited ghConn, String prId) {
         ContributionCheckStatus status = new ContributionCheckStatus();
 
         status.suiteId = suiteId;
@@ -679,7 +566,7 @@ public class TcBotTriggerAndSignOffService {
             status.resolvedBranch = status.branchWithFinishedSuite;
             //todo take into account running/queued
         else
-            status.resolvedBranch = !builds.isEmpty() ? builds.get(0).branchName(compactor) : branchForTcDefault(prId, teamcity);
+            status.resolvedBranch = !builds.isEmpty() ? builds.get(0).branchName(compactor) : branchForTcDefault(prId, ghConn);
 
         String observationsStatus = buildObserverProvider.get().getObservationStatus(new ContributionKey(srvId, status.resolvedBranch));
 
@@ -878,5 +765,4 @@ public class TcBotTriggerAndSignOffService {
 
         return xmlEscapeText(res.toString());
     }
-
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/ITeamcityIgnited.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/ITeamcityIgnited.java
index df2099a..e1eca89 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/ITeamcityIgnited.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/ITeamcityIgnited.java
@@ -189,9 +189,6 @@ public interface ITeamcityIgnited {
 
     public List<String> getAllProjectsIds();
 
-    public String gitBranchPrefix();
-
-
     /**
      * Get list of teamcity agents. Never cached, request goes directly to pure TC.
      *
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/ITeamcityIgnitedProvider.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/ITeamcityIgnitedProvider.java
index 4c0fb56..0497527 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/ITeamcityIgnitedProvider.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/ITeamcityIgnitedProvider.java
@@ -18,14 +18,22 @@ package org.apache.ignite.ci.teamcity.ignited;
 
 import javax.annotation.Nullable;
 import org.apache.ignite.ci.user.ICredentialsProv;
+import org.apache.ignite.ci.web.rest.exception.ServiceUnauthorizedException;
 
 /**
  * Provides instance of particular cache-based teamcity connection.
  */
 public interface ITeamcityIgnitedProvider {
+    public boolean hasAccess(String srvId, @Nullable ICredentialsProv prov);
+
     /**
      * @param srvId Server id.
      * @param prov Prov.
      */
     public ITeamcityIgnited server(String srvId, @Nullable ICredentialsProv prov);
+
+    default void checkAccess(@Nullable String srvId, ICredentialsProv credsProv) {
+        if (!hasAccess(srvId, credsProv))
+            throw ServiceUnauthorizedException.noCreds(srvId);
+    }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TcIgnitedCachingProvider.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TcIgnitedCachingProvider.java
index ec1afc9..227b99e 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TcIgnitedCachingProvider.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TcIgnitedCachingProvider.java
@@ -25,6 +25,8 @@ import javax.annotation.Nullable;
 import javax.inject.Inject;
 import javax.inject.Provider;
 import org.apache.ignite.ci.ITeamcity;
+import org.apache.ignite.ci.tcbot.conf.ITcBotConfig;
+import org.apache.ignite.ci.tcbot.conf.ITcServerConfig;
 import org.apache.ignite.ci.teamcity.restcached.ITcServerProvider;
 import org.apache.ignite.ci.user.ICredentialsProv;
 import org.apache.ignite.ci.util.ExceptionUtil;
@@ -37,6 +39,9 @@ class TcIgnitedCachingProvider implements ITeamcityIgnitedProvider {
     @Inject
     private ITcServerProvider srvFactory;
 
+    /** Config. */
+    @Inject private ITcBotConfig cfg;
+
     @Inject private Provider<TeamcityIgnitedImpl> provider;
 
     private final Cache<String, ITeamcityIgnited> srvs
@@ -47,22 +52,37 @@ class TcIgnitedCachingProvider implements ITeamcityIgnitedProvider {
             .build();
 
     /** {@inheritDoc} */
-    @Override public ITeamcityIgnited server(String srvId, @Nullable ICredentialsProv prov) {
-        String fullKey = Strings.nullToEmpty(prov == null ? null : prov.getUser(srvId)) + ":" + Strings.nullToEmpty(srvId);
+    @Override public boolean hasAccess(String srvId, @Nullable ICredentialsProv prov) {
+        String ref = cfg.getTeamcityConfig(srvId).reference();
+
+        if (!Strings.isNullOrEmpty(ref))
+            return prov.hasAccess(ref);
+
+        return prov.hasAccess(srvId);
+    }
+
+    /** {@inheritDoc} */
+    @Override public ITeamcityIgnited server(String srvIdReq, @Nullable ICredentialsProv prov) {
+        ITcServerConfig cfg = this.cfg.getTeamcityConfig(srvIdReq);
+        String ref = cfg.reference();
+
+        String realSrvId = !Strings.isNullOrEmpty(ref) && !srvIdReq.equals(ref) ? ref : srvIdReq;
+
+        String fullKey = Strings.nullToEmpty(prov == null ? null : prov.getUser(realSrvId)) + ":" + Strings.nullToEmpty(realSrvId);
 
         try {
             return srvs.get(fullKey, () -> {
-                ITeamcity tcRealConn = srvFactory.server(srvId, prov);
+                ITeamcity tcRealConn = srvFactory.server(realSrvId, prov);
 
                 if (prov != null) {
-                    final String user = prov.getUser(srvId);
-                    final String pwd = prov.getPassword(srvId);
+                    final String user = prov.getUser(realSrvId);
+                    final String pwd = prov.getPassword(realSrvId);
                     tcRealConn.setAuthData(user, pwd);
                 }
 
                 TeamcityIgnitedImpl impl = provider.get();
 
-                impl.init(srvId, tcRealConn);
+                impl.init(realSrvId, tcRealConn);
 
                 return impl;
             });
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedImpl.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedImpl.java
index 79aae65..b38350e 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedImpl.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedImpl.java
@@ -419,11 +419,6 @@ public class TeamcityIgnitedImpl implements ITeamcityIgnited {
         return conn.getProjects().stream().map(Project::id).collect(Collectors.toList());
     }
 
-    /** {@inheritDoc} */
-    @Override public String gitBranchPrefix() {
-        return conn.gitBranchPrefix();
-    }
-
     @Override
     public List<Agent> agents(boolean connected, boolean authorized) {
         return conn.agents(connected, authorized);
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/TestCompacted.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/TestCompacted.java
index b5a8e36..cdd99c7 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/TestCompacted.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/fatbuild/TestCompacted.java
@@ -20,14 +20,11 @@ package org.apache.ignite.ci.teamcity.ignited.fatbuild;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Objects;
 import com.google.common.base.Strings;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.BitSet;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
 import org.apache.ignite.ci.analysis.RunStat;
+import org.apache.ignite.ci.tcbot.common.StringFieldCompacted;
 import org.apache.ignite.ci.tcmodel.hist.BuildRef;
 import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrence;
 import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrenceFull;
@@ -201,17 +198,9 @@ public class TestCompacted {
             return new String(details, StandardCharsets.UTF_8);
         else if (!flag1 && flag2) {
             try {
-                final ByteArrayInputStream in = new ByteArrayInputStream(details);
-                ByteArrayOutputStream bos = new ByteArrayOutputStream();
-                try (final GZIPInputStream gzi = new GZIPInputStream(in)) {
-                    byte[] outbuf = new byte[details.length];
-                    int len;
-                    while ((len = gzi.read(outbuf, 0, outbuf.length)) != -1)
-                        bos.write(outbuf, 0, len);
-                }
-
-                return new String(bos.toByteArray(), StandardCharsets.UTF_8);
-            } catch (Exception e) {
+                return StringFieldCompacted.unzipToString(details);
+            }
+            catch (Exception e) {
                 logger.error("GZip.uncompress failed: " + e.getMessage(), e);
                 return null;
             }
@@ -243,18 +232,14 @@ public class TestCompacted {
         }
 
         try {
-            final ByteArrayOutputStream out = new ByteArrayOutputStream();
-            try(final GZIPOutputStream gzipOutputStream = new GZIPOutputStream(out)) {
-                gzipOutputStream.write(uncompressed);
-            }
-            gzip = out.toByteArray();
+            gzip = StringFieldCompacted.zipBytes(uncompressed);
         }
         catch (Exception e) {
             logger.error("Snappy.compress failed: " + e.getMessage(), e);
         }
 
-        final int snappyLen = snappy!=null ? snappy.length : -1;
-        final int gzipLen = gzip!=null ? gzip.length : -1;
+        final int snappyLen = snappy != null ? snappy.length : -1;
+        final int gzipLen = gzip != null ? gzip.length : -1;
 
         flags.set(COMPRESS_TYPE_FLAG1, true);
         flags.set(COMPRESS_TYPE_FLAG2, false);
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/pure/ITeamcityConn.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/pure/ITeamcityConn.java
index 5af968f..d0e38dd 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/pure/ITeamcityConn.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/pure/ITeamcityConn.java
@@ -52,8 +52,15 @@ public interface ITeamcityConn {
      */
     public String host();
 
+    /**
+     * @param buildId Build id.
+     */
     public Build getBuild(int buildId);
 
+    /**
+     * @param fullUrl Full url.
+     * @param nextPage Next page.
+     */
     public List<BuildRef> getBuildRefsPage(String fullUrl, AtomicReference<String> nextPage);
 
     /**
@@ -82,13 +89,25 @@ public interface ITeamcityConn {
      */
     public Build triggerBuild(String buildTypeId, @Nonnull String branchName, boolean cleanRebuild, boolean queueAtTop);
 
-    ProblemOccurrences getProblems(int buildId);
+    /**
+     * @param buildId Build id.
+     */
+    public ProblemOccurrences getProblems(int buildId);
 
-    Statistics getStatistics(int buildId);
+    /**
+     * @param buildId Build id.
+     */
+    public Statistics getStatistics(int buildId);
 
-    ChangesList getChangesList(int buildId);
+    /**
+     * @param buildId Build id.
+     */
+    public ChangesList getChangesList(int buildId);
 
-    Change getChange(int changeId);
+    /**
+     * @param changeId Change id.
+     */
+    public Change getChange(int changeId);
 
     /**
      * List of project suites.
@@ -96,23 +115,18 @@ public interface ITeamcityConn {
      * @param projectId Project id.
      * @return List of buildType's references.
      */
-    List<BuildType> getBuildTypes(String projectId);
+    public List<BuildType> getBuildTypes(String projectId);
 
     /**
      * @param buildTypeId BuildType id.
      * @return BuildType.
      */
-    BuildTypeFull getBuildType(String buildTypeId);
+    public BuildTypeFull getBuildType(String buildTypeId);
 
     /**
      * @return List of all project available at Teamcity server.
      */
-    List<Project> getProjects();
-
-    /**
-     * @return Branch name mandatory prefix for all PR-less contributions, e.g. "ignite-".
-     */
-    public String gitBranchPrefix();
+    public List<Project> getProjects();
 
     /**
      * Get list of teamcity agents.
@@ -121,5 +135,5 @@ public interface ITeamcityConn {
      * @param authorized Authorized flag.
      * @return List of teamcity agents.
      */
-    List<Agent> agents(boolean connected, boolean authorized);
+    public List<Agent> agents(boolean connected, boolean authorized);
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/ChainAtServerCurrentStatus.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/ChainAtServerCurrentStatus.java
index 8c12e42..9ffcf73 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/ChainAtServerCurrentStatus.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/ChainAtServerCurrentStatus.java
@@ -33,16 +33,14 @@ import org.apache.ignite.ci.github.PullRequest;
 import org.apache.ignite.ci.github.ignited.IGitHubConnIgnited;
 import org.apache.ignite.ci.github.pure.IGitHubConnection;
 import org.apache.ignite.ci.jira.pure.IJiraIntegration;
-import org.apache.ignite.ci.tcbot.visa.TcBotTriggerAndSignOffService;
+import org.apache.ignite.ci.tcbot.visa.BranchTicketMatcher;
 import org.apache.ignite.ci.tcmodel.conf.BuildType;
 import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnited;
 import org.apache.ignite.ci.util.CollectionUtil;
 import org.apache.ignite.internal.util.typedef.T2;
 
 import static org.apache.ignite.ci.util.UrlUtil.escape;
-import static org.apache.ignite.ci.web.model.current.SuiteCurrentStatus.branchForLink;
-import static org.apache.ignite.ci.web.model.current.SuiteCurrentStatus.createOccurForLogConsumer;
-import static org.apache.ignite.ci.web.model.current.SuiteCurrentStatus.createOrrucForLongRun;
+import static org.apache.ignite.ci.web.model.current.SuiteCurrentStatus.*;
 
 /**
  * Represent Run All chain results/ or RunAll+latest re-runs.
@@ -51,7 +49,7 @@ import static org.apache.ignite.ci.web.model.current.SuiteCurrentStatus.createOr
  */
 @SuppressWarnings({"WeakerAccess", "PublicField"})
 public class ChainAtServerCurrentStatus {
-    /** {@link BuildType#name} */
+    /** {@link BuildType#getName()} */
     public String chainName;
 
     /** Server ID. */
@@ -123,29 +121,31 @@ public class ChainAtServerCurrentStatus {
     }
 
     /** */
-    public void initJiraAndGitInfo(ITeamcityIgnited tcIgnited,
-        IJiraIntegration jiraIntegration, IGitHubConnIgnited gitHubConnIgnited) {
-        Integer prNum = IGitHubConnection.convertBranchToId(branchName);
-
-        String prUrl = null;
+    public void initJiraAndGitInfo(BranchTicketMatcher ticketMatcher,
+                                   IJiraIntegration jiraIntegration,
+                                   IGitHubConnIgnited gitHubConnIgnited) {
 
         String ticketFullName = null;
+        try {
+            ticketFullName = ticketMatcher
+                    .resolveTicketFromBranch(jiraIntegration.getServiceId(),
+                            null,
+                            branchName);
+        } catch (BranchTicketMatcher.TicketNotFoundException ignore) {
+        }
 
-        String ticketUrl = null;
+        Integer prNum = IGitHubConnection.convertBranchToPrId(branchName);
 
-        String ticketPrefix = jiraIntegration.ticketPrefix();
+        String prUrl = null;
+        String ticketUrl = null;
 
         if (prNum != null) {
             PullRequest pullReq = gitHubConnIgnited.getPullRequest(prNum);
 
             if (pullReq != null && pullReq.getTitle() != null) {
                 prUrl = pullReq.htmlUrl();
-
-                ticketFullName = TcBotTriggerAndSignOffService.getTicketFullName(pullReq, ticketPrefix);
             }
         }
-        else
-            ticketFullName = TcBotTriggerAndSignOffService.prLessTicket(branchName, ticketPrefix, tcIgnited);
 
         if (!Strings.isNullOrEmpty(ticketFullName))
             ticketUrl = jiraIntegration.generateTicketUrl(ticketFullName);
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/UpdateInfo.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/UpdateInfo.java
index 17624a5..5de0f23 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/UpdateInfo.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/UpdateInfo.java
@@ -64,7 +64,7 @@ import org.apache.ignite.ci.jira.pure.IJiraIntegration;
         if (gitHubConn.isGitTokenAvailable())
             javaFlags |= GITHUB_FLAG;
 
-        if (jiraIntegration.isJiraTokenAvailable())
+        if (jiraIntegration.config().isJiraTokenAvailable())
             javaFlags |= JIRA_FLAG;
     }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/GetBuildLog.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/GetBuildLog.java
index 0a141d9..03e00fb 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/GetBuildLog.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/GetBuildLog.java
@@ -64,8 +64,7 @@ public class GetBuildLog {
     @PermitAll
     public Response getThreadDump(
         @QueryParam(SERVER_ID) String srvId,
-        @QueryParam(BUILD_NO) Integer buildNo,
-        @Deprecated @QueryParam(FILE_IDX) Integer fileIdx) {
+        @QueryParam(BUILD_NO) Integer buildNo) {
 
         ITcServerProvider helper = CtxListener.getInjector(ctx).getInstance(ITcServerProvider.class);
         ITcAnalytics srv = helper.server(srvId, null);
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/GetTrackedBranches.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/GetTrackedBranches.java
index 1d20811..ea62194 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/GetTrackedBranches.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/GetTrackedBranches.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.ci.web.rest;
 
 import com.google.common.base.Strings;
+import com.google.inject.Injector;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -31,10 +32,10 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
-import org.apache.ignite.ci.HelperConfig;
 import org.apache.ignite.ci.conf.ChainAtServer;
 import org.apache.ignite.ci.tcbot.TcBotGeneralService;
 import org.apache.ignite.ci.tcbot.conf.ITcBotConfig;
+import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnitedProvider;
 import org.apache.ignite.ci.user.ICredentialsProv;
 import org.apache.ignite.ci.web.CtxListener;
 import org.apache.ignite.ci.web.model.Version;
@@ -76,7 +77,9 @@ public class GetTrackedBranches {
     public Set<ChainAtServer> getSuites(@Nullable @QueryParam("server") String srvId) {
         final ICredentialsProv prov = ICredentialsProv.get(req);
 
-        return HelperConfig.getTrackedBranches()
+        Injector injector = CtxListener.getInjector(ctx);
+        ITcBotConfig cfg = injector.getInstance(ITcBotConfig.class);
+        return cfg.getTrackedBranches()
             .getSuitesUnique()
             .stream()
             .filter(chainAtSrv ->
@@ -86,16 +89,26 @@ public class GetTrackedBranches {
             .collect(Collectors.toSet());
     }
 
+    /**
+     * Return all servers registered in TC Bot config: Both from tracked branches and from
+     */
     @GET
     @Path("getServerIds")
     public Set<String> getServerIds() {
         final ICredentialsProv prov = ICredentialsProv.get(req);
 
-        return HelperConfig.getTrackedBranches()
-                .getServerIds()
-                .stream()
-                .filter(prov::hasAccess)
-                .collect(Collectors.toSet());
+        Injector injector = CtxListener.getInjector(ctx);
+        ITcBotConfig cfg = injector.getInstance(ITcBotConfig.class);
+
+        ITeamcityIgnitedProvider tcProv = injector.getInstance(ITeamcityIgnitedProvider.class);
+        return cfg.getServerIds()
+            .stream()
+            .filter(srvId ->
+            {
+                return tcProv.hasAccess(srvId, prov);
+
+            })
+            .collect(Collectors.toSet());
     }
 
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuilds.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuilds.java
index 5f4031c..5c527ba 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuilds.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuilds.java
@@ -125,7 +125,7 @@ public class TriggerBuilds {
 
             IGitHubConnection gh = injector.getInstance(IGitHubConnectionProvider.class).server(srvId);
 
-            return new ServerIntegrationLinks(srvId, gh.gitApiUrl(), jira.getJiraApiUrl());
+            return new ServerIntegrationLinks(srvId, gh.gitApiUrl(), jira.restApiUrl());
         }).filter(Objects::nonNull).collect(Collectors.toSet());
     }
 }
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/exception/ServiceUnauthorizedException.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/exception/ServiceUnauthorizedException.java
index f59be5d..80e4b5a 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/exception/ServiceUnauthorizedException.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/exception/ServiceUnauthorizedException.java
@@ -32,8 +32,8 @@ public class ServiceUnauthorizedException extends RuntimeException
     public ServiceUnauthorizedException() {
     }
 
-    public static ServiceUnauthorizedException noCreds(String serverId) {
-        return new ServiceUnauthorizedException("Service [" + serverId + "] is not available for current user");
+    public static ServiceUnauthorizedException noCreds(String srvId) {
+        return new ServiceUnauthorizedException("Service [" + srvId + "] is not available for current user");
     }
 
     @Override
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/login/Login.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/login/Login.java
index f6f62d1..f508aeb 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/login/Login.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/login/Login.java
@@ -58,7 +58,7 @@ public class Login {
         Injector injector = CtxListener.getInjector(ctx);
 
         ITcBotConfig tcBotCfg = injector.getInstance(ITcBotConfig.class);
-        String srvId = tcBotCfg.primaryServerId();
+        String srvId = tcBotCfg.primaryServerCode();
         String host = injector.getInstance(ITeamcityIgnitedProvider.class).server(srvId, null).host();
         return new ServerDataResponse(host);
     }
@@ -76,11 +76,10 @@ public class Login {
         final ITcLogin tcLogin = injector.getInstance(ITcLogin.class);
         IUserStorage users = injector.getInstance(IUserStorage.class);
 
-        String primarySrvId = cfg.primaryServerId();
+        String primarySrvCode = cfg.primaryServerCode();
 
         try {
-            return doLogin(username, pwd, users, primarySrvId,
-                cfg.getServerIds(), tcLogin);
+            return doLogin(username, pwd, users, primarySrvCode, cfg.getServerIds(), tcLogin);
         } catch (Exception e) {
             e.printStackTrace();
             throw e;
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/pr/GetPrTestFailures.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/pr/GetPrTestFailures.java
index 7745e73..9a7bbba 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/pr/GetPrTestFailures.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/pr/GetPrTestFailures.java
@@ -17,7 +17,6 @@
 
 package org.apache.ignite.ci.web.rest.pr;
 
-import com.google.common.base.Preconditions;
 import com.google.inject.Injector;
 import javax.annotation.Nonnull;
 import javax.servlet.ServletContext;
@@ -142,7 +141,7 @@ public class GetPrTestFailures {
         PullRequest pr;
 
         try {
-            Integer prId = IGitHubConnection.convertBranchToId(branchForTc);
+            Integer prId = IGitHubConnection.convertBranchToPrId(branchForTc);
 
             if (prId == null)
                 return "Invalid TC branch name: [" + branchForTc + "]";
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/tracked/GetTrackedBranchTestResults.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/tracked/GetTrackedBranchTestResults.java
index de1d0a8..e8a9202 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/tracked/GetTrackedBranchTestResults.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/tracked/GetTrackedBranchTestResults.java
@@ -42,7 +42,6 @@ import org.apache.ignite.internal.util.typedef.F;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import static org.apache.ignite.ci.tcbot.conf.ITcBotConfig.DEFAULT_SERVER_ID;
 import static org.apache.ignite.ci.teamcity.ignited.TeamcityIgnitedImpl.DEFAULT_PROJECT_ID;
 
 @Path(GetTrackedBranchTestResults.TRACKED)
@@ -156,7 +155,7 @@ public class GetTrackedBranchTestResults {
         ITcBotConfig cfg = CtxListener.getInjector(ctx).getInstance(ITcBotConfig.class);
 
         if (F.isEmpty(srvId))
-            srvId = DEFAULT_SERVER_ID;
+            srvId = cfg.primaryServerCode();
 
         if (F.isEmpty(projectId))
             projectId = DEFAULT_PROJECT_ID;
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/visa/TcBotVisaService.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/visa/TcBotVisaService.java
index f7bfa78..a3d94cb 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/visa/TcBotVisaService.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/visa/TcBotVisaService.java
@@ -16,6 +16,7 @@
  */
 package org.apache.ignite.ci.web.rest.visa;
 
+import com.google.inject.Injector;
 import java.util.Collection;
 import java.util.List;
 import java.util.Set;
@@ -34,6 +35,7 @@ import org.apache.ignite.ci.tcbot.visa.ContributionToCheck;
 import org.apache.ignite.ci.tcbot.visa.CurrentVisaStatus;
 import org.apache.ignite.ci.tcbot.visa.TcBotTriggerAndSignOffService;
 import org.apache.ignite.ci.tcbot.visa.VisaStatus;
+import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnitedProvider;
 import org.apache.ignite.ci.user.ICredentialsProv;
 import org.apache.ignite.ci.web.CtxListener;
 import org.apache.ignite.ci.web.model.ContributionKey;
@@ -82,11 +84,11 @@ public class TcBotVisaService {
     public List<ContributionToCheck> contributions(@Nullable @QueryParam("serverId") String srvId) {
         ICredentialsProv credsProv = ICredentialsProv.get(req);
 
-        if (!credsProv.hasAccess(srvId))
-            throw ServiceUnauthorizedException.noCreds(srvId);
+        Injector injector = CtxListener.getInjector(ctx);
 
-        return CtxListener.getInjector(ctx)
-            .getInstance(TcBotTriggerAndSignOffService.class).getContributionsToCheck(srvId, credsProv);
+        injector.getInstance(ITeamcityIgnitedProvider.class).checkAccess(srvId, credsProv);
+
+        return injector.getInstance(TcBotTriggerAndSignOffService.class).getContributionsToCheck(srvId, credsProv);
     }
 
     @GET
@@ -94,11 +96,12 @@ public class TcBotVisaService {
     public Set<ContributionCheckStatus> contributionStatus(@Nullable @QueryParam("serverId") String srvId,
         @QueryParam("prId") String prId) {
         ICredentialsProv prov = ICredentialsProv.get(req);
-        if (!prov.hasAccess(srvId))
-            throw ServiceUnauthorizedException.noCreds(srvId);
 
-        return CtxListener.getInjector(ctx)
-            .getInstance(TcBotTriggerAndSignOffService.class).contributionStatuses(srvId, prov, prId);
+        Injector injector = CtxListener.getInjector(ctx);
+
+        injector.getInstance(ITeamcityIgnitedProvider.class).checkAccess(srvId, prov);
+
+        return injector.getInstance(TcBotTriggerAndSignOffService.class).contributionStatuses(srvId, prov, prId);
     }
 
     @GET
diff --git a/ignite-tc-helper-web/src/main/webapp/js/prs-1.1.js b/ignite-tc-helper-web/src/main/webapp/js/prs-1.1.js
index 2ecb6fc..1328764 100644
--- a/ignite-tc-helper-web/src/main/webapp/js/prs-1.1.js
+++ b/ignite-tc-helper-web/src/main/webapp/js/prs-1.1.js
@@ -17,7 +17,7 @@ function drawTable(srvId, element) {
         "        </table>\n");
 }
 
-function requestTableForServer(srvId, suiteIdIgnored, element) {
+function requestTableForServer(srvId, element) {
 
     let tableId = "serverContributions-" + srvId;
 
diff --git a/ignite-tc-helper-web/src/main/webapp/prs.html b/ignite-tc-helper-web/src/main/webapp/prs.html
index 1eabc89..6989a00 100644
--- a/ignite-tc-helper-web/src/main/webapp/prs.html
+++ b/ignite-tc-helper-web/src/main/webapp/prs.html
@@ -61,9 +61,7 @@
 
         function showServerContribTabs(result) {
             const processed = new Set();
-            for (let chainAtServer of result) {
-                let serverId = chainAtServer.serverId;
-
+            for (let serverId of result) {
                 if (processed.has(serverId))
                     continue;
 
@@ -82,7 +80,7 @@
 
                 tabsInsertion.after("<div id=\"" + tabId + "\">" + "</div>");
 
-                requestTableForServer(serverId, chainAtServer.suiteId, $(tabRef));
+                requestTableForServer(serverId, $(tabRef));
             }
 
             showTabs();
@@ -104,7 +102,14 @@
                     showSuitesForTeamCityRunData(result);
                     showFormAndSuitesForPrCheck(result);
                     showCommentJiraForm(result);
+                },
+                error: showErrInLoadStatus
+            });
 
+            $.ajax({
+                url: "rest/branches/getServerIds",
+                success: function(result) {
+                    $("#loadStatus").html("");
                     showServerContribTabs(result);
                 },
                 error: showErrInLoadStatus
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 77fd7c2..e4bfdec 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
@@ -17,11 +17,14 @@
 
 package org.apache.ignite.ci.tcbot.chain;
 
-import com.google.common.base.Preconditions;
 import com.google.inject.AbstractModule;
 import com.google.inject.internal.SingletonScope;
+import java.io.File;
+import java.util.Properties;
+
+import org.apache.ignite.ci.HelperConfig;
 import org.apache.ignite.ci.IAnalyticsEnabledTeamcity;
-import org.apache.ignite.ci.conf.BranchesTracked;
+import org.apache.ignite.ci.tcbot.conf.BranchesTracked;
 import org.apache.ignite.ci.github.PullRequest;
 import org.apache.ignite.ci.github.ignited.IGitHubConnIgnited;
 import org.apache.ignite.ci.github.ignited.IGitHubConnIgnitedProvider;
@@ -29,7 +32,7 @@ import org.apache.ignite.ci.github.pure.IGitHubConnection;
 import org.apache.ignite.ci.github.pure.IGitHubConnectionProvider;
 import org.apache.ignite.ci.jira.pure.IJiraIntegration;
 import org.apache.ignite.ci.jira.pure.IJiraIntegrationProvider;
-import org.apache.ignite.ci.tcbot.conf.ITcBotConfig;
+import org.apache.ignite.ci.tcbot.conf.*;
 import org.apache.ignite.ci.tcbot.issue.IIssuesStorage;
 import org.apache.ignite.ci.tcbot.user.IUserStorage;
 import org.apache.ignite.ci.teamcity.ignited.IStringCompactor;
@@ -98,13 +101,35 @@ public class MockBasedTcBotModule extends AbstractModule {
         bind(ITcServerProvider.class).toInstance(tcSrvOldProv);
 
         bind(ITcBotConfig.class).toInstance(new ITcBotConfig() {
-            @Override public String primaryServerId() {
-                return ITcBotConfig.DEFAULT_SERVER_ID;
+            @Override public String primaryServerCode() {
+                return ITcBotConfig.DEFAULT_SERVER_CODE;
             }
 
             @Override public BranchesTracked getTrackedBranches() {
                 return tracked;
             }
+
+            /** {@inheritDoc} */
+            @Override public ITcServerConfig getTeamcityConfig(String srvCode) {
+                return new TcServerConfig(srvCode, loadOldProps(srvCode));
+            }
+
+            @Override public IJiraServerConfig getJiraConfig(String srvCode) {
+                return new JiraServerConfig(srvCode, loadOldProps(srvCode));
+            }
+
+            @Override
+            public IGitHubConfig getGitConfig(String srvCode) {
+                return new GitHubConfig(srvCode, loadOldProps(srvCode));
+            }
+
+            private Properties loadOldProps(String srvCode) {
+                File workDir = HelperConfig.resolveWorkDir();
+
+                String cfgName = HelperConfig.prepareConfigName(srvCode);
+
+                return HelperConfig.loadAuthProperties(workDir, cfgName);
+            }
         });
 
         bind(IIssuesStorage.class).toInstance(Mockito.mock(IIssuesStorage.class));
diff --git a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/TrackedBranchProcessorTest.java b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/TrackedBranchProcessorTest.java
index 3164e7d..87e35ed 100644
--- a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/TrackedBranchProcessorTest.java
+++ b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/chain/TrackedBranchProcessorTest.java
@@ -27,7 +27,7 @@ import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import org.apache.ignite.ci.ITeamcity;
 import org.apache.ignite.ci.conf.BranchTracked;
-import org.apache.ignite.ci.conf.BranchesTracked;
+import org.apache.ignite.ci.tcbot.conf.BranchesTracked;
 import org.apache.ignite.ci.conf.ChainAtServerTracked;
 import org.apache.ignite.ci.teamcity.ignited.IStringCompactor;
 import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnitedProvider;
diff --git a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/issue/IssueDetectorTest.java b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/issue/IssueDetectorTest.java
index a1b6f59..d519ef4 100644
--- a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/issue/IssueDetectorTest.java
+++ b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/tcbot/issue/IssueDetectorTest.java
@@ -29,7 +29,7 @@ import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import org.apache.ignite.ci.ITeamcity;
 import org.apache.ignite.ci.conf.BranchTracked;
-import org.apache.ignite.ci.conf.BranchesTracked;
+import org.apache.ignite.ci.tcbot.conf.BranchesTracked;
 import org.apache.ignite.ci.conf.ChainAtServerTracked;
 import org.apache.ignite.ci.tcbot.chain.MockBasedTcBotModule;
 import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrenceFull;
diff --git a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedMock.java b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedMock.java
index b5b1ace..159f336 100644
--- a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedMock.java
+++ b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedMock.java
@@ -116,7 +116,7 @@ public class TeamcityIgnitedMock {
                     return runHistCompacted;
                 });
 
-        when(tcIgnited.gitBranchPrefix()).thenReturn("ignite-");
+        // when(tcIgnited.gitBranchPrefix()).thenReturn("ignite-");
 
         return tcIgnited;
     }
diff --git a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedProviderMock.java b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedProviderMock.java
index f9f0eca..d484a13 100644
--- a/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedProviderMock.java
+++ b/ignite-tc-helper-web/src/test/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedProviderMock.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.ci.teamcity.ignited;
 
+import javax.annotation.Nullable;
 import org.apache.ignite.ci.teamcity.ignited.fatbuild.FatBuildCompacted;
 import org.apache.ignite.ci.user.ICredentialsProv;
 
@@ -25,6 +26,7 @@ import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class TeamcityIgnitedProviderMock implements ITeamcityIgnitedProvider {
+    /** Compactor. */
     @Inject IStringCompactor compactor;
 
     private Map<String, Map<Integer, FatBuildCompacted>> tcBuildsData = new ConcurrentHashMap<>();
@@ -33,8 +35,13 @@ public class TeamcityIgnitedProviderMock implements ITeamcityIgnitedProvider {
         tcBuildsData.put(srvId, apacheBuilds);
     }
 
-    @Override
-    public ITeamcityIgnited server(String srvId, ICredentialsProv prov) {
+    /** {@inheritDoc} */
+    @Override public boolean hasAccess(String srvId, @Nullable ICredentialsProv prov) {
+        return prov.hasAccess(srvId);
+    }
+
+    /** {@inheritDoc} */
+    @Override public ITeamcityIgnited server(String srvId, ICredentialsProv prov) {
         final Map<Integer, FatBuildCompacted> integerFatBuildCompactedMap = tcBuildsData.get(srvId);
 
         return TeamcityIgnitedMock.getMutableMapTeamcityIgnited(integerFatBuildCompactedMap, compactor);