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
commit 3f369509c308163a6495b5e6569773d8d39ad711 Author: ololo3000 <pmgheap....@gmail.com> AuthorDate: Wed Oct 24 15:15:15 2018 +0300 IGNITE-9645 Add comparison of failed tests lists in two date intervals - Fixes #36. Signed-off-by: Dmitriy Pavlov <dpav...@apache.org> --- .../main/java/org/apache/ignite/ci/ITeamcity.java | 14 +- .../apache/ignite/ci/IgnitePersistentTeamcity.java | 80 +++-- .../apache/ignite/ci/IgniteTeamcityConnection.java | 48 ++- .../java/org/apache/ignite/ci/db/DbMigrations.java | 4 +- .../ignite/ci/tcmodel/result/Configurations.java | 54 ++++ .../ci/tcmodel/result/tests/TestOccurrence.java | 8 + .../ignite/ci/tcmodel/result/tests/TestRef.java | 2 + .../web/model/current/BuildStatisticsSummary.java | 16 +- .../ignite/ci/web/model/hist/BuildsHistory.java | 334 +++++++++++++++++++++ .../ci/web/rest/build/GetBuildTestFailures.java | 110 +++---- .../ignite/ci/web/rest/parms/FullQueryParams.java | 32 +- .../src/main/webapp/comparison.html | 240 ++++++++++++++- .../src/main/webapp/css/style-1.5.css | 128 ++++++++ 13 files changed, 954 insertions(+), 116 deletions(-) diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java index 6aa3b60..628581f 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.util.Date; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -36,15 +37,18 @@ import org.apache.ignite.ci.tcmodel.changes.ChangesList; import org.apache.ignite.ci.tcmodel.conf.BuildType; import org.apache.ignite.ci.tcmodel.hist.BuildRef; import org.apache.ignite.ci.tcmodel.result.Build; +import org.apache.ignite.ci.tcmodel.result.Configurations; import org.apache.ignite.ci.tcmodel.result.issues.IssuesUsagesList; import org.apache.ignite.ci.tcmodel.result.problems.ProblemOccurrences; import org.apache.ignite.ci.tcmodel.result.stat.Statistics; import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrence; import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrenceFull; import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrences; +import org.apache.ignite.ci.tcmodel.result.tests.TestRef; import org.apache.ignite.ci.tcmodel.user.User; import org.apache.ignite.ci.teamcity.pure.ITeamcityConn; import org.apache.ignite.ci.util.Base64Util; +import org.apache.ignite.ci.web.rest.parms.FullQueryParams; import org.jetbrains.annotations.NotNull; import static com.google.common.base.Strings.isNullOrEmpty; @@ -150,10 +154,12 @@ public interface ITeamcity extends ITeamcityConn { * @param build * @return */ - ProblemOccurrences getProblems(Build build); + ProblemOccurrences getProblems(BuildRef build); TestOccurrences getTests(String href, String normalizedBranch); + TestOccurrences getFailedTests(String href, int count, String normalizedBranch); + Statistics getBuildStatistics(String href); CompletableFuture<TestOccurrenceFull> getTestFull(String href); @@ -162,6 +168,10 @@ public interface ITeamcity extends ITeamcityConn { ChangesList getChangesList(String href); + CompletableFuture<TestRef> getTestRef(FullQueryParams key); + + Configurations getConfigurations(FullQueryParams key); + /** * List of build's related issues. * @@ -251,6 +261,8 @@ public interface ITeamcity extends ITeamcityConn { void setExecutor(ExecutorService pool); + Executor getExecutor(); + /** * @param tok TeamCity authorization token. 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 d1e94e4..60f61ba 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 @@ -32,6 +32,7 @@ import java.util.TreeMap; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -66,16 +67,19 @@ import org.apache.ignite.ci.tcmodel.changes.ChangesList; import org.apache.ignite.ci.tcmodel.conf.BuildType; import org.apache.ignite.ci.tcmodel.hist.BuildRef; import org.apache.ignite.ci.tcmodel.result.Build; +import org.apache.ignite.ci.tcmodel.result.Configurations; import org.apache.ignite.ci.tcmodel.result.issues.IssuesUsagesList; import org.apache.ignite.ci.tcmodel.result.problems.ProblemOccurrences; import org.apache.ignite.ci.tcmodel.result.stat.Statistics; import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrence; import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrenceFull; import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrences; +import org.apache.ignite.ci.tcmodel.result.tests.TestRef; import org.apache.ignite.ci.tcmodel.user.User; import org.apache.ignite.ci.util.CacheUpdateUtil; import org.apache.ignite.ci.util.CollectionUtil; import org.apache.ignite.ci.util.ObjectInterner; +import org.apache.ignite.ci.web.rest.parms.FullQueryParams; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -104,6 +108,8 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea private static final String BUILD_HIST_FINISHED = "buildHistFinished"; private static final String BUILD_HIST_FINISHED_OR_FAILED = "buildHistFinishedOrFailed"; public static final String BOT_DETECTED_ISSUES = "botDetectedIssues"; + public static final String TEST_REFS = "testRefs"; + public static final String CONFIGURATIONS = "configurations"; //todo need separate cache or separate key for 'execution time' because it is placed in statistics private static final String BUILDS_FAILURE_RUN_STAT = "buildsFailureRunStat"; @@ -125,6 +131,9 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea */ private ConcurrentMap<String, CompletableFuture<TestOccurrenceFull>> testOccFullFutures = new ConcurrentHashMap<>(); + /** Cached loads of test refs.*/ + private ConcurrentMap<String, CompletableFuture<TestRef>> testRefsFutures = new ConcurrentHashMap<>(); + /** cached running builds for branch. */ private ConcurrentMap<String, Expirable<List<BuildRef>>> queuedBuilds = new ConcurrentHashMap<>(); @@ -162,7 +171,8 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea buildProblemsCache(), buildStatisticsCache(), buildHistCache(), - buildHistIncFailedCache()); + buildHistIncFailedCache(), + testRefsCache()); } @Override @@ -221,6 +231,20 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea } /** + * @return {@link Configurations} instances cache, 32 parts. + */ + private IgniteCache<String, Configurations> configurationsCache() { + return getOrCreateCacheV2(ignCacheNme(CONFIGURATIONS)); + } + + /** + * @return {@link TestRef} instances cache, 32 parts. + */ + private IgniteCache<String, TestRef> testRefsCache() { + return getOrCreateCacheV2(ignCacheNme(TEST_REFS)); + } + + /** * @return Build {@link ProblemOccurrences} instances cache, 32 parts. */ private IgniteCache<String, ProblemOccurrences> buildProblemsCache() { @@ -766,26 +790,20 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea /** {@inheritDoc}*/ @AutoProfiling - @Override public ProblemOccurrences getProblems(Build build) { - if (build.problemOccurrences != null) { - String href = build.problemOccurrences.href; - - return loadIfAbsent( - buildProblemsCache(), - href, - k -> { - ProblemOccurrences problems = teamcity.getProblems(build); + @Override public ProblemOccurrences getProblems(BuildRef buildRef) { + return loadIfAbsent( + buildProblemsCache(), + "/app/rest/latest/problemOccurrences?locator=build:(id:" + buildRef.getId() + ")", + k -> { + ProblemOccurrences problems = teamcity.getProblems(buildRef); - registerCriticalBuildProblemInStat(build, problems); + registerCriticalBuildProblemInStat(buildRef, problems); - return problems; - }); - } - else - return new ProblemOccurrences(); + return problems; + }); } - private void registerCriticalBuildProblemInStat(Build build, ProblemOccurrences problems) { + private void registerCriticalBuildProblemInStat(BuildRef build, ProblemOccurrences problems) { boolean criticalFail = problems.getProblemsNonNull().stream().anyMatch(occurrence -> occurrence.isExecutionTimeout() || occurrence.isJvmCrash()); @@ -827,6 +845,20 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea hrefIgnored -> teamcity.getTests(href, normalizedBranch)); } + /** {@inheritDoc} */ + @AutoProfiling + @Override public TestOccurrences getFailedTests(String href, int count, String normalizedBranch) { + return getTests(href + ",muted:false,status:FAILURE,count:" + count + "&fields=testOccurrence(id,name)", normalizedBranch); + } + + /** {@inheritDoc} */ + @AutoProfiling + @Override public Configurations getConfigurations(FullQueryParams key) { + return loadIfAbsent(configurationsCache(), + key.toString(), + k -> teamcity.getConfigurations(key)); + } + private void addTestOccurrencesToStat(TestOccurrences val) { for (TestOccurrence next : val.getTests()) addTestOccurrenceToStat(next, ITeamcity.DEFAULT, null); @@ -876,6 +908,16 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea /** {@inheritDoc} */ @AutoProfiling + @Override public CompletableFuture<TestRef> getTestRef(FullQueryParams key) { + return CacheUpdateUtil.loadAsyncIfAbsent( + testRefsCache(), + key.toString(), + testRefsFutures, + k -> teamcity.getTestRef(key)); + } + + /** {@inheritDoc} */ + @AutoProfiling @Override public Change getChange(String href) { return loadIfAbsentV2(CHANGE_INFO_FULL, href, href1 -> { try { @@ -1119,6 +1161,10 @@ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITea }); } + public Executor getExecutor() { + return this.teamcity.getExecutor(); + } + /** {@inheritDoc} */ @Override public void setExecutor(ExecutorService executor) { this.teamcity.setExecutor(executor); 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 0cb684b..892fd54 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 @@ -62,11 +62,13 @@ import org.apache.ignite.ci.tcmodel.conf.bt.BuildTypeFull; import org.apache.ignite.ci.tcmodel.hist.BuildRef; import org.apache.ignite.ci.tcmodel.hist.Builds; import org.apache.ignite.ci.tcmodel.result.Build; +import org.apache.ignite.ci.tcmodel.result.Configurations; import org.apache.ignite.ci.tcmodel.result.issues.IssuesUsagesList; import org.apache.ignite.ci.tcmodel.result.problems.ProblemOccurrences; import org.apache.ignite.ci.tcmodel.result.stat.Statistics; import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrenceFull; import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrences; +import org.apache.ignite.ci.tcmodel.result.tests.TestRef; import org.apache.ignite.ci.tcmodel.user.User; import org.apache.ignite.ci.tcmodel.user.Users; import org.apache.ignite.ci.teamcity.pure.ITeamcityHttpConnection; @@ -75,6 +77,7 @@ import org.apache.ignite.ci.util.HttpUtil; import org.apache.ignite.ci.util.UrlUtil; import org.apache.ignite.ci.util.XmlUtil; import org.apache.ignite.ci.util.ZipUtil; +import org.apache.ignite.ci.web.rest.parms.FullQueryParams; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -122,6 +125,12 @@ public class IgniteTeamcityConnection implements ITeamcity { /** Build logger processing running. */ private ConcurrentHashMap<Integer, CompletableFuture<LogCheckTask>> buildLogProcessingRunning = new ConcurrentHashMap<>(); + private static int MAX_CFG_CNT = 500; + + public Executor getExecutor() { + return executor; + } + /** {@inheritDoc} */ public void init(@Nullable String tcName) { this.tcName = tcName; @@ -422,18 +431,14 @@ public class IgniteTeamcityConnection implements ITeamcity { return getJaxbUsingHref(href, Build.class); } - @Override @AutoProfiling - public ProblemOccurrences getProblems(Build build) { - if (build.problemOccurrences != null) { - ProblemOccurrences coll = getJaxbUsingHref(build.problemOccurrences.href, ProblemOccurrences.class); + @Override public ProblemOccurrences getProblems(BuildRef buildRef) { + ProblemOccurrences coll = getJaxbUsingHref("app/rest/latest/problemOccurrences?" + + "locator=build:(id:" + buildRef.getId() + ")", ProblemOccurrences.class); - coll.getProblemsNonNull().forEach(p -> p.buildRef = build); + coll.getProblemsNonNull().forEach(p -> p.buildRef = buildRef); - return coll; - } - else - return new ProblemOccurrences(); + return coll; } /** {@inheritDoc} */ @@ -456,6 +461,31 @@ public class IgniteTeamcityConnection implements ITeamcity { /** {@inheritDoc} */ @AutoProfiling + @Override public TestOccurrences getFailedTests(String href, int count, String normalizedBranch) { + return getTests(href + ",muted:false,status:FAILURE,count:" + count + "&fields=testOccurrence(id,name)", normalizedBranch); + } + + /** {@inheritDoc} */ + @AutoProfiling + @Override public CompletableFuture<TestRef> getTestRef(FullQueryParams key) { + return supplyAsync(() -> { + return getJaxbUsingHref("app/rest/latest/tests/name:" + key.getTestName(), TestRef.class); + }, executor); + } + + + /** {@inheritDoc} */ + @AutoProfiling + @Override public Configurations getConfigurations(FullQueryParams key) { + Configurations configurations = getJaxbUsingHref("app/rest/latest/builds?locator=snapshotDependency:(to:(id:" + key.getBuildId() + + "),includeInitial:true),defaultFilter:false,count:" + MAX_CFG_CNT, Configurations.class); + + return configurations.setBuild(key.getBuildId()); + } + + + /** {@inheritDoc} */ + @AutoProfiling @Override public Change getChange(String href) { return getJaxbUsingHref(href, Change.class); } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/db/DbMigrations.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/db/DbMigrations.java index 1acb643..19f807b 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/db/DbMigrations.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/db/DbMigrations.java @@ -42,6 +42,7 @@ import org.apache.ignite.ci.tcmodel.result.problems.ProblemOccurrences; import org.apache.ignite.ci.tcmodel.result.stat.Statistics; import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrenceFull; import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrences; +import org.apache.ignite.ci.tcmodel.result.tests.TestRef; import org.apache.ignite.ci.web.rest.Metrics; import org.apache.ignite.ci.web.rest.build.GetBuildTestFailures; import org.apache.ignite.ci.web.rest.pr.GetPrTestFailures; @@ -113,7 +114,8 @@ public class DbMigrations { Cache<String, ProblemOccurrences> problemsCache, Cache<String, Statistics> buildStatCache, Cache<SuiteInBranch, Expirable<List<BuildRef>>> buildHistCache, - Cache<SuiteInBranch, Expirable<List<BuildRef>>> buildHistInFailedCache) { + Cache<SuiteInBranch, Expirable<List<BuildRef>>> buildHistInFailedCache, + Cache<String, TestRef> testRefsCache) { doneMigrations = doneMigrationsCache(); diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/Configurations.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/Configurations.java new file mode 100644 index 0000000..eb62c46 --- /dev/null +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/Configurations.java @@ -0,0 +1,54 @@ +/* + * 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.tcmodel.result; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; +import org.apache.ignite.ci.tcmodel.hist.BuildRef; + +/** + */ +@XmlRootElement(name = "builds") +public class Configurations { + /** */ + @XmlElement(name = "build") + private List<BuildRef> builds; + + /** */ + public List<BuildRef> getBuilds() { + return builds == null ? new ArrayList<>() : builds; + } + + /** BuildId which that configurations belong to. */ + private Integer build; + + /** */ + public Configurations setBuild(Integer build) { + this.build = build; + + return this; + } + + /** */ + public Integer getBuild() { + return build; + } +} diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/tests/TestOccurrence.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/tests/TestOccurrence.java index 43c91a7..9a3c807 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/tests/TestOccurrence.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/tests/TestOccurrence.java @@ -20,6 +20,7 @@ package org.apache.ignite.ci.tcmodel.result.tests; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; +import org.apache.ignite.ci.analysis.RunStat; /** * Test occurrence. Can be provided by build as list of occurrences. @@ -98,4 +99,11 @@ public class TestOccurrence { return this; } + + /** + * @return BuildId which that test occurrence belongs to + */ + public Integer getBuildId() { + return RunStat.extractIdPrefixed(id, "build:(id:", ")"); + } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/tests/TestRef.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/tests/TestRef.java index 29d369b..76f6a13 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/tests/TestRef.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/result/tests/TestRef.java @@ -18,11 +18,13 @@ package org.apache.ignite.ci.tcmodel.result.tests; import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; import org.apache.ignite.ci.tcmodel.result.AbstractRef; /** * Reference to particular test */ +@XmlRootElement(name = "test") public class TestRef extends AbstractRef { @XmlAttribute public Long id; @XmlAttribute public String name; diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/BuildStatisticsSummary.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/BuildStatisticsSummary.java index d4a7bf6..d3cd968 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/BuildStatisticsSummary.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/BuildStatisticsSummary.java @@ -34,6 +34,7 @@ import org.apache.ignite.ci.tcmodel.result.TestOccurrencesRef; import org.apache.ignite.ci.tcmodel.result.problems.ProblemOccurrence; import org.apache.ignite.ci.util.TimeUtil; import org.apache.ignite.ci.web.IBackgroundUpdatable; +import org.apache.ignite.ci.web.rest.parms.FullQueryParams; /** * Summary of build statistics. @@ -132,7 +133,7 @@ public class BuildStatisticsSummary extends UpdateInfo implements IBackgroundUpd for (BuildRef buildRef : builds) problemOccurrences.addAll(teamcity - .getProblems(teamcity.getBuild(buildRef.href)) + .getProblems(buildRef) .getProblemsNonNull()); return problemOccurrences; @@ -145,17 +146,12 @@ public class BuildStatisticsSummary extends UpdateInfo implements IBackgroundUpd * @param buildRef Build reference. */ private List<BuildRef> getSnapshotDependencies(@Nonnull final ITeamcity teamcity, BuildRef buildRef){ - List<BuildRef> snapshotDependencies = new ArrayList<>(); + FullQueryParams key = new FullQueryParams(); - if (buildRef.isComposite()){ - Build build = teamcity.getBuild(buildRef.href); + key.setServerId(teamcity.serverId()); + key.setBuildId(buildRef.getId()); - for (BuildRef snDep : build.getSnapshotDependenciesNonNull()) - snapshotDependencies.addAll(getSnapshotDependencies(teamcity, snDep)); - } else - snapshotDependencies.add(buildRef); - - return snapshotDependencies; + return teamcity.getConfigurations(key).getBuilds(); } /** diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/hist/BuildsHistory.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/hist/BuildsHistory.java new file mode 100644 index 0000000..90b78f0 --- /dev/null +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/hist/BuildsHistory.java @@ -0,0 +1,334 @@ +/* + * 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 com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.UncheckedIOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import javax.servlet.ServletContext; +import org.apache.ignite.ci.IAnalyticsEnabledTeamcity; +import org.apache.ignite.ci.ITcHelper; +import org.apache.ignite.ci.ITeamcity; +import org.apache.ignite.ci.tcbot.chain.BuildChainProcessor; +import org.apache.ignite.ci.tcmodel.result.Build; +import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrence; +import org.apache.ignite.ci.tcmodel.result.tests.TestOccurrences; +import org.apache.ignite.ci.user.ICredentialsProv; +import org.apache.ignite.ci.web.CtxListener; +import org.apache.ignite.ci.web.model.current.BuildStatisticsSummary; +import org.apache.ignite.ci.web.rest.exception.ServiceUnauthorizedException; +import org.apache.ignite.ci.web.rest.parms.FullQueryParams; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.google.common.base.Strings.isNullOrEmpty; + +/** + * Builds History: includes statistic for every build and merged failed unmuted tests in specified time interval. + */ +public class BuildsHistory { + /** */ + private String srvId; + + /** */ + private String projectId; + + /** */ + private String buildTypeId; + + /** */ + private String branchName; + + /** */ + private Date sinceDateFilter; + + /** */ + private Date untilDateFilter; + + /** */ + private Map<String, Set<String>> mergedTestsBySuites = new ConcurrentHashMap<>(); + + /** */ + private boolean skipTests; + + /** */ + public List<BuildStatisticsSummary> buildsStatistics = new ArrayList<>(); + + /** */ + public String mergedTestsJson; + + /** */ + private static final Logger logger = LoggerFactory.getLogger(BuildsHistory.class); + + /** */ + public void initialize(ICredentialsProv prov, ServletContext context) { + if (!prov.hasAccess(srvId)) + throw ServiceUnauthorizedException.noCreds(srvId); + + ITcHelper tcHelper = CtxListener.getTcHelper(context); + + IAnalyticsEnabledTeamcity teamcity = tcHelper.server(srvId, prov); + + int[] finishedBuildsIds = teamcity.getBuildNumbersFromHistory(buildTypeId, branchName, + sinceDateFilter, untilDateFilter); + + initStatistics(teamcity, finishedBuildsIds); + + if (!skipTests) { + initFailedTests(teamcity, finishedBuildsIds); + } + + ObjectMapper objectMapper = new ObjectMapper(); + + try { + mergedTestsJson = objectMapper.writeValueAsString(mergedTestsBySuites); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + /** */ + private void initStatistics(IAnalyticsEnabledTeamcity teamcity, int[] buildIds) { + List<Future<BuildStatisticsSummary>> buildStatiscsFutures = new ArrayList<>(); + + for (int buildId : buildIds) { + Future<BuildStatisticsSummary> buildFuture = CompletableFuture.supplyAsync(() -> { + BuildStatisticsSummary buildsStatistic = new BuildStatisticsSummary(buildId); + + buildsStatistic.initialize(teamcity); + + return buildsStatistic; + + }, teamcity.getExecutor()); + + buildStatiscsFutures.add(buildFuture); + } + + buildStatiscsFutures.forEach(new Consumer<Future<BuildStatisticsSummary>>() { + @Override public void accept(Future<BuildStatisticsSummary> v) { + try { + BuildStatisticsSummary buildsStatistic = v.get(); + + if (buildsStatistic != null && !buildsStatistic.isFakeStub) + buildsStatistics.add(buildsStatistic); + } + catch (ExecutionException e) { + if (e.getCause() instanceof UncheckedIOException) + logger.error(e.getStackTrace().toString()); + + else + throw new RuntimeException(e); + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }); + } + + /** */ + private Map<Integer, String> getConfigurations(ITeamcity teamcity, int buildId) { + Map<Integer, String> configurations = new HashMap<>(); + + FullQueryParams key = new FullQueryParams(); + + key.setServerId(teamcity.serverId()); + key.setBuildId(buildId); + + teamcity.getConfigurations(key).getBuilds().forEach(buildRef -> { + Integer id = buildRef.getId(); + + String configurationName = buildRef.buildTypeId; + + if (id != null && configurationName != null) + configurations.put(id, configurationName); + }); + + return configurations; + } + + /** */ + private void initFailedTests(IAnalyticsEnabledTeamcity teamcity, int[] buildIds) { + List<Future<Void>> buildProcessorFutures = new ArrayList<>(); + + for (int buildId : buildIds) { + Future<Void> buildFuture = CompletableFuture.supplyAsync(() -> { + Map<Integer, String> configurations = getConfigurations(teamcity, buildId); + + Build build = teamcity.getBuild(teamcity.getBuildHrefById(buildId)); + + TestOccurrences testOccurrences = teamcity.getFailedTests(build.testOccurrences.href, + build.testOccurrences.failed, BuildChainProcessor.normalizeBranch(build.branchName)); + + for (TestOccurrence testOccurrence : testOccurrences.getTests()) { + String configurationName = configurations.get(testOccurrence.getBuildId()); + + if(configurationName == null) + continue; + + Set<String> tests = mergedTestsBySuites.computeIfAbsent(configurationName, + k -> new HashSet<>()); + + if (!tests.add(testOccurrence.getName())) + continue; + + FullQueryParams key = new FullQueryParams(); + + key.setServerId(srvId); + key.setProjectId(projectId); + key.setTestName(testOccurrence.getName()); + key.setSuiteId(configurationName); + + teamcity.getTestRef(key); + } + + return null; + }, teamcity.getExecutor()); + + buildProcessorFutures.add(buildFuture); + } + + buildProcessorFutures.forEach(v -> { + try { + v.get(); + } catch (ExecutionException e) { + if (e.getCause() instanceof UncheckedIOException) + logger.error(e.getStackTrace().toString()); + + else + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + } + + /** */ + public BuildsHistory(Builder builder) { + this.skipTests = builder.skipTests; + this.srvId = builder.srvId; + this.buildTypeId = builder.buildTypeId; + this.branchName = builder.branchName; + this.sinceDateFilter = builder.sinceDate; + this.untilDateFilter = builder.untilDate; + this.projectId = builder.projectId; + } + + /** */ + public static class Builder { + /** */ + private boolean skipTests = false; + + /** */ + private String projectId = "IgniteTests24Java8"; + + /** */ + private String srvId = "apache"; + + /** */ + private String buildTypeId = "IgniteTests24Java8_RunAll"; + + /** */ + private String branchName = "refs/heads/master"; + + /** */ + private Date sinceDate = null; + + /** */ + private Date untilDate = null; + + /** */ + private DateFormat dateFormat = new SimpleDateFormat("ddMMyyyyHHmmss"); + + /** */ + public Builder server(String srvId) { + if (!isNullOrEmpty(srvId)) + this.srvId = srvId; + + return this; + } + + /** */ + public Builder buildType(String buildType) { + if (!isNullOrEmpty(buildType)) + this.buildTypeId = buildType; + + return this; + } + + /** */ + public Builder project(String projectId) { + if (!isNullOrEmpty(projectId)) + this.projectId = projectId; + + return this; + } + + /** */ + public Builder branch(String branchName) { + if (!isNullOrEmpty(branchName)) + this.branchName = branchName; + + return this; + } + + /** */ + public Builder sinceDate(String sinceDate) throws ParseException { + if (!isNullOrEmpty(sinceDate)) + this.sinceDate = dateFormat.parse(sinceDate); + + return this; + } + + /** */ + public Builder untilDate(String untilDate) throws ParseException { + if (!isNullOrEmpty(untilDate)) + this.untilDate = dateFormat.parse(untilDate); + + return this; + } + + /** */ + public Builder skipTests() { + this.skipTests = true; + + return this; + } + + + /** */ + public BuildsHistory build() { + return new BuildsHistory(this); + } + } +} diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/build/GetBuildTestFailures.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/build/GetBuildTestFailures.java index 15692c8..fdfe218 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/build/GetBuildTestFailures.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/build/GetBuildTestFailures.java @@ -17,6 +17,9 @@ package org.apache.ignite.ci.web.rest.build; +import java.text.ParseException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import com.google.inject.Injector; import org.apache.ignite.ci.tcbot.chain.BuildChainProcessor; import org.apache.ignite.ci.IAnalyticsEnabledTeamcity; @@ -26,10 +29,11 @@ import org.apache.ignite.ci.analysis.FullChainRunCtx; import org.apache.ignite.ci.analysis.mode.LatestRebuildMode; import org.apache.ignite.ci.analysis.mode.ProcessLogsMode; import org.apache.ignite.ci.tcmodel.hist.BuildRef; +import org.apache.ignite.ci.tcmodel.result.tests.TestRef; import org.apache.ignite.ci.user.ICredentialsProv; +import org.apache.ignite.ci.web.model.hist.BuildsHistory; import org.apache.ignite.ci.web.BackgroundUpdater; import org.apache.ignite.ci.web.CtxListener; -import org.apache.ignite.ci.web.model.current.BuildStatisticsSummary; import org.apache.ignite.ci.web.model.current.ChainAtServerCurrentStatus; import org.apache.ignite.ci.web.model.current.TestFailuresSummary; import org.apache.ignite.ci.web.model.current.UpdateInfo; @@ -46,16 +50,8 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; -import java.util.ArrayList; import java.util.Collections; -import java.util.List; -import java.util.Date; import java.util.concurrent.atomic.AtomicInteger; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; - -import static com.google.common.base.Strings.isNullOrEmpty; @Path(GetBuildTestFailures.BUILD) @Produces(MediaType.APPLICATION_JSON) @@ -159,75 +155,65 @@ public class GetBuildTestFailures { } @GET - @Path("history") - public List<BuildStatisticsSummary> getBuildsHistory( + @Produces(MediaType.TEXT_PLAIN) + @Path("testRef") + public String getTestRef( + @NotNull @QueryParam("testName") String name, + @NotNull @QueryParam("suiteName") String suiteName, @Nullable @QueryParam("server") String srv, - @Nullable @QueryParam("buildType") String buildType, - @Nullable @QueryParam("branch") String branch, - @Nullable @QueryParam("sinceDate") String sinceDate, - @Nullable @QueryParam("untilDate") String untilDate) - throws ServiceUnauthorizedException { - String srvId = isNullOrEmpty(srv) ? "apache" : srv; - String buildTypeId = isNullOrEmpty(buildType) ? "IgniteTests24Java8_RunAll" : buildType; - String branchName = isNullOrEmpty(branch) ? "refs/heads/master" : branch; - Date sinceDateFilter = isNullOrEmpty(sinceDate) ? null : dateParse(sinceDate); - Date untilDateFilter = isNullOrEmpty(untilDate) ? null : dateParse(untilDate); - - final BackgroundUpdater updater = CtxListener.getBackgroundUpdater(ctx); - - final ITcHelper tcHelper = CtxListener.getTcHelper(ctx); + @Nullable @QueryParam("projectId") String projectId) + throws InterruptedException, ExecutionException, ServiceUnauthorizedException { + final ITcHelper helper = CtxListener.getTcHelper(ctx); final ICredentialsProv prov = ICredentialsProv.get(req); - IAnalyticsEnabledTeamcity teamcity = tcHelper.server(srvId, prov); - - int[] finishedBuilds = teamcity.getBuildNumbersFromHistory(buildTypeId, branchName, sinceDateFilter, untilDateFilter); - - List<BuildStatisticsSummary> buildsStatistics = new ArrayList<>(); + String project = projectId == null ? "IgniteTests24Java8" : projectId; - for (int i = 0; i < finishedBuilds.length; i++) { - int buildId = finishedBuilds[i]; + String srvId = srv == null ? "apache" : srv; - FullQueryParams param = new FullQueryParams(); - param.setBuildId(buildId); - param.setBranch(branchName); - param.setServerId(srvId); + if (!prov.hasAccess(srvId)) + throw ServiceUnauthorizedException.noCreds(srvId); - BuildStatisticsSummary buildsStatistic = updater.get( - BUILDS_STATISTICS_SUMMARY_CACHE_NAME, prov, param, - (k) -> getBuildStatisticsSummaryNoCache(srvId, buildId), false); + IAnalyticsEnabledTeamcity teamcity = helper.server(srvId, prov); - if (!buildsStatistic.isFakeStub) - buildsStatistics.add(buildsStatistic); - } + FullQueryParams key = new FullQueryParams(); - return buildsStatistics; - } + key.setTestName(name); + key.setProjectId(project); + key.setServerId(srvId); + key.setSuiteId(suiteName); - private Date dateParse(String date){ - DateFormat dateFormat = new SimpleDateFormat("ddMMyyyyHHmmss"); + CompletableFuture<TestRef> ref = teamcity.getTestRef(key); - try { - return dateFormat.parse(date); - } - catch (ParseException e) { - return null; - } + return ref.isDone() && !ref.isCompletedExceptionally() ? teamcity.host() + "project.html?" + + "projectId=" + project + + "&testNameId=" + ref.get().id + + "&tab=testDetails" : null; } - private BuildStatisticsSummary getBuildStatisticsSummaryNoCache(String server, int buildId) { - String srvId = isNullOrEmpty(server) ? "apache" : server; - - final ITcHelper tcHelper = CtxListener.getTcHelper(ctx); - - final ICredentialsProv creds = ICredentialsProv.get(req); + @GET + @Path("history") + public BuildsHistory getBuildsHistory( + @Nullable @QueryParam("server") String srvId, + @Nullable @QueryParam("buildType") String buildType, + @Nullable @QueryParam("branch") String branch, + @Nullable @QueryParam("sinceDate") String sinceDate, + @Nullable @QueryParam("untilDate") String untilDate, + @Nullable @QueryParam("skipTests") String skipTests) throws ParseException { + BuildsHistory.Builder builder = new BuildsHistory.Builder() + .branch(branch) + .server(srvId) + .buildType(buildType) + .sinceDate(sinceDate) + .untilDate(untilDate); - IAnalyticsEnabledTeamcity teamcity = tcHelper.server(srvId, creds); + if (Boolean.valueOf(skipTests)) + builder.skipTests(); - BuildStatisticsSummary stat = new BuildStatisticsSummary(buildId); + BuildsHistory buildsHistory = builder.build(); - stat.initialize(teamcity); + buildsHistory.initialize(ICredentialsProv.get(req), ctx); - return stat; + return buildsHistory; } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/parms/FullQueryParams.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/parms/FullQueryParams.java index 1026fb4..47fac6f 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/parms/FullQueryParams.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/parms/FullQueryParams.java @@ -60,6 +60,12 @@ public class FullQueryParams { /** TC identified base branch: null means the same as <default>, master. For not tracked branches. */ @Nullable @QueryParam("baseBranchForTc") private String baseBranchForTc; + /** TC project identifier */ + @Nullable @QueryParam("projectId") String projectId; + + /** TC test name */ + @Nullable @QueryParam("testName") String testName; + public FullQueryParams() { } @@ -97,6 +103,14 @@ public class FullQueryParams { return count; } + @Nullable public String getTestName() { + return testName; + } + + @Nullable public String getProjectId() { + return projectId ; + } + @Nullable public Boolean getCheckAllLogs() { return checkAllLogs; } @@ -118,13 +132,15 @@ public class FullQueryParams { Objects.equal(count, param.count) && Objects.equal(checkAllLogs, param.checkAllLogs) && Objects.equal(buildId, param.buildId) && + Objects.equal(projectId, param.projectId) && + Objects.equal(testName, param.testName) && Objects.equal(baseBranchForTc, param.baseBranchForTc); } /** {@inheritDoc} */ @Override public int hashCode() { return Objects.hashCode(branch, serverId, suiteId, branchForTc, action, count, checkAllLogs, buildId, - baseBranchForTc); + baseBranchForTc, testName, projectId); } public void setBranch(@Nullable String branch) { @@ -146,13 +162,27 @@ public class FullQueryParams { .add("checkAllLogs", checkAllLogs) .add("buildId", buildId) .add("baseBranchForTc", baseBranchForTc) + .add("projectId", projectId) + .add("testName", testName) .toString(); } + public void setSuiteId(@Nonnull String suiteId) { + this.suiteId = suiteId; + } + public void setCount(@Nullable int count) { this.count = count; } + public void setProjectId(@Nullable String projectId) { + this.projectId = projectId; + } + + public void setTestName(@Nullable String testName) { + this.testName = testName; + } + public void setBuildId(Integer buildId) { this.buildId = buildId; } diff --git a/ignite-tc-helper-web/src/main/webapp/comparison.html b/ignite-tc-helper-web/src/main/webapp/comparison.html index fd62a47..392e71c 100644 --- a/ignite-tc-helper-web/src/main/webapp/comparison.html +++ b/ignite-tc-helper-web/src/main/webapp/comparison.html @@ -17,7 +17,7 @@ <body> <br> <br> -<table class="compare" width="100%"> +<table style="table-layout: fixed" class="compare"> <tr> <th class="section" width="15%">DATE INTERVAL</th> <th width="5%"></th> @@ -148,15 +148,64 @@ <td class="t2 data2" id="RunsCount2"></td> </tr> </table><br> +<table style="table-layout: fixed" id="testsTable" class="testsTable"> + <tbody> + <tr> + <th class="failedTestsHeader" width="15%">FAILED TESTS</th> + <th width="5%"> + <label class="switch"> + <input type="checkbox" onclick="toggleTests()"> + <span class="slider"></span> + </label> + </th> + <th width="40%"></th> + <th width="40%"></th> + </tr> + </tbody> +</table> + +<div id="myModal" class="modal"> + + <!-- Modal content --> + <div class="modal-content"> + <span class="close">×</span> + <p id="tooltipText"></p> + </div> + +</div> + <div id="version"></div> <script> - let oneWeekAgo = new Date(), - twoWeekAgo = new Date(), - g_updTimer = null; + const TESTS_TABLE = '#testsTable'; + const SKIP_TESTS = 'skipTests=true'; + let oneWeekAgo = new Date(); + let twoWeekAgo = new Date(); + let g_updTimer = null; + let testsTrigger = false; + + /** Structure for storing tests by suites parsed response for every date interval. */ + let mergedTestsResults = {1 : {}, 2 : {} }; + oneWeekAgo.setDate(oneWeekAgo.getDate() - 7); twoWeekAgo.setDate(twoWeekAgo.getDate() - 14); + let dateIntervals = {1: {start: moment(oneWeekAgo), end: moment()}, + 2: {start: moment(twoWeekAgo), end: moment(oneWeekAgo)}}; + + function showTooltip(message) { + $("#tooltipText").html(message); + modal.show(); + } + + function toggleTests() { + testsTrigger = !testsTrigger; + + loadData(1, dateIntervals[1].start, dateIntervals[1].end, testsTrigger); + + loadData(2, dateIntervals[2].start, dateIntervals[2].end, testsTrigger); + } + const parseTime = d3.timeParse("%d-%m-%YT%H:%M:%S"), formatTime = d3.timeFormat("%d-%m-%Y %H:%M:%S"), formatMillisecond = d3.timeFormat(".%L"), @@ -224,6 +273,130 @@ return parseFloat(stringMedian); } + var changeDisplay = function(id) { + $('*[id=' + id + ']').each(function() { + $(this).toggle(); + }); + } + + function mergeSuits(results) { + let mergedSuites = new Set(); + + for (let key of Object.keys(results)) { + for (let suite of Object.keys(results[key])) + mergedSuites.add(suite); + } + + return Array.from(mergedSuites); + } + + function printTests(results) { + $(TESTS_TABLE + " tr:not(:first-child)").remove(); + + let markedRow = true; + + for (let suite of mergeSuits(results).sort()) { + let suiteName = suite.split('_').filter((value, index) => index != 0).join('_'); + let testsCntCells = ''; + let testsCells = ''; + + for (let key of Object.keys(results)) { + let obj = results[key]; + let testLength = !obj.hasOwnProperty(suite) || obj[suite].length == 0 ? + '' : obj[suite].length; + + testsCntCells = testsCntCells + '<td class="testsCntCell"><p id="' + suite + '">' + testLength + '</p></td>'; + + testsCells = testsCells + '<td class="testsCell">' + getSuiteTestsHtml(results, suite, key) + '</td>' + } + + $(TESTS_TABLE + " > tbody:last-child").append('<tr class="testsCntRow" onclick="changeDisplay(\'' + suite + '\')">' + + '<td class = "suiteCell">' + suiteName + '</td>' + + '<td></td>' + testsCntCells + '</tr>'); + + $(TESTS_TABLE + " > tbody:last-child").append('<tr class="testsRow" id="' + suite + '">' + + '<td class="testsSuiteCell" onclick="changeDisplay(\'' + suite + '\')"></td>' + + '<td></td>' + testsCells + '</tr>'); + } + } + + function generateCompareTestsResults(results) { + let compareTestsResults = {}; + + for (let key of Object.keys(results)) { + let uniqueObj = {}; + + for (let suite of Object.keys(results[key])) { + let allTests = []; + + for (let key2 of Object.keys(results)) { + if (key == key2) + continue; + + allTests = allTests.concat(results[key2][suite]); + } + + let uniqTests = results[key][suite].filter(function(test) { + return allTests.indexOf(test) == -1; + }); + + if (uniqTests.length != 0) + uniqueObj[suite] = uniqTests; + } + + compareTestsResults[key] = uniqueObj; + } + + return compareTestsResults; + } + + function getSuiteTestsHtml(results, suite, key) { + if (!results[key].hasOwnProperty(suite) || results[key][suite].length == 0) + return ''; + + let res = '<body><div id="' + suite + key + '"style="cursor: default; margin-left: 10px;">'; + + for (let test of results[key][suite].sort()) { + let list = test.toString().split("."); + + if (list.length < 2) + list = test.toString().split(":"); + + let testName = list.pop(); + let testClass = list.pop(); + + res += '<p align="left" title="' + test + '">' + testClass + '.' + testName + + '<a href="#" onclick="getTestRef(\'' + test + '\'' + ',\'' + suite + '\'); return false;">' + + ' >></a>' + '</p>' + } + + res += '</div></body>'; + + return res; + } + + function getTestRef(testName, suite) { + let res = null; + + $.ajax({ + async: false, + url: 'rest/build/testRef?testName=' + testName + '&suiteName=' + suite, + success: function (result) { + res = result; + }, + error: showErrInLoadStatus + } + ); + + if (res == null) { + showTooltip('Link to TC test history is not ready yet. Try later.'); + + return; + } + + window.open(res); + } + function printStatistics(num, map, sinceDate, untilDate) { clearBackgroundFromAllDataCells(); clearGraphs(num); @@ -325,8 +498,8 @@ } $(document).ready(function() { - loadData(1, moment(oneWeekAgo), moment()); - loadData(2, moment(twoWeekAgo), moment(oneWeekAgo)); + loadData(1, moment(oneWeekAgo), moment(), testsTrigger); + loadData(2, moment(twoWeekAgo), moment(oneWeekAgo), testsTrigger); $.ajax({ url: "rest/branches/version", success: showVersionInfo, error: showErrInLoadStatus }); @@ -353,6 +526,7 @@ function fillAllDataCells(num, message) { $('.data' + num).html(message); + $('.testsCntCell').html(message); } function clearGraphs(num) { @@ -375,16 +549,32 @@ $('#showInfo').css('display', ''); } - function loadData(num, sinceDate, untilDate) { + function loadData(num, sinceDate, untilDate, testsTrigger) { loadGif(num); - $.ajax( - { - url: 'rest/build/history?sinceDate=' + sinceDate.format("DDMMYYYY") + - '000001&untilDate=' + untilDate.format("DDMMYYYY") + '235959', + + mergedTestsResults[num] = {}; + + let url = 'rest/build/history?sinceDate=' + sinceDate.format("DDMMYYYY") + + '000001&untilDate=' + untilDate.format("DDMMYYYY") + '235959'; + + if (!testsTrigger) + url = url + '&' + SKIP_TESTS; + + $.ajax({ + url: url, success: function (result) { - printStatistics(num, result, sinceDate, untilDate); + printStatistics(num, result.buildsStatistics, sinceDate, untilDate); + + try { + mergedTestsResults[num] = JSON.parse(result.mergedTestsJson); + } catch (e) { + printImportantMessage(num, "#ff0000", "Invalid server response. Unable to parse JSON"); + } + + printTests(generateCompareTestsResults(mergedTestsResults)); }, - error: showErrInLoadStatus + error: showErrInLoadStatus, + timeout: 1800000 } ); } @@ -392,14 +582,22 @@ $(function() { $('input[name="daterange1"]').daterangepicker( dateRangePickerParam(oneWeekAgo, new Date()), function (start, end, label) { - loadData(1, start, end); + dateIntervals[1].start = start; + + dateIntervals[1].end = end; + + loadData(1, start, end, testsTrigger); }); }); $(function() { $('input[name="daterange2"]').daterangepicker( dateRangePickerParam(twoWeekAgo, oneWeekAgo), function (start, end, label) { - loadData(2, start, end); + dateIntervals[2].start = start; + + dateIntervals[2].end = end; + + loadData(2, start, end, testsTrigger); }); }); @@ -545,6 +743,18 @@ } }); } + + // Get the modal + var modal = $('#myModal'); + + // Get the <span> element that closes the modal + var span = $(".close"); + + // When the user clicks on <span> (x), close the modal + span.click(function() { + modal.hide(); + }) + </script> <div style="visibility:hidden"><div id="modalDialog" title="Information"></div></div> </body> diff --git a/ignite-tc-helper-web/src/main/webapp/css/style-1.5.css b/ignite-tc-helper-web/src/main/webapp/css/style-1.5.css index ca6d537..c5aa999 100644 --- a/ignite-tc-helper-web/src/main/webapp/css/style-1.5.css +++ b/ignite-tc-helper-web/src/main/webapp/css/style-1.5.css @@ -249,6 +249,52 @@ form li:after background-color: #fafaff; } +.testsCntCell { + cursor: pointer; + text-align: center; + vertical-align: middle; +} + +.testsCell { + cursor: default; + word-wrap: break-word; + vertical-align: top; +} + +.testsRow { + display: none; +} + +.suiteCell { + cursor: default; + vertical-align: middle; + padding: 15px; +} + +.testsSuiteCell { + cursor: pointer; +} + +.testsCntRow { + padding: 10px +} + +.testsTable { + width: 96%; + border-collapse: collapse; + margin-left: 2%; + margin-right: 2%; +} + +.testsTable tr:nth-child(4n + 2) { + background-color: #fafaff; +} + +.failedTestsHeader { + vertical-align: middle; + text-align: left; + padding: 5px +} td.details-control { //background: url('../resources/details_open.png') no-repeat center center; @@ -306,3 +352,85 @@ div.tooltip { height: auto; } +.switch { + position: relative; + display: inline-block; + width: 60px; + height: 34px; +} + +.switch input {display:none;} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 26px; + width: 26px; + left: 4px; + bottom: 4px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + +input:checked + .slider { + background-color: #12AD5E; +} + +input:focus + .slider { + box-shadow: 0 0 1px #12AD5E; +} + +input:checked + .slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); +} + +.modal { + display: none; + position: fixed; + z-index: 1; + left: 0; + top: 0; + width: 100%; + height: 100%; +} + +.modal-content { + margin-top: 100px; + margin-left: auto; + margin-right: auto; + text-align: center; + background-color: #fefefe; + padding: 10px; + border: 5px solid #12AD5E; + width: 30%; +} + +.close { + color: #12AD5E; + float: right; + font-size: 28px; + font-weight: bold; +} + +.close:hover, +.close:focus { + color: black; + text-decoration: none; + cursor: pointer; +} +