| /* |
| * 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 java.util.Collection; |
| import java.util.Objects; |
| import javax.inject.Inject; |
| import javax.ws.rs.QueryParam; |
| 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; |
| |
| /** |
| * |
| */ |
| 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 gitHubCfg) { |
| 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 gitHubCfg.gitBranchPrefix() + ticketId; |
| } |
| |
| String branchJiraIdentification = findFixPrefixedNoInValues(branchNumPrefix, |
| ticket.key, |
| ticket.fields.summary, |
| ticket.fields.customfield_11050); |
| |
| return convertJiraToGit(branchJiraIdentification, branchNumPrefix, gitHubCfg); |
| |
| } |
| |
| /** |
| * 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 gitHubCfg GH connection config. |
| */ |
| private String convertJiraToGit(String branchJiraIdentification, |
| String branchNumPrefix, |
| IGitHubConfig gitHubCfg) { |
| if (Strings.isNullOrEmpty(branchJiraIdentification)) |
| return null; |
| |
| return gitHubCfg.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); |
| } |
| |
| /** |
| * @param srvCode Server code. |
| * @param branchNum Branch number to be checked. |
| */ |
| @SuppressWarnings("WeakerAccess") |
| @GuavaCached(maximumSize = 3000, expireAfterWriteSecs = 60, cacheNullRval = true) |
| protected String findTicketMentions(String srvCode, @Nullable String branchNum) { |
| return findTicketMentions(jiraIgnProv.server(srvCode).getTickets(), branchNum); |
| } |
| |
| /** |
| * @param tickets Tickets. |
| * @param branchNum Branch number to be checked. |
| */ |
| @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)); |
| } |
| |
| /** |
| * @param tickets Tickets. |
| * @param branchNum Branch number to be checked. |
| */ |
| @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 fixPrefixedNum = findFixPrefixedNumber(value, prefix); |
| |
| if (fixPrefixedNum != null) |
| return fixPrefixedNum; |
| } |
| return null; |
| } |
| |
| /** |
| * @param val 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 val, @NotNull String prefix) { |
| if (Strings.isNullOrEmpty(val)) |
| return null; |
| |
| int idxOfBranchNum = val.toUpperCase().indexOf(prefix.toUpperCase()); |
| |
| if (idxOfBranchNum < 0) |
| return null; |
| |
| int beginIdx = prefix.length() + idxOfBranchNum; |
| int endIdx = beginIdx; |
| |
| while (endIdx < val.length() && Character.isDigit(val.charAt(endIdx))) |
| endIdx++; |
| |
| if (endIdx == beginIdx) |
| return null; |
| |
| return prefix + val.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 gitCfg = 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, gitCfg); |
| 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; |
| } |
| } |