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 efdd0c7 IGNITE-9939 [Tc Bot] Add visas caсhing and monitoring[D[F - Fixes #40. efdd0c7 is described below commit efdd0c7e0dbda0ae05edbf42775330133b1f7180 Author: ololo3000 <pmgheap....@gmail.com> AuthorDate: Sat Nov 10 16:05:50 2018 +0300 IGNITE-9939 [Tc Bot] Add visas caсhing and monitoring[D[F - Fixes #40. Signed-off-by: Dmitriy Pavlov <dpav...@apache.org> --- .../main/java/org/apache/ignite/ci/ITcHelper.java | 5 +- .../main/java/org/apache/ignite/ci/TcHelper.java | 139 ++++++++++++------- .../org/apache/ignite/ci/di/IgniteTcBotModule.java | 3 +- .../apache/ignite/ci/jira/IJiraIntegration.java | 3 +- .../apache/ignite/ci/observer/BuildObserver.java | 19 ++- .../org/apache/ignite/ci/observer/BuildsInfo.java | 99 ++++++++++++-- .../ignite/ci/observer/CompactBuildsInfo.java | 95 +++++++++++++ .../apache/ignite/ci/observer/ObserverTask.java | 68 +++++++--- .../tcbot/visa/TcBotTriggerAndSignOffService.java | 72 +++++++++- .../visa/VisaStatus.java} | 39 +++--- .../apache/ignite/ci/tcmodel/hist/BuildRef.java | 5 + .../ci/web/model/CompactContributionKey.java | 62 +++++++++ .../model/CompactVisa.java} | 38 +++--- .../model/CompactVisaRequest.java} | 38 +++--- .../model/ContributionKey.java} | 32 ++--- .../model/JiraCommentResponse.java} | 25 ++-- .../java/org/apache/ignite/ci/web/model/Visa.java | 84 ++++++++++++ .../model/VisaRequest.java} | 48 ++++--- .../ci/web/model/hist/VisasHistoryStorage.java | 118 ++++++++++++++++ .../ignite/ci/web/rest/visa/TcBotVisaService.java | 16 ++- .../src/main/webapp/js/common-1.6.js | 3 +- ignite-tc-helper-web/src/main/webapp/visas.html | 150 +++++++++++++++++++++ 22 files changed, 966 insertions(+), 195 deletions(-) diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITcHelper.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITcHelper.java index 25a5bd0..481ffa1 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITcHelper.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITcHelper.java @@ -24,6 +24,7 @@ import org.apache.ignite.ci.issue.IssuesStorage; import org.apache.ignite.ci.teamcity.restcached.ITcServerProvider; import org.apache.ignite.ci.user.ICredentialsProv; import org.apache.ignite.ci.user.UserAndSessionsStorage; +import org.apache.ignite.ci.web.model.Visa; /** * Teamcity Bot main interface. This inteface became too huge. @@ -67,7 +68,7 @@ public interface ITcHelper extends ITcServerProvider { * @param buildTypeId Suite name. * @param branchForTc Branch for TeamCity. * @param ticket JIRA ticket full name. - * @return {@code True} if JIRA was notified. + * @return {@code Visa} which contains info about JIRA notification. */ - String notifyJira(String srvId, ICredentialsProv prov, String buildTypeId, String branchForTc, String ticket); + Visa notifyJira(String srvId, ICredentialsProv prov, String buildTypeId, String branchForTc, String ticket); } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/TcHelper.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/TcHelper.java index b40ba69..3c56b4e 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/TcHelper.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/TcHelper.java @@ -17,12 +17,15 @@ package org.apache.ignite.ci; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.ignite.ci.tcbot.chain.PrChainsProcessor; import org.apache.ignite.ci.conf.BranchesTracked; import org.apache.ignite.ci.issue.IssueDetector; import org.apache.ignite.ci.issue.IssuesStorage; import org.apache.ignite.ci.jira.IJiraIntegration; import org.apache.ignite.ci.tcmodel.hist.BuildRef; +import org.apache.ignite.ci.web.model.JiraCommentResponse; +import org.apache.ignite.ci.web.model.Visa; import org.apache.ignite.ci.tcmodel.result.problems.ProblemOccurrence; import org.apache.ignite.ci.teamcity.restcached.ITcServerProvider; import org.apache.ignite.ci.user.ICredentialsProv; @@ -68,7 +71,11 @@ public class TcHelper implements ITcHelper, IJiraIntegration { @Inject private PrChainsProcessor prChainsProcessor; + /** */ + private final ObjectMapper objectMapper; + public TcHelper() { + objectMapper = new ObjectMapper(); } /** {@inheritDoc} */ @@ -132,7 +139,7 @@ public class TcHelper implements ITcHelper, IJiraIntegration { } /** {@inheritDoc} */ - @Override public String notifyJira( + @Override public Visa notifyJira( String srvId, ICredentialsProv prov, String buildTypeId, @@ -144,15 +151,23 @@ public class TcHelper implements ITcHelper, IJiraIntegration { List<BuildRef> builds = teamcity.getFinishedBuildsIncludeSnDepFailed(buildTypeId, branchForTc); if (builds.isEmpty()) - return "JIRA wasn't commented - no finished builds to analyze."; + return new Visa("JIRA wasn't commented - no finished builds to analyze."); BuildRef build = builds.get(builds.size() - 1); - String comment; + + int blockers; + + JiraCommentResponse res; try { - comment = generateJiraComment(buildTypeId, build.branchName, srvId, prov, build.webUrl); + List<SuiteCurrentStatus> suitesStatuses = getSuitesStatuses(buildTypeId, build.branchName, srvId, prov); - teamcity.sendJiraComment(ticket, comment); + String comment = generateJiraComment(suitesStatuses, build.webUrl); + + blockers = suitesStatuses.stream().mapToInt(suite -> + suite.testFailures.size()).sum(); + + res = objectMapper.readValue(teamcity.sendJiraComment(ticket, comment), JiraCommentResponse.class); } catch (Exception e) { String errMsg = "Exception happened during commenting JIRA ticket " + @@ -160,10 +175,10 @@ public class TcHelper implements ITcHelper, IJiraIntegration { logger.error(errMsg); - return "JIRA wasn't commented - " + errMsg; + return new Visa("JIRA wasn't commented - " + errMsg); } - return JIRA_COMMENTED; + return new Visa(JIRA_COMMENTED, res, blockers); } /** @@ -171,17 +186,14 @@ public class TcHelper implements ITcHelper, IJiraIntegration { * @param branchForTc Branch for TeamCity. * @param srvId Server id. * @param prov Credentials. - * @param webUrl Build URL. - * @return Comment, which should be sent to the JIRA ticket. + * @return List of suites with possible blockers. */ - private String generateJiraComment( - String buildTypeId, + public List<SuiteCurrentStatus> getSuitesStatuses(String buildTypeId, String branchForTc, String srvId, - ICredentialsProv prov, - String webUrl - ) { - StringBuilder res = new StringBuilder(); + ICredentialsProv prov) { + List<SuiteCurrentStatus> res = new ArrayList<>(); + TestFailuresSummary summary = prChainsProcessor.getTestFailuresSummary( prov, srvId, buildTypeId, branchForTc, FullQueryParams.LATEST, null, null, false); @@ -193,54 +205,61 @@ public class TcHelper implements ITcHelper, IJiraIntegration { Map<String, List<SuiteCurrentStatus>> fails = findFailures(server); - for (List<SuiteCurrentStatus> suites : fails.values()) { - for (SuiteCurrentStatus suite : suites) { - res.append("{color:#d04437}").append(suite.name).append("{color}"); - res.append(" [[tests ").append(suite.failedTests); + fails.forEach((k, v) -> res.addAll(v)); + } + } - if (suite.result != null && !suite.result.isEmpty()) - res.append(' ').append(suite.result); + return res; + } - res.append('|').append(suite.webToBuild).append("]]\\n"); + /** */ + private String generateJiraComment(List<SuiteCurrentStatus> suites, String webUrl) { + StringBuilder res = new StringBuilder(); + + for (SuiteCurrentStatus suite : suites) { + res.append("{color:#d04437}").append(suite.name).append("{color}"); + res.append(" [[tests ").append(suite.failedTests); - for (TestFailure failure : suite.testFailures) { - res.append("* "); + if (suite.result != null && !suite.result.isEmpty()) + res.append(' ').append(suite.result); - if (failure.suiteName != null && failure.testName != null) - res.append(failure.suiteName).append(": ").append(failure.testName); - else - res.append(failure.name); + res.append('|').append(suite.webToBuild).append("]]\\n"); - FailureSummary recent = failure.histBaseBranch.recent; + for (TestFailure failure : suite.testFailures) { + res.append("* "); - if (recent != null) { - if (recent.failureRate != null) { - res.append(" - ").append(recent.failureRate).append("% fails in last ") - .append(MAX_LATEST_RUNS).append(" master runs."); - } - else if (recent.failures != null && recent.runs != null) { - res.append(" - ").append(recent.failures).append(" fails / ") - .append(recent.runs).append(" runs."); - } - } + if (failure.suiteName != null && failure.testName != null) + res.append(failure.suiteName).append(": ").append(failure.testName); + else + res.append(failure.name); - res.append("\\n"); - } + FailureSummary recent = failure.histBaseBranch.recent; - res.append("\\n"); + if (recent != null) { + if (recent.failureRate != null) { + res.append(" - ").append(recent.failureRate).append("% fails in last ") + .append(MAX_LATEST_RUNS).append(" master runs."); + } + else if (recent.failures != null && recent.runs != null) { + res.append(" - ").append(recent.failures).append(" fails / ") + .append(recent.runs).append(" runs."); } } - if (res.length() > 0) { - res.insert(0, "{panel:title=Possible Blockers|" + - "borderStyle=dashed|borderColor=#ccc|titleBGColor=#F7D6C1}\\n") - .append("{panel}"); - } - else { - res.append("{panel:title=No blockers found!|" + - "borderStyle=dashed|borderColor=#ccc|titleBGColor=#D6F7C1}{panel}"); - } + res.append("\\n"); } + + res.append("\\n"); + } + + if (res.length() > 0) { + res.insert(0, "{panel:title=Possible Blockers|" + + "borderStyle=dashed|borderColor=#ccc|titleBGColor=#F7D6C1}\\n") + .append("{panel}"); + } + else { + res.append("{panel:title=No blockers found!|" + + "borderStyle=dashed|borderColor=#ccc|titleBGColor=#D6F7C1}{panel}"); } res.append("\\n").append("[TeamCity Run All Results|").append(webUrl).append(']'); @@ -249,6 +268,24 @@ public class TcHelper implements ITcHelper, IJiraIntegration { } /** + * @param buildTypeId Suite name. + * @param branchForTc Branch for TeamCity. + * @param srvId Server id. + * @param prov Credentials. + * @param webUrl Build URL. + * @return Comment, which should be sent to the JIRA ticket. + */ + private String generateJiraComment( + String buildTypeId, + String branchForTc, + String srvId, + ICredentialsProv prov, + String webUrl + ) { + return generateJiraComment(getSuitesStatuses(buildTypeId, branchForTc,srvId, prov), webUrl); + } + + /** * @param srv Server. * @return Failures for given server. */ diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/di/IgniteTcBotModule.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/di/IgniteTcBotModule.java index b2aa2d8..618b681 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/di/IgniteTcBotModule.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/di/IgniteTcBotModule.java @@ -42,6 +42,7 @@ import org.apache.ignite.ci.user.ICredentialsProv; import org.apache.ignite.ci.util.ExceptionUtil; import org.apache.ignite.ci.web.BackgroundUpdater; import org.apache.ignite.ci.web.TcUpdatePool; +import org.apache.ignite.ci.web.model.Visa; import org.apache.ignite.ci.web.rest.exception.ServiceStartingException; /** @@ -90,7 +91,7 @@ public class IgniteTcBotModule extends AbstractModule { private static class Jira implements IJiraIntegration { @Inject ITcHelper helper; - @Override public String notifyJira(String srvId, ICredentialsProv prov, String buildTypeId, String branchForTc, + @Override public Visa notifyJira(String srvId, ICredentialsProv prov, String buildTypeId, String branchForTc, String ticket) { return helper.notifyJira(srvId, prov, buildTypeId, branchForTc, ticket); } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java index a20ead4..aa1084c 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java @@ -17,6 +17,7 @@ package org.apache.ignite.ci.jira; +import org.apache.ignite.ci.web.model.Visa; import org.apache.ignite.ci.user.ICredentialsProv; /** @@ -34,6 +35,6 @@ public interface IJiraIntegration { * @param ticket JIRA ticket full name. E.g. IGNITE-5555 * @return {@code True} if JIRA was notified. */ - public String notifyJira(String srvId, ICredentialsProv prov, String buildTypeId, String branchForTc, + public Visa notifyJira(String srvId, ICredentialsProv prov, String buildTypeId, String branchForTc, String ticket); } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildObserver.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildObserver.java index 1e08eb8..cf29468 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildObserver.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildObserver.java @@ -23,6 +23,8 @@ import java.util.Timer; import javax.inject.Inject; import org.apache.ignite.ci.tcmodel.result.Build; import org.apache.ignite.ci.user.ICredentialsProv; +import org.apache.ignite.ci.web.model.VisaRequest; +import org.apache.ignite.ci.web.model.hist.VisasHistoryStorage; /** * @@ -37,6 +39,9 @@ public class BuildObserver { /** Task, which should be done periodically. */ private ObserverTask observerTask; + /** Visas History Storage. */ + @Inject private VisasHistoryStorage visasStorage; + /** */ @Inject @@ -58,10 +63,15 @@ public class BuildObserver { /** * @param srvId Server id. * @param prov Credentials. + * @param branchForTc Branch for TC. * @param ticket JIRA ticket name. */ - public void observe(String srvId, ICredentialsProv prov, String ticket, Build... builds) { - observerTask.addBuild(new BuildsInfo(srvId, prov, ticket, builds)); + public void observe(String srvId, ICredentialsProv prov, String ticket, String branchForTc, Build... builds) { + BuildsInfo buildsInfo = new BuildsInfo(srvId, prov, ticket, branchForTc, builds); + + visasStorage.put(new VisaRequest(buildsInfo)); + + observerTask.addInfo(buildsInfo); } /** @@ -70,10 +80,11 @@ public class BuildObserver { */ public String getObservationStatus(String srvId, String branch) { StringBuilder sb = new StringBuilder(); - Collection<BuildsInfo> builds = observerTask.getBuilds(); + + Collection<BuildsInfo> builds = observerTask.getInfos(); for (BuildsInfo bi : builds) { - if (Objects.equals(bi.branchName, branch) + if (Objects.equals(bi.branchForTc, branch) && Objects.equals(bi.srvId, srvId)) { sb.append(bi.ticket).append(" to be commented, waiting for builds. "); sb.append(bi.finishedBuildsCount()); diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildsInfo.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildsInfo.java index ba8b48a..d3b17c0 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildsInfo.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildsInfo.java @@ -17,17 +17,34 @@ package org.apache.ignite.ci.observer; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Objects; import org.apache.ignite.ci.IAnalyticsEnabledTeamcity; import org.apache.ignite.ci.tcmodel.result.Build; +import org.apache.ignite.ci.teamcity.ignited.IgniteStringCompactor; import org.apache.ignite.ci.user.ICredentialsProv; +import org.apache.ignite.ci.web.model.ContributionKey; /** * */ public class BuildsInfo { + /** */ + public static final String FINISHED_STATE = "finished"; + + /** */ + public static final String RUNNING_STATE = "running"; + + /** */ + public static final String FINISHED_WITH_FAILURES_STATE = "finished with failures"; + + /** */ + public final String userName; + /** Server id. */ public final String srvId; @@ -35,42 +52,85 @@ public class BuildsInfo { public final String buildTypeId; /** Branch name. */ - public final String branchName; + public final String branchForTc; /** JIRA ticket full name. */ public final String ticket; + /** */ + public final Date date; + /** Finished builds. */ - private final Map<Build, Boolean> finishedBuilds = new HashMap<>(); + private final Map<Integer, Boolean> finishedBuilds = new HashMap<>(); + + /** */ + public BuildsInfo(CompactBuildsInfo buildsInfo, IgniteStringCompactor strCompactor) { + this.userName = strCompactor.getStringFromId(buildsInfo.userName); + this.date = buildsInfo.date; + this.srvId = strCompactor.getStringFromId(buildsInfo.srvId); + this.ticket = strCompactor.getStringFromId(buildsInfo.ticket); + this.branchForTc = strCompactor.getStringFromId(buildsInfo.branchForTc); + this.buildTypeId = strCompactor.getStringFromId(buildsInfo.buildTypeId); + this.finishedBuilds.putAll(buildsInfo.getFinishedBuilds()); + } /** * @param srvId Server id. * @param prov Prov. + * @param branchForTc Branch for TC. * @param ticket Ticket. * @param builds Builds. */ - public BuildsInfo(String srvId, ICredentialsProv prov, String ticket, Build[] builds) { + public BuildsInfo(String srvId, ICredentialsProv prov, String ticket, String branchForTc, Build... builds) { + this.userName = prov.getUser(srvId); + this.date = Calendar.getInstance().getTime(); this.srvId = srvId; this.ticket = ticket; - this.buildTypeId = builds.length > 1 ? "IgniteTests24Java8_RunAll" : builds[0].buildTypeId; - this.branchName = builds[0].branchName; + this.branchForTc = branchForTc; + this.buildTypeId = builds.length == 1 ? builds[0].buildTypeId : "IgniteTests24Java8_RunAll"; for (Build build : builds) - finishedBuilds.put(build, false); + finishedBuilds.put(build.getId(), false); } /** * @param teamcity Teamcity. */ - public boolean isFinished(IAnalyticsEnabledTeamcity teamcity) { - for (Map.Entry<Build, Boolean> entry : finishedBuilds.entrySet()) { + public String getState(IAnalyticsEnabledTeamcity teamcity) { + for (Map.Entry<Integer, Boolean> entry : finishedBuilds.entrySet()) { + if (entry.getValue() == null) + return FINISHED_WITH_FAILURES_STATE; + if (!entry.getValue()) { - Build build = teamcity.getBuild(entry.getKey().getId()); - entry.setValue(build.isFinished()); + Build build = teamcity.getBuild(entry.getKey()); + + if (build.isFinished()) { + if (build.isUnknown()) { + entry.setValue(null); + + return FINISHED_WITH_FAILURES_STATE; + } + + entry.setValue(true); + } } } - return !finishedBuilds.containsValue(false); + return finishedBuilds.containsValue(false) ? RUNNING_STATE : FINISHED_STATE; + } + + /** + * @param teamcity Teamcity. + */ + public boolean isFinished(IAnalyticsEnabledTeamcity teamcity) { + return FINISHED_STATE.equals(getState(teamcity)); + } + + /** + * @param teamcity Teamcity. + */ + public boolean isFinishedWithFailures(IAnalyticsEnabledTeamcity teamcity) { + return FINISHED_WITH_FAILURES_STATE.equals(getState(teamcity)); } /** @@ -87,6 +147,16 @@ public class BuildsInfo { return (int)finishedBuilds.values().stream().filter(v -> v).count(); } + /** */ + public ContributionKey getContributionKey() { + return new ContributionKey(srvId, ticket, branchForTc); + } + + /** */ + public Map<Integer, Boolean> getBuilds() { + return Collections.unmodifiableMap(finishedBuilds); + } + /** {@inheritDoc} */ @Override public boolean equals(Object o) { if (this == o) @@ -99,13 +169,14 @@ public class BuildsInfo { return Objects.equals(srvId, info.srvId) && Objects.equals(buildTypeId, info.buildTypeId) && - Objects.equals(branchName, info.branchName) && + Objects.equals(branchForTc, info.branchForTc) && Objects.equals(ticket, info.ticket) && - Objects.equals(finishedBuilds.keySet(), info.finishedBuilds.keySet()); + Objects.equals(finishedBuilds.keySet(), info.finishedBuilds.keySet()) && + Objects.equals(date, info.date); } /** {@inheritDoc} */ @Override public int hashCode() { - return Objects.hash(srvId, buildTypeId, branchName, ticket, finishedBuilds.keySet()); + return Objects.hash(srvId, buildTypeId, branchForTc, ticket, finishedBuilds.keySet(), date); } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/CompactBuildsInfo.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/CompactBuildsInfo.java new file mode 100644 index 0000000..6bab08d --- /dev/null +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/CompactBuildsInfo.java @@ -0,0 +1,95 @@ +/* + * 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.observer; + +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.apache.ignite.ci.teamcity.ignited.IgniteStringCompactor; + +/** + * + */ +public class CompactBuildsInfo { + /** */ + public final int userName; + + /** Server id. */ + public final int srvId; + + /** Build type id. */ + public final int buildTypeId; + + /** Branch name. */ + public final int branchForTc; + + /** JIRA ticket full name. */ + public final int ticket; + + /** */ + public final Date date; + + /** Finished builds. */ + private final Map<Integer, Boolean> finishedBuilds = new HashMap<>(); + + /** */ + public CompactBuildsInfo(BuildsInfo buildsInfo, IgniteStringCompactor strCompactor) { + this.userName = strCompactor.getStringId(buildsInfo.userName); + this.date = buildsInfo.date; + this.srvId = strCompactor.getStringId(buildsInfo.srvId); + this.ticket = strCompactor.getStringId(buildsInfo.ticket); + this.branchForTc = strCompactor.getStringId(buildsInfo.branchForTc); + this.buildTypeId = strCompactor.getStringId(buildsInfo.buildTypeId); + this.finishedBuilds.putAll(buildsInfo.getBuilds()); + } + + /** */ + public Map<Integer, Boolean> getFinishedBuilds() { + return Collections.unmodifiableMap(finishedBuilds); + } + + /** */ + public BuildsInfo toBuildInfo(IgniteStringCompactor compactor) { + return new BuildsInfo(this, compactor); + } + + /** {@inheritDoc} */ + @Override public boolean equals(Object o) { + if (this == o) + return true; + + if (!(o instanceof CompactBuildsInfo)) + return false; + + CompactBuildsInfo info = (CompactBuildsInfo)o; + + return Objects.equals(srvId, info.srvId) && + Objects.equals(buildTypeId, info.buildTypeId) && + Objects.equals(branchForTc, info.branchForTc) && + Objects.equals(ticket, info.ticket) && + Objects.equals(finishedBuilds.keySet(), info.finishedBuilds.keySet()) && + Objects.equals(date, info.date); + } + + /** {@inheritDoc} */ + @Override public int hashCode() { + return Objects.hash(srvId, buildTypeId, branchForTc, ticket, finishedBuilds.keySet(), date); + } +} diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/ObserverTask.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/ObserverTask.java index 37d6e0f..22f00e7 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/ObserverTask.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/ObserverTask.java @@ -17,26 +17,29 @@ package org.apache.ignite.ci.observer; +import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; -import java.util.Queue; +import java.util.List; import java.util.Set; import java.util.TimerTask; +import javax.cache.Cache; import javax.inject.Inject; import org.apache.ignite.Ignite; +import org.apache.ignite.IgniteCache; import org.apache.ignite.ci.IAnalyticsEnabledTeamcity; import org.apache.ignite.ci.ITcHelper; +import org.apache.ignite.ci.db.TcHelperDb; import org.apache.ignite.ci.di.AutoProfiling; import org.apache.ignite.ci.di.MonitoredTask; import org.apache.ignite.ci.jira.IJiraIntegration; +import org.apache.ignite.ci.teamcity.ignited.IgniteStringCompactor; import org.apache.ignite.ci.user.ICredentialsProv; -import org.apache.ignite.configuration.CollectionConfiguration; +import org.apache.ignite.ci.web.model.Visa; +import org.apache.ignite.ci.web.model.hist.VisasHistoryStorage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.apache.ignite.ci.jira.IJiraIntegration.JIRA_COMMENTED; - /** * Checks observed builds for finished status and comments JIRA ticket. */ @@ -44,6 +47,9 @@ public class ObserverTask extends TimerTask { /** Logger. */ private static final Logger logger = LoggerFactory.getLogger(ObserverTask.class); + /** */ + public static final String BUILDS_CACHE_NAME = "compactBuildsInfos"; + /** Helper. */ @Inject private ITcHelper tcHelper; @@ -53,28 +59,34 @@ public class ObserverTask extends TimerTask { /** Ignite. */ @Inject private Ignite ignite; + /** */ + @Inject private VisasHistoryStorage visasHistoryStorage; + + /** */ + @Inject private IgniteStringCompactor strCompactor; + /** */ ObserverTask() { } /** */ - private Queue<BuildsInfo> buildsQueue() { - CollectionConfiguration cfg = new CollectionConfiguration(); - - cfg.setBackups(1); - - return ignite.queue("buildsQueue", 0, cfg); + private IgniteCache<CompactBuildsInfo, Object> compactInfos() { + return ignite.getOrCreateCache(TcHelperDb.getCacheV2TxConfig(BUILDS_CACHE_NAME)); } /** */ - public Collection<BuildsInfo> getBuilds() { - return Collections.unmodifiableCollection(buildsQueue()); + public Collection<BuildsInfo> getInfos() { + List<BuildsInfo> buildsInfos = new ArrayList<>(); + + compactInfos().forEach(entry -> buildsInfos.add(entry.getKey().toBuildInfo(strCompactor))); + + return buildsInfos; } /** */ - public void addBuild(BuildsInfo build) { - buildsQueue().add(build); + public void addInfo(BuildsInfo info) { + compactInfos().put(new CompactBuildsInfo(info, strCompactor), new Object()); } /** {@inheritDoc} */ @@ -100,13 +112,25 @@ public class ObserverTask extends TimerTask { int notFinishedBuilds = 0; Set<String> ticketsNotified = new HashSet<>(); - Queue<BuildsInfo> builds = buildsQueue(); + for (Cache.Entry<CompactBuildsInfo, Object> entry : compactInfos()) { + CompactBuildsInfo compactInfo = entry.getKey(); + + BuildsInfo info = compactInfo.toBuildInfo(strCompactor); - for (BuildsInfo info : builds) { checkedBuilds += info.buildsCount(); IAnalyticsEnabledTeamcity teamcity = tcHelper.server(info.srvId, tcHelper.getServerAuthorizerCreds()); + if (info.isFinishedWithFailures(teamcity)) { + compactInfos().remove(compactInfo); + + logger.error("JIRA will not be commented." + + " [ticket: " + info.ticket + ", branch:" + info.branchForTc + "] : " + + "one or more re-runned blocker's builds finished with UNKNOWN status."); + + continue; + } + if (!info.isFinished(teamcity)) { notFinishedBuilds += info.buildsCount() - info.finishedBuildsCount(); @@ -115,13 +139,15 @@ public class ObserverTask extends TimerTask { ICredentialsProv creds = tcHelper.getServerAuthorizerCreds(); - String jiraRes = jiraIntegration.notifyJira(info.srvId, creds, info.buildTypeId, - info.branchName, info.ticket); + Visa visa = jiraIntegration.notifyJira(info.srvId, creds, info.buildTypeId, + info.branchForTc, info.ticket); + + visasHistoryStorage.updateVisaRequestRes(info.getContributionKey(), info.date, visa); - if (JIRA_COMMENTED.equals(jiraRes)) { + if (visa.isSuccess()) { ticketsNotified.add(info.ticket); - builds.remove(info); + compactInfos().remove(compactInfo); } } 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 5c64286..33ed6a0 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 @@ -19,6 +19,8 @@ package org.apache.ignite.ci.tcbot.visa; import com.google.common.base.Strings; import com.google.inject.Provider; +import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -28,6 +30,7 @@ import javax.annotation.Nonnull; import javax.inject.Inject; import javax.ws.rs.QueryParam; import org.apache.ignite.ci.ITcHelper; +import org.apache.ignite.ci.IAnalyticsEnabledTeamcity; import org.apache.ignite.ci.github.GitHubUser; import org.apache.ignite.ci.github.PullRequest; import org.apache.ignite.ci.github.ignited.IGitHubConnIgnitedProvider; @@ -36,11 +39,16 @@ import org.apache.ignite.ci.github.pure.IGitHubConnectionProvider; import org.apache.ignite.ci.jira.IJiraIntegration; import org.apache.ignite.ci.observer.BuildObserver; import org.apache.ignite.ci.tcmodel.hist.BuildRef; +import org.apache.ignite.ci.observer.BuildsInfo; import org.apache.ignite.ci.tcmodel.result.Build; import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnited; import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnitedProvider; +import org.apache.ignite.ci.teamcity.ignited.IgniteStringCompactor; +import org.apache.ignite.ci.web.model.VisaRequest; +import org.apache.ignite.ci.web.model.Visa; import org.apache.ignite.ci.user.ICredentialsProv; import org.apache.ignite.ci.web.model.SimpleResult; +import org.apache.ignite.ci.web.model.hist.VisasHistoryStorage; import org.apache.ignite.internal.util.typedef.F; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -66,14 +74,66 @@ public class TcBotTriggerAndSignOffService { @Inject Provider<BuildObserver> observer; + /** */ + @Inject private VisasHistoryStorage visasHistoryStorage; + + /** */ + @Inject IgniteStringCompactor strCompactor; + /** Helper. */ @Inject ITcHelper tcHelper; /** */ + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + /** */ public void startObserver() { buildObserverProvider.get(); } + /** */ + public List<VisaStatus> getVisasStatus(String srvId, ICredentialsProv prov) { + List<VisaStatus> visaStatuses = new ArrayList<>(); + + IAnalyticsEnabledTeamcity teamcity = tcHelper.server(srvId, prov); + + for (VisaRequest visaRequest : visasHistoryStorage.getVisas()) { + VisaStatus visaStatus = new VisaStatus(); + + BuildsInfo info = visaRequest.getInfo(); + + Visa visa = visaRequest.getResult(); + + visaStatus.date = formatter.format(info.date); + visaStatus.branchName = info.branchForTc; + visaStatus.userName = info.userName; + visaStatus.ticket = info.ticket; + + if (info.isFinished(teamcity)) { + if (visa.isEmpty()) + visaStatus.state = BuildsInfo.FINISHED_STATE + " [ waiting results ]"; + else if (visa.isSuccess()) { + visaStatus.commentUrl = "https://issues.apache.org/jira/browse/" + visaStatus.ticket + + "?focusedCommentId=" + visa.getJiraCommentResponse().getId() + + "&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-" + + visa.getJiraCommentResponse().getId(); + + visaStatus.blockers = visa.getBlockers(); + + visaStatus.state = BuildsInfo.FINISHED_STATE; + } + else + visaStatus.state = BuildsInfo.FINISHED_WITH_FAILURES_STATE; + } + else + visaStatus.state = info.getState(teamcity); + + visaStatuses.add(visaStatus); + } + + return visaStatuses; + } + /** * @param pr Pull Request. * @return JIRA ticket full name or empty string. @@ -94,7 +154,6 @@ public class TcBotTriggerAndSignOffService { return ticketId; } - @NotNull public String triggerBuildsAndObserve( @Nullable String srvId, @Nullable String branchForTc, @@ -161,7 +220,7 @@ public class TcBotTriggerAndSignOffService { ticketFullName = ticketFullName.toUpperCase().startsWith("IGNITE-") ? ticketFullName : "IGNITE-" + ticketFullName; } - buildObserverProvider.get().observe(srvId, prov, ticketFullName, builds); + buildObserverProvider.get().observe(srvId, prov, ticketFullName, branchForTc, builds); if (!tcHelper.isServerAuthorized()) return "Ask server administrator to authorize the Bot to enable JIRA notifications."; @@ -211,9 +270,14 @@ public class TcBotTriggerAndSignOffService { } if (!Strings.isNullOrEmpty(ticketFullName)) { - jiraRes = jiraIntegration.notifyJira(srvId, prov, suiteId, branchForTc, ticketFullName); + BuildsInfo buildsInfo = new BuildsInfo(srvId, prov, ticketFullName, branchForTc); + + Visa visa = jiraIntegration.notifyJira(srvId, prov, suiteId, branchForTc, ticketFullName); + + visasHistoryStorage.put(new VisaRequest(buildsInfo) + .setResult(visa)); - return new SimpleResult(jiraRes); + return new SimpleResult(visa.status); } else return new SimpleResult("JIRA wasn't commented." + (!jiraRes.isEmpty() ? "<br>" + jiraRes : "")); diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/VisaStatus.java similarity index 53% copy from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java copy to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/VisaStatus.java index a20ead4..1dfe31f 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/VisaStatus.java @@ -15,25 +15,32 @@ * limitations under the License. */ -package org.apache.ignite.ci.jira; +package org.apache.ignite.ci.tcbot.visa; -import org.apache.ignite.ci.user.ICredentialsProv; +import org.jetbrains.annotations.Nullable; /** * */ -public interface IJiraIntegration { - /** Message to show user when JIRA ticket was successfully commented by the Bot. */ - public static String JIRA_COMMENTED = "JIRA commented."; - - /** - * @param srvId TC Server ID to take information about token from. - * @param prov Credentials. - * @param buildTypeId Suite name. - * @param branchForTc Branch for TeamCity. - * @param ticket JIRA ticket full name. E.g. IGNITE-5555 - * @return {@code True} if JIRA was notified. - */ - public String notifyJira(String srvId, ICredentialsProv prov, String buildTypeId, String branchForTc, - String ticket); +public class VisaStatus { + /** */ + @Nullable public String userName; + + /** Branch name. */ + @Nullable public String branchName; + + /** JIRA ticket full name. */ + @Nullable public String ticket; + + /** */ + @Nullable public String state; + + /** */ + @Nullable public String commentUrl; + + /** */ + @Nullable public String date; + + /** */ + public int blockers; } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/hist/BuildRef.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/hist/BuildRef.java index 0ac3293..ee9f7e6 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/hist/BuildRef.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/hist/BuildRef.java @@ -187,4 +187,9 @@ public class BuildRef extends AbstractRef { public boolean isRunning() { return STATE_RUNNING.equals(state()); } + + /** */ + public boolean isUnknown() { + return STATUS_UNKNOWN.equals(status()); + } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/CompactContributionKey.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/CompactContributionKey.java new file mode 100644 index 0000000..2d783fd --- /dev/null +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/CompactContributionKey.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.ci.web.model; + +import java.util.Objects; +import org.apache.ignite.ci.teamcity.ignited.IgniteStringCompactor; + +/** + * + */ +public class CompactContributionKey { + /** */ + public final int srvId; + + /** */ + public final int ticket; + + /** */ + public final int branchForTc; + + /** */ + public CompactContributionKey(ContributionKey key, IgniteStringCompactor strCompactor) { + this.branchForTc = strCompactor.getStringId(key.branchForTc); + this.srvId = strCompactor.getStringId(key.srvId); + this.ticket = strCompactor.getStringId(key.ticket); + } + + /** {@inheritDoc} */ + @Override public boolean equals(Object o) { + if (this == o) + return true; + + if (!(o instanceof CompactContributionKey)) + return false; + + CompactContributionKey key = (CompactContributionKey)o; + + return Objects.equals(srvId, key.srvId) && + Objects.equals(ticket, key.ticket) && + Objects.equals(branchForTc, key.branchForTc); + } + + /** {@inheritDoc} */ + @Override public int hashCode() { + return Objects.hash(srvId, branchForTc, ticket); + } +} diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/CompactVisa.java similarity index 52% copy from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java copy to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/CompactVisa.java index a20ead4..9d67f07 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/CompactVisa.java @@ -15,25 +15,33 @@ * limitations under the License. */ -package org.apache.ignite.ci.jira; +package org.apache.ignite.ci.web.model; -import org.apache.ignite.ci.user.ICredentialsProv; +import org.apache.ignite.ci.teamcity.ignited.IgniteStringCompactor; +import org.jetbrains.annotations.Nullable; /** * */ -public interface IJiraIntegration { - /** Message to show user when JIRA ticket was successfully commented by the Bot. */ - public static String JIRA_COMMENTED = "JIRA commented."; +public class CompactVisa { + /** */ + public final int status; - /** - * @param srvId TC Server ID to take information about token from. - * @param prov Credentials. - * @param buildTypeId Suite name. - * @param branchForTc Branch for TeamCity. - * @param ticket JIRA ticket full name. E.g. IGNITE-5555 - * @return {@code True} if JIRA was notified. - */ - public String notifyJira(String srvId, ICredentialsProv prov, String buildTypeId, String branchForTc, - String ticket); + /** */ + @Nullable public final JiraCommentResponse jiraCommentRes; + + /** */ + public final int blockers; + + /** */ + public CompactVisa(Visa visa, IgniteStringCompactor strCompactor) { + this.status = strCompactor.getStringId(visa.status); + this.blockers = visa.blockers; + this.jiraCommentRes = visa.getJiraCommentResponse(); + } + + /** */ + public Visa toVisa(IgniteStringCompactor strCompactor) { + return new Visa(strCompactor.getStringFromId(status), jiraCommentRes, blockers); + } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/CompactVisaRequest.java similarity index 51% copy from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java copy to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/CompactVisaRequest.java index a20ead4..1fcc59f 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/CompactVisaRequest.java @@ -15,25 +15,31 @@ * limitations under the License. */ -package org.apache.ignite.ci.jira; +package org.apache.ignite.ci.web.model; -import org.apache.ignite.ci.user.ICredentialsProv; +import org.apache.ignite.ci.observer.CompactBuildsInfo; +import org.apache.ignite.ci.teamcity.ignited.IgniteStringCompactor; /** * */ -public interface IJiraIntegration { - /** Message to show user when JIRA ticket was successfully commented by the Bot. */ - public static String JIRA_COMMENTED = "JIRA commented."; - - /** - * @param srvId TC Server ID to take information about token from. - * @param prov Credentials. - * @param buildTypeId Suite name. - * @param branchForTc Branch for TeamCity. - * @param ticket JIRA ticket full name. E.g. IGNITE-5555 - * @return {@code True} if JIRA was notified. - */ - public String notifyJira(String srvId, ICredentialsProv prov, String buildTypeId, String branchForTc, - String ticket); +public class CompactVisaRequest { + /** */ + public final CompactVisa compactVisa; + + /** */ + public final CompactBuildsInfo compactInfo; + + /** */ + public CompactVisaRequest(VisaRequest visaReq, IgniteStringCompactor strCompactor) { + compactInfo = new CompactBuildsInfo(visaReq.getInfo(), strCompactor); + + compactVisa = new CompactVisa(visaReq.getResult(), strCompactor); + } + + /** */ + public VisaRequest toVisaRequest(IgniteStringCompactor strCompactor) { + return new VisaRequest(compactInfo.toBuildInfo(strCompactor)).setResult(compactVisa.toVisa(strCompactor)); + } + } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/ContributionKey.java similarity index 53% copy from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java copy to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/ContributionKey.java index a20ead4..fa62ea6 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/ContributionKey.java @@ -15,25 +15,25 @@ * limitations under the License. */ -package org.apache.ignite.ci.jira; - -import org.apache.ignite.ci.user.ICredentialsProv; +package org.apache.ignite.ci.web.model; /** * */ -public interface IJiraIntegration { - /** Message to show user when JIRA ticket was successfully commented by the Bot. */ - public static String JIRA_COMMENTED = "JIRA commented."; +public class ContributionKey { + /** */ + public final String srvId; + + /** */ + public final String ticket; + + /** */ + public final String branchForTc; - /** - * @param srvId TC Server ID to take information about token from. - * @param prov Credentials. - * @param buildTypeId Suite name. - * @param branchForTc Branch for TeamCity. - * @param ticket JIRA ticket full name. E.g. IGNITE-5555 - * @return {@code True} if JIRA was notified. - */ - public String notifyJira(String srvId, ICredentialsProv prov, String buildTypeId, String branchForTc, - String ticket); + /** */ + public ContributionKey(String srvId, String ticket, String branchForTc) { + this.branchForTc = branchForTc; + this.srvId = srvId; + this.ticket = ticket; + } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/JiraCommentResponse.java similarity index 53% copy from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java copy to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/JiraCommentResponse.java index a20ead4..26bde0a 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/JiraCommentResponse.java @@ -15,25 +15,20 @@ * limitations under the License. */ -package org.apache.ignite.ci.jira; +package org.apache.ignite.ci.web.model; -import org.apache.ignite.ci.user.ICredentialsProv; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; /** * */ -public interface IJiraIntegration { - /** Message to show user when JIRA ticket was successfully commented by the Bot. */ - public static String JIRA_COMMENTED = "JIRA commented."; +@JsonIgnoreProperties(ignoreUnknown = true) +public class JiraCommentResponse { + /** */ + private int id; - /** - * @param srvId TC Server ID to take information about token from. - * @param prov Credentials. - * @param buildTypeId Suite name. - * @param branchForTc Branch for TeamCity. - * @param ticket JIRA ticket full name. E.g. IGNITE-5555 - * @return {@code True} if JIRA was notified. - */ - public String notifyJira(String srvId, ICredentialsProv prov, String buildTypeId, String branchForTc, - String ticket); + /** */ + public int getId() { + return id; + } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/Visa.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/Visa.java new file mode 100644 index 0000000..4d234ba --- /dev/null +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/Visa.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.ci.web.model; + +import org.apache.ignite.ci.jira.IJiraIntegration; +import org.jetbrains.annotations.Nullable; + +/** + * + */ +public class Visa { + /** */ + public static final String EMPTY_VISA_STATUS = "emptyVisa"; + + /** */ + public final String status; + + /** */ + @Nullable public final JiraCommentResponse jiraCommentRes; + + /** */ + public final int blockers; + + /** */ + public static Visa emptyVisa() { + return new Visa(EMPTY_VISA_STATUS); + } + + /** */ + public Visa(String status) { + this.status = status; + this.jiraCommentRes = null; + this.blockers = 0; + } + + /** */ + public Visa(String status, JiraCommentResponse res, Integer blockers) { + this.status = status; + this.jiraCommentRes = res; + this.blockers = blockers; + } + + /** */ + @Nullable public JiraCommentResponse getJiraCommentResponse() { + return jiraCommentRes; + } + + /** */ + @Nullable public int getBlockers() { + return blockers; + } + + /** */ + public boolean isSuccess() { + return IJiraIntegration.JIRA_COMMENTED.equals(status) + && jiraCommentRes != null; + } + + /** */ + public boolean isEmpty() { + return EMPTY_VISA_STATUS.equals(status); + } + + + /** */ + @Override public String toString() { + return status; + } +} diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/VisaRequest.java similarity index 53% copy from ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java copy to ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/VisaRequest.java index a20ead4..c281a65 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/jira/IJiraIntegration.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/VisaRequest.java @@ -15,25 +15,41 @@ * limitations under the License. */ -package org.apache.ignite.ci.jira; +package org.apache.ignite.ci.web.model; -import org.apache.ignite.ci.user.ICredentialsProv; +import org.apache.ignite.ci.observer.BuildsInfo; +import org.jetbrains.annotations.Nullable; /** * */ -public interface IJiraIntegration { - /** Message to show user when JIRA ticket was successfully commented by the Bot. */ - public static String JIRA_COMMENTED = "JIRA commented."; - - /** - * @param srvId TC Server ID to take information about token from. - * @param prov Credentials. - * @param buildTypeId Suite name. - * @param branchForTc Branch for TeamCity. - * @param ticket JIRA ticket full name. E.g. IGNITE-5555 - * @return {@code True} if JIRA was notified. - */ - public String notifyJira(String srvId, ICredentialsProv prov, String buildTypeId, String branchForTc, - String ticket); +public class VisaRequest { + /** */ + private BuildsInfo info; + + /** */ + private Visa visa; + + /** */ + public VisaRequest(BuildsInfo info) { + this.info = info; + this.visa = Visa.emptyVisa(); + } + + /** */ + @Nullable public BuildsInfo getInfo() { + return info; + } + + /** */ + @Nullable public Visa getResult() { + return visa; + } + + /** */ + public VisaRequest setResult(Visa res) { + this.visa = res; + + return this; + } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/hist/VisasHistoryStorage.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/hist/VisasHistoryStorage.java new file mode 100644 index 0000000..842d86d --- /dev/null +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/hist/VisasHistoryStorage.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.ci.web.model.hist; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import javax.cache.Cache; +import javax.inject.Inject; +import org.apache.ignite.Ignite; +import org.apache.ignite.ci.db.TcHelperDb; +import org.apache.ignite.ci.teamcity.ignited.IgniteStringCompactor; +import org.apache.ignite.ci.web.model.CompactContributionKey; +import org.apache.ignite.ci.web.model.CompactVisaRequest; +import org.apache.ignite.ci.web.model.ContributionKey; +import org.apache.ignite.ci.web.model.Visa; +import org.apache.ignite.ci.web.model.VisaRequest; + +/** + * + */ +public class VisasHistoryStorage { + /** */ + private static final String VISAS_CACHE_NAME = "visasCompactCache"; + + /** */ + @Inject + private IgniteStringCompactor strCompactor; + + /** */ + @Inject + private Ignite ignite; + + /** */ + public void clear() { + visas().clear(); + } + + /** */ + private Cache<CompactContributionKey, Map<Date, CompactVisaRequest>> visas() { + return ignite.getOrCreateCache(TcHelperDb.getCacheV2TxConfig(VISAS_CACHE_NAME)); + } + + /** */ + public void put(VisaRequest visaReq) { + CompactVisaRequest compactVisaReq = new CompactVisaRequest(visaReq, strCompactor); + + CompactContributionKey key = new CompactContributionKey(new ContributionKey( + visaReq.getInfo().srvId, + visaReq.getInfo().ticket, + visaReq.getInfo().branchForTc), strCompactor); + + Map<Date, CompactVisaRequest> contributionVisas = visas().get(key); + + if (contributionVisas == null) + contributionVisas = new HashMap<>(); + + contributionVisas.put(compactVisaReq.compactInfo.date, compactVisaReq); + + visas().put(key, contributionVisas); + } + + /** */ + public VisaRequest getVisaReq(ContributionKey key, Date date) { + Map<Date, CompactVisaRequest> reqs = visas().get(new CompactContributionKey(key, strCompactor)); + + if (Objects.isNull(reqs)) + return null; + + return reqs.get(date).toVisaRequest(strCompactor); + } + + /** */ + public boolean updateVisaRequestRes(ContributionKey key, Date date, Visa visa) { + VisaRequest req = getVisaReq(key, date); + + if (req == null) + return false; + + req.setResult(visa); + + put(req); + + return true; + } + + /** */ + public Collection<VisaRequest> getVisas() { + List<VisaRequest> res = new ArrayList<>(); + + visas().forEach(entry -> res.addAll(entry.getValue().values().stream() + .map(v -> v.toVisaRequest(strCompactor)) + .collect(Collectors.toList()))); + + return Collections.unmodifiableCollection(res); + } +} 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 5bc1cbf..5b52dfa 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 java.util.Collection; import java.util.List; import javax.annotation.Nonnull; import javax.servlet.ServletContext; @@ -27,11 +28,11 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import org.apache.ignite.ci.tcbot.visa.ContributionCheckStatus; -import org.apache.ignite.ci.user.ICredentialsProv; import org.apache.ignite.ci.tcbot.visa.ContributionToCheck; import org.apache.ignite.ci.tcbot.visa.TcBotTriggerAndSignOffService; +import org.apache.ignite.ci.tcbot.visa.VisaStatus; +import org.apache.ignite.ci.user.ICredentialsProv; import org.apache.ignite.ci.web.CtxListener; -import org.apache.ignite.ci.web.model.SimpleResult; import org.apache.ignite.ci.web.rest.exception.ServiceUnauthorizedException; import org.jetbrains.annotations.Nullable; @@ -50,6 +51,17 @@ public class TcBotVisaService { * @param srvId Server id. */ @GET + @Path("history") + public Collection<VisaStatus> history(@Nullable @QueryParam("serverId") String srvId) { + return CtxListener.getInjector(ctx) + .getInstance(TcBotTriggerAndSignOffService.class) + .getVisasStatus(srvId, ICredentialsProv.get(req)); + } + + /** + * @param srvId Server id. + */ + @GET @Path("contributions") public List<ContributionToCheck> contributions(@Nullable @QueryParam("serverId") String srvId) { if (!ICredentialsProv.get(req).hasAccess(srvId)) diff --git a/ignite-tc-helper-web/src/main/webapp/js/common-1.6.js b/ignite-tc-helper-web/src/main/webapp/js/common-1.6.js index 5ca9a34..3b5a72c 100644 --- a/ignite-tc-helper-web/src/main/webapp/js/common-1.6.js +++ b/ignite-tc-helper-web/src/main/webapp/js/common-1.6.js @@ -132,7 +132,8 @@ function showMenu(menuData) { res += "<a href=\"/comparison.html\">Master Trends</a>"; res += "<a href=\"/compare.html\">Compare builds</a>"; res += "<a href=\"/issues.html\">Issues history</a>"; - //uncomment when Visa history is merged: res += "<a href=\"/visas.html\">Visas history</a>"; + //uncomment when Visa history is merged: + res += "<a href=\"/visas.html\">Visas history</a>"; res += "<div class='topnav-right'>"; diff --git a/ignite-tc-helper-web/src/main/webapp/visas.html b/ignite-tc-helper-web/src/main/webapp/visas.html new file mode 100644 index 0000000..87c1864 --- /dev/null +++ b/ignite-tc-helper-web/src/main/webapp/visas.html @@ -0,0 +1,150 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8"> + <title>Ignite Teamcity - Visas history</title> + <link rel="icon" href="img/leaf-icon-png-7066.png"> + <script src="https://code.jquery.com/jquery-1.12.4.js"></script> + <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> + <script type="text/javascript" src="https://cdn.jsdelivr.net/momentjs/latest/moment.min.js"></script> + <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script> + <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css" /> + <link rel="stylesheet" href="css/style-1.5.css"> + <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> + <script src="js/common-1.6.js"></script> + <script src="https://d3js.org/d3.v4.min.js"></script> + <script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.js"></script> + <script src="https://cdn.datatables.net/1.10.16/js/dataTables.jqueryui.js"></script> + <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> + <link rel="stylesheet" href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css"> + </head> +<body> + <br> + <div id="loadStatus"></div> + <br> + <table id="visasTable" class="cell-border" style="width:100%"> + <thead> + <tr class="ui-widget-header "> + <th>.</th> + <th>.</th> + <th>.</th> + <th>.</th> + <th>.</th> + <th>.</th> + </tr> + </thead> + </table> + <br> + <div id="version"></div> +<script> +function showErrInLoadStatus(jqXHR, exception) { + if (jqXHR.status === 0) { + $("#loadStatus").html('Not connect.\n Verify Network.'); + } else if (jqXHR.status === 404) { + $("#loadStatus").html('Requested page not found. [404]'); + } else if (jqXHR.status === 401) { + $("#loadStatus").html('Unauthorized [401]'); + + setTimeout(function() { + window.location.href = "/login.html" + "?backref=" + encodeURIComponent(window.location.href); + }, 1000); + } else if (jqXHR.status === 403) { + $("#loadStatus").html('Forbidden [403]'); + } else if( jqXHR.status === 418) { + $("#loadStatus").html('Services are starting [418], I\'m a teapot'); + } else if (jqXHR.status === 424) { + $("#loadStatus").html('Dependency problem: [424]: ' + jqXHR.responseText); + } else if (jqXHR.status === 500) { + $("#loadStatus").html('Internal Server Error [500].'); + } else if (exception === 'parsererror') { + $("#loadStatus").html('Requested JSON parse failed.'); + } else if (exception === 'timeout') { + $("#loadStatus").html('Time out error.'); + } else if (exception === 'abort') { + $("#loadStatus").html('Ajax request aborted.'); + } else { + $("#loadStatus").html('Uncaught Error.\n' + jqXHR.responseText); + } +} + +$(document).ready(function() { + loadData(); + + $.ajax({ url: "rest/branches/version", success: showVersionInfo, error: showErrInLoadStatus }); +}); + +function showVisasTable(result) { + let visasTable = $('#visasTable'); + + visasTable.dataTable().fnDestroy(); + + var table = visasTable.DataTable({ + "order": [[ 1, 'desc' ]], + data: result, + "iDisplayLength": 30, //rows to be shown by default + stateSave: true, + columnDefs: [ + { + targets: '_all', + className: 'dt-body-center' + }, + ], + columns: [ + { + "data": "state", + title: "Status", + "render": function (data, type, row, meta) { + if (type === 'display') { + if (data ==='finished' && row.commentUrl) + data = "<a href='" + row.commentUrl + "'>" + data + "</a>"; + } + + return data; + } + }, + { + "data": "date", + title: "Date" + }, + { + "data": "userName", + title: "User" + }, + { + "data": "branchName", + title: "Branch" + }, + { + "data": "ticket", + title: "Ticket" + }, + { + "data": "blockers", + title: "Blockers", + "render": function (data, type, row, meta) { + if (type === 'display') { + if (row.state != 'finished') + data = ""; + } + + return data; + } + } + + ] + }); +} + +function loadData() { + $.ajax({ + url: "rest/visa/history?serverId=apache", + success: function (result) { + showVisasTable(result); + }, + error: showErrInLoadStatus + } + ); +} +</script> +</body> +</html>