You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by dp...@apache.org on 2018/10/26 12:16:12 UTC
[ignite-teamcity-bot] branch master updated: IGNITE-9849 Updated
tests along with statistics, changed switch button - Fixes #38.
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 15d88a6 IGNITE-9849 Updated tests along with statistics, changed switch button - Fixes #38.
15d88a6 is described below
commit 15d88a63989b2d39c7e5f3a08cf0ab2193fe5b51
Author: zzzadruga <zz...@gmail.com>
AuthorDate: Fri Oct 26 15:08:55 2018 +0300
IGNITE-9849 Updated tests along with statistics, changed switch button - Fixes #38.
Signed-off-by: Dmitriy Pavlov <dp...@apache.org>
---
.../ignite/ci/tcbot/condition/BuildCondition.java | 91 +++
.../tcbot/condition/BuildConditionCompacted.java | 71 ++
.../ci/tcbot/condition/BuildConditionDao.java | 78 ++
.../ci/teamcity/ignited/ITeamcityIgnited.java | 17 +-
.../ci/teamcity/ignited/TeamcityIgnitedImpl.java | 19 +-
.../ci/teamcity/ignited/TeamcityIgnitedModule.java | 2 +
.../web/model/current/BuildStatisticsSummary.java | 22 +-
.../ignite/ci/web/model/hist/BuildsHistory.java | 35 +-
.../ci/web/rest/build/GetBuildTestFailures.java | 51 +-
.../src/main/webapp/comparison.html | 903 ++++++++++++++-------
.../src/main/webapp/css/style-1.5.css | 119 +--
11 files changed, 1024 insertions(+), 384 deletions(-)
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/condition/BuildCondition.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/condition/BuildCondition.java
new file mode 100644
index 0000000..6a1e0fa
--- /dev/null
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/condition/BuildCondition.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ci.tcbot.condition;
+
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * Mark build as valid or invalid.
+ */
+public class BuildCondition {
+ /** Build id. */
+ public int buildId;
+
+ /** Username. */
+ public String username;
+
+ /** Is valid. */
+ public boolean isValid;
+
+ /** Date. */
+ public Date date;
+
+ /** Field, where build was marked. */
+ public String field;
+
+ /**
+ * Default constructor.
+ */
+ public BuildCondition(){}
+
+ /**
+ * @param buildId Build id.
+ * @param username Username.
+ * @param isValid Is valid.
+ * @param field Field.
+ */
+ public BuildCondition(int buildId, String username, boolean isValid, String field) {
+ this.buildId = buildId;
+ this.username = username;
+ this.isValid = isValid;
+ this.date = new Date();
+ this.field = field;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return "BuildCondition{" +
+ "buildId=" + buildId +
+ ", username='" + username + '\'' +
+ ", isValid=" + isValid +
+ ", date=" + date +
+ ", field='" + field + '\'' +
+ '}';
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+
+ if (!(o instanceof BuildCondition))
+ return false;
+
+ BuildCondition that = (BuildCondition)o;
+
+ return buildId == that.buildId &&
+ isValid == that.isValid &&
+ Objects.equals(username, that.username);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return Objects.hash(buildId, username, isValid);
+ }
+}
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/condition/BuildConditionCompacted.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/condition/BuildConditionCompacted.java
new file mode 100644
index 0000000..83beee8
--- /dev/null
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/condition/BuildConditionCompacted.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ci.tcbot.condition;
+
+import java.util.Date;
+import org.apache.ignite.ci.teamcity.ignited.IStringCompactor;
+
+public class BuildConditionCompacted {
+ /** Build id. */
+ int buildId = -1;
+
+ /** Username. */
+ int username = -1;
+
+ /** Is valid. */
+ int isValid = -1;
+
+ /** Date. */
+ long date = -1;
+
+ /** Field. */
+ int field = -1;
+
+ /**
+ * Default constructor.
+ */
+ public BuildConditionCompacted() {
+ }
+
+ /**
+ * @param compactor Compacter.
+ * @param cond Build condition.
+ */
+ public BuildConditionCompacted(IStringCompactor compactor, BuildCondition cond) {
+ buildId = cond.buildId;
+ username = compactor.getStringId(cond.username);
+ isValid = cond.isValid ? 1 : 0;
+ date = cond.date.getTime();
+ field = compactor.getStringId(cond.field);
+ }
+
+ /**
+ * @param compactor Compactor.
+ */
+ public BuildCondition toBuildCondition(IStringCompactor compactor) {
+ BuildCondition cond = new BuildCondition();
+
+ cond.buildId = buildId;
+ cond.isValid = isValid == 1;
+ cond.date = new Date(date);
+ cond.username = compactor.getStringFromId(username);
+ cond.field = compactor.getStringFromId(field);
+
+ return cond;
+ }
+}
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/condition/BuildConditionDao.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/condition/BuildConditionDao.java
new file mode 100644
index 0000000..633eddc
--- /dev/null
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/condition/BuildConditionDao.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.ci.tcbot.condition;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.ci.db.TcHelperDb;
+import org.apache.ignite.ci.teamcity.ignited.IStringCompactor;
+
+public class BuildConditionDao {
+ /** Cache name*/
+ public static final String BUILD_CONDITIONS_CACHE_NAME = "buildConditions";
+
+ /** Ignite provider. */
+ @Inject private Provider<Ignite> igniteProvider;
+
+ /** Builds cache. */
+ private IgniteCache<Long, BuildConditionCompacted> buildsCache;
+
+ /** Compactor. */
+ @Inject private IStringCompactor compactor;
+
+ /**
+ * Initialize
+ */
+ public void init () {
+ Ignite ignite = igniteProvider.get();
+ buildsCache = ignite.getOrCreateCache(TcHelperDb.getCacheV2Config(BUILD_CONDITIONS_CACHE_NAME));
+ }
+
+ /**
+ * @param srvIdMaskHigh Server id mask high.
+ * @param buildId Build id.
+ */
+ private long buildIdToCacheKey(long srvIdMaskHigh, int buildId) {
+
+ return (long)buildId | srvIdMaskHigh << 32;
+ }
+
+ public BuildCondition getBuildCondition(long srvIdMaskHigh, int buildId) {
+ long key = buildIdToCacheKey(srvIdMaskHigh, buildId);
+
+ return buildsCache.containsKey(key) ? buildsCache.get(key).toBuildCondition(compactor) : null;
+ }
+
+ public boolean setBuildCondition(long srvIdMaskHigh, BuildCondition cond) {
+ long key = buildIdToCacheKey(srvIdMaskHigh, cond.buildId);
+
+ if (cond.isValid)
+ return buildsCache.remove(key);
+ else {
+ if (!buildsCache.containsKey(key)) {
+ buildsCache.put(key, new BuildConditionCompacted(compactor, cond));
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/ITeamcityIgnited.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/ITeamcityIgnited.java
index fadf0fb..bd85911 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/ITeamcityIgnited.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/ITeamcityIgnited.java
@@ -18,6 +18,7 @@ package org.apache.ignite.ci.teamcity.ignited;
import java.util.List;
import javax.annotation.Nullable;
+import org.apache.ignite.ci.tcbot.condition.BuildCondition;
import org.apache.ignite.ci.tcmodel.hist.BuildRef;
import org.apache.ignite.ci.tcmodel.result.Build;
@@ -40,7 +41,7 @@ public interface ITeamcityIgnited {
@Nullable String branchName);
/**
- * Trigger build. Enforces TC Bot to load all buidls related to this triggered one.
+ * Trigger build. Enforces TC Bot to load all builds related to this triggered one.
*
* @param buildTypeId Build type identifier.
* @param branchName Branch name.
@@ -56,4 +57,18 @@ public interface ITeamcityIgnited {
public static int serverIdToInt(String srvId) {
return Math.abs(srvId.hashCode());
}
+
+ /**
+ * Check build condition.
+ *
+ * @param buildId Build id.
+ */
+ public boolean buildIsValid(int buildId);
+
+ /**
+ * Set build condition.
+ *
+ * @param cond Condition.
+ */
+ public boolean setBuildCondition(BuildCondition cond);
}
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedImpl.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedImpl.java
index 0667976..b5bab97 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedImpl.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedImpl.java
@@ -30,6 +30,8 @@ import org.apache.ignite.ci.ITeamcity;
import org.apache.ignite.ci.di.AutoProfiling;
import org.apache.ignite.ci.di.MonitoredTask;
import org.apache.ignite.ci.di.scheduler.IScheduler;
+import org.apache.ignite.ci.tcbot.condition.BuildCondition;
+import org.apache.ignite.ci.tcbot.condition.BuildConditionDao;
import org.apache.ignite.ci.tcmodel.hist.BuildRef;
import org.apache.ignite.ci.tcmodel.result.Build;
import org.apache.ignite.ci.teamcity.pure.ITeamcityConn;
@@ -47,16 +49,19 @@ public class TeamcityIgnitedImpl implements ITeamcityIgnited {
/** Build reference DAO. */
@Inject private BuildRefDao buildRefDao;
+ /** Build condition DAO. */
+ @Inject private BuildConditionDao buildConditionDao;
+
/** Server ID mask for cache Entries. */
private long srvIdMaskHigh;
-
public void init(String srvId, ITeamcityConn conn) {
this.srvId = srvId;
this.conn = conn;
srvIdMaskHigh = ITeamcityIgnited.serverIdToInt(srvId);
buildRefDao.init(); //todo init somehow in auto
+ buildConditionDao.init();
}
/** {@inheritDoc} */
@@ -91,6 +96,18 @@ public class TeamcityIgnitedImpl implements ITeamcityIgnited {
return build;
}
+ /** {@inheritDoc} */
+ @Override public boolean buildIsValid(int buildId) {
+ BuildCondition cond = buildConditionDao.getBuildCondition(srvIdMaskHigh, buildId);
+
+ return cond == null || cond.isValid;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean setBuildCondition(BuildCondition cond) {
+ return buildConditionDao.setBuildCondition(srvIdMaskHigh, cond);
+ }
+
/**
*
*/
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedModule.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedModule.java
index 3909e8d..16ad2cc 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedModule.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/teamcity/ignited/TeamcityIgnitedModule.java
@@ -18,6 +18,7 @@ package org.apache.ignite.ci.teamcity.ignited;
import com.google.inject.AbstractModule;
import com.google.inject.internal.SingletonScope;
+import org.apache.ignite.ci.tcbot.condition.BuildConditionDao;
import org.apache.ignite.ci.teamcity.pure.ITeamcityHttpConnection;
import org.apache.ignite.ci.teamcity.restcached.TcRestCachedModule;
import org.jetbrains.annotations.Nullable;
@@ -33,6 +34,7 @@ public class TeamcityIgnitedModule extends AbstractModule {
@Override protected void configure() {
bind(ITeamcityIgnitedProvider.class).to(TcIgnitedCachingProvider.class).in(new SingletonScope());
bind(BuildRefDao.class).in(new SingletonScope());
+ bind(BuildConditionDao.class).in(new SingletonScope());
bind(IStringCompactor.class).to(IgniteStringCompactor.class).in(new SingletonScope());
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 d3cd968..0c604cd 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
@@ -17,6 +17,8 @@
package org.apache.ignite.ci.web.model.current;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -32,18 +34,20 @@ import org.apache.ignite.ci.tcmodel.hist.BuildRef;
import org.apache.ignite.ci.tcmodel.result.Build;
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.
*/
-public class BuildStatisticsSummary extends UpdateInfo implements IBackgroundUpdatable {
+public class BuildStatisticsSummary {
/** Short problem names. */
public static final String TOTAL = "TOTAL";
- private static Map<String, String> shortProblemNames = new HashMap<>();
+ /** Short problem names map. Full name - key, short name - value. */
+ public static BiMap<String, String> shortProblemNames = HashBiMap.create();
+
+ /** Full problem names map. Short name - key, full name - value. */
+ public static BiMap<String, String> fullProblemNames;
static {
shortProblemNames.put(TOTAL, "TT");
@@ -51,6 +55,8 @@ public class BuildStatisticsSummary extends UpdateInfo implements IBackgroundUpd
shortProblemNames.put(ProblemOccurrence.TC_JVM_CRASH, "JC");
shortProblemNames.put(ProblemOccurrence.TC_OOME, "OO");
shortProblemNames.put(ProblemOccurrence.TC_EXIT_CODE, "EC");
+
+ fullProblemNames = shortProblemNames.inverse();
}
/** Build with test and problems references. */
@@ -74,6 +80,9 @@ public class BuildStatisticsSummary extends UpdateInfo implements IBackgroundUpd
/** Is fake stub. */
public boolean isFakeStub;
+ /** Is valid. */
+ public boolean isValid = true;
+
/**
* @param buildId Build id.
*/
@@ -204,11 +213,6 @@ public class BuildStatisticsSummary extends UpdateInfo implements IBackgroundUpd
}
/** {@inheritDoc} */
- @Override public void setUpdateRequired(boolean update) {
- updateRequired = update;
- }
-
- /** {@inheritDoc} */
@Override public boolean equals(Object o) {
if (this == o)
return true;
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
index 90b78f0..7f88a98 100644
--- 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
@@ -19,14 +19,18 @@ package org.apache.ignite.ci.web.model.hist;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.Lists;
import java.io.UncheckedIOException;
+import java.lang.reflect.Array;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -35,6 +39,8 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
import javax.servlet.ServletContext;
import org.apache.ignite.ci.IAnalyticsEnabledTeamcity;
import org.apache.ignite.ci.ITcHelper;
@@ -43,6 +49,8 @@ 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.teamcity.ignited.ITeamcityIgnited;
+import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnitedProvider;
import org.apache.ignite.ci.user.ICredentialsProv;
import org.apache.ignite.ci.web.CtxListener;
import org.apache.ignite.ci.web.model.current.BuildStatisticsSummary;
@@ -99,14 +107,26 @@ public class BuildsHistory {
IAnalyticsEnabledTeamcity teamcity = tcHelper.server(srvId, prov);
+ ITeamcityIgnitedProvider tcIgnitedProv = CtxListener.getInjector(context)
+ .getInstance(ITeamcityIgnitedProvider.class);
+
+ ITeamcityIgnited ignited = tcIgnitedProv.server(srvId, prov);
+
int[] finishedBuildsIds = teamcity.getBuildNumbersFromHistory(buildTypeId, branchName,
sinceDateFilter, untilDateFilter);
- initStatistics(teamcity, finishedBuildsIds);
+ Map<Integer, Boolean> buildIdsWithConditions = IntStream.of(finishedBuildsIds)
+ .boxed().collect(Collectors.toMap(v -> v, ignited::buildIsValid, (e1, e2) -> e1, LinkedHashMap::new));
- if (!skipTests) {
- initFailedTests(teamcity, finishedBuildsIds);
- }
+ initStatistics(teamcity, buildIdsWithConditions);
+
+ List<Integer> validBuilds = buildIdsWithConditions.keySet()
+ .stream()
+ .filter(buildIdsWithConditions::get)
+ .collect(Collectors.toList());
+
+ if (!skipTests)
+ initFailedTests(teamcity, validBuilds);
ObjectMapper objectMapper = new ObjectMapper();
@@ -118,12 +138,13 @@ public class BuildsHistory {
}
/** */
- private void initStatistics(IAnalyticsEnabledTeamcity teamcity, int[] buildIds) {
+ private void initStatistics(IAnalyticsEnabledTeamcity teamcity, Map<Integer, Boolean> buildIdsWithConditions) {
List<Future<BuildStatisticsSummary>> buildStatiscsFutures = new ArrayList<>();
- for (int buildId : buildIds) {
+ for (int buildId : buildIdsWithConditions.keySet()) {
Future<BuildStatisticsSummary> buildFuture = CompletableFuture.supplyAsync(() -> {
BuildStatisticsSummary buildsStatistic = new BuildStatisticsSummary(buildId);
+ buildsStatistic.isValid = buildIdsWithConditions.get(buildId);
buildsStatistic.initialize(teamcity);
@@ -178,7 +199,7 @@ public class BuildsHistory {
}
/** */
- private void initFailedTests(IAnalyticsEnabledTeamcity teamcity, int[] buildIds) {
+ private void initFailedTests(IAnalyticsEnabledTeamcity teamcity, List<Integer> buildIds) {
List<Future<Void>> buildProcessorFutures = new ArrayList<>();
for (int buildId : buildIds) {
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 fdfe218..dffbb69 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,10 +17,12 @@
package org.apache.ignite.ci.web.rest.build;
+import com.google.common.collect.BiMap;
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.condition.BuildCondition;
import org.apache.ignite.ci.tcbot.chain.BuildChainProcessor;
import org.apache.ignite.ci.IAnalyticsEnabledTeamcity;
import org.apache.ignite.ci.ITcHelper;
@@ -29,8 +31,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.teamcity.ignited.ITeamcityIgnited;
+import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnitedProvider;
import org.apache.ignite.ci.tcmodel.result.tests.TestRef;
import org.apache.ignite.ci.user.ICredentialsProv;
+import org.apache.ignite.ci.web.model.current.BuildStatisticsSummary;
import org.apache.ignite.ci.web.model.hist.BuildsHistory;
import org.apache.ignite.ci.web.BackgroundUpdater;
import org.apache.ignite.ci.web.CtxListener;
@@ -53,6 +58,8 @@ import javax.ws.rs.core.MediaType;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger;
+import static com.google.common.base.Strings.isNullOrEmpty;
+
@Path(GetBuildTestFailures.BUILD)
@Produces(MediaType.APPLICATION_JSON)
public class GetBuildTestFailures {
@@ -191,6 +198,44 @@ public class GetBuildTestFailures {
+ "&tab=testDetails" : null;
}
+ /**
+ * Mark builds as "valid" or "invalid".
+ *
+ * @param buildId Build id.
+ * @param isValid Is valid.
+ * @param field Field.
+ * @param serverId Server.
+ */
+ @GET
+ @Path("condition")
+ public Boolean setBuildCondition(
+ @QueryParam("buildId") Integer buildId,
+ @QueryParam("isValid") Boolean isValid,
+ @QueryParam("field") String field,
+ @QueryParam("serverId") String serverId) {
+ String srvId = isNullOrEmpty(serverId) ? "apache" : serverId;
+
+ if (buildId == null || isValid == null)
+ return null;
+
+ final ICredentialsProv prov = ICredentialsProv.get(req);
+
+ if (!prov.hasAccess(srvId))
+ throw ServiceUnauthorizedException.noCreds(srvId);
+
+ ITeamcityIgnitedProvider tcIgnitedProv = CtxListener.getInjector(ctx)
+ .getInstance(ITeamcityIgnitedProvider.class);
+
+ ITeamcityIgnited ignited = tcIgnitedProv.server(srvId, prov);
+
+ BiMap<String, String> problemNames = BuildStatisticsSummary.fullProblemNames;
+
+ BuildCondition buildCond =
+ new BuildCondition(buildId, prov.getPrincipalId(), isValid, problemNames.getOrDefault(field, field));
+
+ return ignited.setBuildCondition(buildCond);
+ }
+
@GET
@Path("history")
public BuildsHistory getBuildsHistory(
@@ -210,10 +255,10 @@ public class GetBuildTestFailures {
if (Boolean.valueOf(skipTests))
builder.skipTests();
- BuildsHistory buildsHistory = builder.build();
+ BuildsHistory buildsHist = builder.build();
- buildsHistory.initialize(ICredentialsProv.get(req), ctx);
+ buildsHist.initialize(ICredentialsProv.get(req), ctx);
- return buildsHistory;
+ return buildsHist;
}
}
diff --git a/ignite-tc-helper-web/src/main/webapp/comparison.html b/ignite-tc-helper-web/src/main/webapp/comparison.html
index 65348c9..1d4c13c 100644
--- a/ignite-tc-helper-web/src/main/webapp/comparison.html
+++ b/ignite-tc-helper-web/src/main/webapp/comparison.html
@@ -11,155 +11,154 @@
<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">
+ <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css"
+ integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous">
<script src="js/common-1.6.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<br>
<br>
+
<table style="table-layout: fixed" class="compare">
<tr>
- <th class="section" width="15%">DATE INTERVAL</th>
- <th width="5%"></th>
- <th style="text-align: center;" width="40%"><input type='text' name='daterange1'/></th>
- <th style="text-align: center;" width="40%"><input type='text' name='daterange2'/></th>
- </tr><tr><td></td><td></td><td></td></tr>
- <tr><td class="field">DURATION</td>
- <td><img id="clickGraphDuration" src='/img/browser.png'></td>
- <td class="mmm1 data1" id="Duration1" data-allow-highlight="true"></td>
- <td class="mmm2 data2" id="Duration2" data-allow-highlight="true"></td>
- </tr>
- <tr id="showGraphDuration" style="display: none;"><td></td>
- <td></td>
- <td style="text-align: center;"><svg id="graphDuration1" width="500" height="200"></svg></td>
- <td style="text-align: center;"><svg id="graphDuration2" width="500" height="200"></svg></td>
+ <th class="section" width="13%">DATE INTERVAL</th>
+ <th width="3%"></th>
+ <th style="text-align: center;" width="42%"><input type='text' name='daterange1'/></th>
+ <th style="text-align: center;" width="42%"><input type='text' name='daterange2'/></th>
</tr>
- <tr id="showInfo" style="display: none;">
- <td class="section">FEATURES</td><td></td>
+ <tr id="showInfo">
+ <td class="section">SHOW INVALID BUILD</td>
+ <td class="icon"><div class="switch-btn builds"></div></td>
<td style="text-align: center;" id="info1"></td>
<td style="text-align: center;" id="info2"></td>
</tr>
- <tr><td class="section">TESTS</td><td></td><td class="title mmm1"></td><td class="title mmm2"></td></tr>
+ <tr><td class="field">DURATION</td>
+ <td class="icon"><img id="clickGraphDuration" src='/img/browser.png'></td>
+ <td class="mmm data 1" id="Duration1" data-allow-highlight="true"></td>
+ <td class="mmm data 2" id="Duration2" data-allow-highlight="true"></td>
+ </tr>
+ <tr id="showGraphDuration" style="display: none;"><td></td>
+ <td></td>
+ <td style="text-align: center;"><svg class="graph 1" id="graphDuration1" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 2" id="graphDuration2" width="500" height="200"></svg></td>
+ </tr><tr></tr>
+ <tr><td class="section">TESTS</td><td></td><td class="mmm title 1"></td><td class="mmm title 2"></td></tr>
<tr><td class="field">COUNT</td>
- <td><img id="clickGraphCount" src='/img/browser.png'></td>
- <td class="mmm1 data1" id="Count1" data-allow-highlight="false"></td>
- <td class="mmm2 data2" id="Count2" data-allow-highlight="false"></td>
+ <td class="icon"><img id="clickGraphCount" src='/img/browser.png'></td>
+ <td class="mmm data 1" id="Count1" data-allow-highlight="false"></td>
+ <td class="mmm data 2" id="Count2" data-allow-highlight="false"></td>
</tr>
<tr id="showGraphCount" style="display: none;"><td></td>
<td></td>
- <td style="text-align: center;"><svg id="graphCount1" width="500" height="200"></svg></td>
- <td style="text-align: center;"><svg id="graphCount2" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 1" id="graphCount1" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 2" id="graphCount2" width="500" height="200"></svg></td>
</tr>
<tr><td class="field">PASSED</td>
- <td><img id="clickGraphPassed" src='/img/browser.png'></td>
- <td class="mmm1 data1" id="Passed1" data-allow-highlight="false"></td>
- <td class="mmm2 data2" id="Passed2" data-allow-highlight="false"></td>
+ <td class="icon"><img id="clickGraphPassed" src='/img/browser.png'></td>
+ <td class="mmm data 1" id="Passed1" data-allow-highlight="false"></td>
+ <td class="mmm data 2" id="Passed2" data-allow-highlight="false"></td>
</tr>
<tr id="showGraphPassed" style="display: none;"><td></td>
<td></td>
- <td style="text-align: center;"><svg id="graphPassed1" width="500" height="200"></svg></td>
- <td style="text-align: center;"><svg id="graphPassed2" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 1" id="graphPassed1" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 2" id="graphPassed2" width="500" height="200"></svg></td>
</tr>
<tr><td class="field">FAILED</td>
- <td><img id="clickGraphFailed" src='/img/browser.png'></td>
- <td class="mmm1 data1" id="Failed1" data-allow-highlight="true"></td>
- <td class="mmm2 data2" id="Failed2" data-allow-highlight="true"></td>
+ <td class="icon"><img id="clickGraphFailed" src='/img/browser.png'></td>
+ <td class="mmm data 1" id="Failed1" data-allow-highlight="true"></td>
+ <td class="mmm data 2" id="Failed2" data-allow-highlight="true"></td>
</tr>
<tr id="showGraphFailed" style="display: none;"><td></td>
<td></td>
- <td style="text-align: center;"><svg id="graphFailed1" width="500" height="200"></svg></td>
- <td style="text-align: center;"><svg id="graphFailed2" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 1" id="graphFailed1" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 2" id="graphFailed2" width="500" height="200"></svg></td>
</tr>
<tr><td class="field">IGNORED</td>
- <td><img id="clickGraphIgnored" src='/img/browser.png'></td>
- <td class="mmm1 data1" id="Ignored1" data-allow-highlight="false"></td>
- <td class="mmm2 data2" id="Ignored2" data-allow-highlight="false"></td>
+ <td class="icon"><img id="clickGraphIgnored" src='/img/browser.png'></td>
+ <td class="mmm data 1" id="Ignored1" data-allow-highlight="false"></td>
+ <td class="mmm data 2" id="Ignored2" data-allow-highlight="false"></td>
</tr>
<tr id="showGraphIgnored" style="display: none;"><td></td>
<td></td>
- <td style="text-align: center;"><svg id="graphIgnored1" width="500" height="200"></svg></td>
- <td style="text-align: center;"><svg id="graphIgnored2" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 1" id="graphIgnored1" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 2" id="graphIgnored2" width="500" height="200"></svg></td>
</tr>
<tr><td class="field">MUTED</td>
- <td><img id="clickGraphMuted" src='/img/browser.png'></td>
- <td class="mmm1 data1" id="Muted1" data-allow-highlight="false"></td>
- <td class="mmm2 data2" id="Muted2" data-allow-highlight="false"></td></tr>
+ <td class="icon"><img id="clickGraphMuted" src='/img/browser.png'></td>
+ <td class="mmm data 1" id="Muted1" data-allow-highlight="false"></td>
+ <td class="mmm data 2" id="Muted2" data-allow-highlight="false"></td></tr>
<tr id="showGraphMuted" style="display: none;"><td></td>
<td></td>
- <td style="text-align: center;"><svg id="graphMuted1" width="500" height="200"></svg></td>
- <td style="text-align: center;"><svg id="graphMuted2" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 1" id="graphMuted1" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 2" id="graphMuted2" width="500" height="200"></svg></td>
</tr>
- <tr><td class="section">PROBLEMS</td><td></td><td class="title mmm1"></td><td class="title mmm2"></td></tr>
+ <tr><td class="section">PROBLEMS</td><td></td><td class="mmm title 1"></td><td class="mmm title 2"></td></tr>
<tr style="display: none;"><td></td><td></td><td></td></tr>
<tr><td class="field">TOTAL</td>
- <td><img id="clickGraphTT" src='/img/browser.png'></td>
- <td class="mmm1 data1" id="TT1" data-allow-highlight="true"></td>
- <td class="mmm2 data2" id="TT2" data-allow-highlight="true"></td></tr>
+ <td class="icon"><img id="clickGraphTT" src='/img/browser.png'></td>
+ <td class="mmm data 1" id="TT1" data-allow-highlight="true"></td>
+ <td class="mmm data 2" id="TT2" data-allow-highlight="true"></td></tr>
<tr id="showGraphTT" style="display: none;"><td></td>
<td></td>
- <td style="text-align: center;"><svg id="graphTT1" width="500" height="200"></svg></td>
- <td style="text-align: center;"><svg id="graphTT2" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 1" id="graphTT1" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 2" id="graphTT2" width="500" height="200"></svg></td>
</tr>
<tr><td class="field">EXECUTION TIMEOUT</td>
- <td><img id="clickGraphET" src='/img/browser.png'></td>
- <td class="mmm1 data1" id="ET1" data-allow-highlight="true"></td>
- <td class="mmm2 data2" id="ET2" data-allow-highlight="true"></td>
+ <td class="icon"><img id="clickGraphET" src='/img/browser.png'></td>
+ <td class="mmm data 1" id="ET1" data-allow-highlight="true"></td>
+ <td class="mmm data 2" id="ET2" data-allow-highlight="true"></td>
</tr>
<tr id="showGraphET" style="display: none;"><td></td>
<td></td>
- <td style="text-align: center;"><svg id="graphET1" width="500" height="200"></svg></td>
- <td style="text-align: center;"><svg id="graphET2" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 1" id="graphET1" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 2" id="graphET2" width="500" height="200"></svg></td>
</tr>
<tr><td class="field">JVM CRASH</td>
- <td><img id="clickGraphJC" src='/img/browser.png'></td>
- <td class="mmm1 data1" id="JC1" data-allow-highlight="true"></td>
- <td class="mmm2 data2" id="JC2" data-allow-highlight="true"></td>
+ <td class="icon"><img id="clickGraphJC" src='/img/browser.png'></td>
+ <td class="mmm data 1" id="JC1" data-allow-highlight="true"></td>
+ <td class="mmm data 2" id="JC2" data-allow-highlight="true"></td>
</tr>
<tr id="showGraphJC" style="display: none;"><td></td>
<td></td>
- <td style="text-align: center;"><svg id="graphJC1" width="500" height="200"></svg></td>
- <td style="text-align: center;"><svg id="graphJC2" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 1" id="graphJC1" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 2" id="graphJC2" width="500" height="200"></svg></td>
</tr>
<tr><td class="field">OOME</td>
- <td><img id="clickGraphOO" src='/img/browser.png'></td>
- <td class="mmm1 data1" id="OO1" data-allow-highlight="true"></td>
- <td class="mmm2 data2" id="OO2" data-allow-highlight="true"></td>
+ <td class="icon"><img id="clickGraphOO" src='/img/browser.png'></td>
+ <td class="mmm data 1" id="OO1" data-allow-highlight="true"></td>
+ <td class="mmm data 2" id="OO2" data-allow-highlight="true"></td>
</tr>
<tr id="showGraphOO" style="display: none;"><td></td>
<td></td>
- <td style="text-align: center;"><svg id="graphOO1" width="500" height="200"></svg></td>
- <td style="text-align: center;"><svg id="graphOO2" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 1" id="graphOO1" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 2" id="graphOO2" width="500" height="200"></svg></td>
</tr>
<tr><td class="field">EXIT CODE</td>
- <td><img id="clickGraphEC" src='/img/browser.png'></td>
- <td class="mmm1 data1" id="EC1" data-allow-highlight="true"></td>
- <td class="mmm2 data2" id="EC2" data-allow-highlight="true"></td>
+ <td class="icon"><img id="clickGraphEC" src='/img/browser.png'></td>
+ <td class="mmm data 1" id="EC1" data-allow-highlight="true"></td>
+ <td class="mmm data 2" id="EC2" data-allow-highlight="true"></td>
</tr>
<tr id="showGraphEC" style="display: none;"><td></td>
<td></td>
- <td style="text-align: center;"><svg id="graphEC1" width="500" height="200"></svg></td>
- <td style="text-align: center;"><svg id="graphEC2" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 1" id="graphEC1" width="500" height="200"></svg></td>
+ <td style="text-align: center;"><svg class="graph 2" id="graphEC2" width="500" height="200"></svg></td>
</tr>
- <tr><td class="section">OTHER METRICS</td><td></td><td class="title t1"></td><td class="title t2"></td></tr>
+ <tr><td class="section">OTHER METRICS</td><td></td><td class="total title 1"></td><td class="total title 2"></td></tr>
<tr style="display: none;"><td></td><td></td><td></td></tr>
<tr><td class="field">RUNS COUNT</td>
<td></td>
- <td class="t1 data1" id="RunsCount1"></td>
- <td class="t2 data2" id="RunsCount2"></td>
+ <td class="total data 1" id="RunsCount1"></td>
+ <td class="total data 2" 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>
+ <th class="failedTestsHeader" width="13%">FAILED TESTS</th>
+ <th width="3%"><div class="switch-btn tests"></div></th>
+ <th width="42%"></th>
+ <th width="42%"></th>
</tr>
</tbody>
</table>
@@ -176,20 +175,27 @@
<div id="version"></div>
<script>
- 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 : {} };
-
+ let oneWeekAgo = new Date(),
+ twoWeekAgo = new Date(),
+ invalidInclude = false,
+ markBuildId = 0,
+ testsTrigger = false;
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
twoWeekAgo.setDate(twoWeekAgo.getDate() - 14);
+ function getDateFewWeeksAgo(numberOfWeeksAgo) {
+ let date = new Date();
+
+ if (isDefinedAndFilled(numberOfWeeksAgo) && Number.isInteger(numberOfWeeksAgo))
+ date.setDate(date.getDate() - numberOfWeeksAgo * 7);
+
+ return date;
+ }
+
+ /** Structure for storing tests by suites parsed response for every date interval. */
+ let mergedTestsResults = {1 : {}, 2 : {} };
+
let dateIntervals = {1: {start: moment(oneWeekAgo), end: moment()},
2: {start: moment(twoWeekAgo), end: moment(oneWeekAgo)}};
@@ -218,59 +224,130 @@
formatYear = d3.timeFormat("%Y"),
parseDuration = d3.utcParse("%s");
- const tOcc = ["Count", "Passed", "Failed", "Ignored", "Muted"],
- prOcc = ["TT", "ET", "JC", "OO", "EC"],
+ const fieldNames = new Map([['Duration', 'Duration'], ['Count', 'Count'], ['Passed', 'Passed'], ['Failed','Failed'],
+ ['Ignored','Ignored'], ['Muted','Muted'], ['OO' ,'OOME'], ['TT', 'Total'], ['JC', 'JVM crash'],
+ ['EC', 'Exit code'], ['ET', 'Execution timeout']]),
mmmTitle = "min - median - max",
tTitle = "total",
- duration = "Duration";
+ duration = "Duration",
+ TESTS_TABLE = '#testsTable',
+ SKIP_TESTS = 'skipTests=true',
+ data = [];
+
+ class Data {
+ constructor(num, array, sinceDate, untilDate) {
+ this.num = num;
+ this.map = new Map(array.map(item => [item.buildId, new BuildStatistics(item)]));
+ this.sinceDate = sinceDate;
+ this.untilDate = untilDate;
+ }
- function multiFormat(date) {
- return (d3.timeSecond(date) < date ? formatMillisecond
- : d3.timeMinute(date) < date ? formatSecond
- : d3.timeHour(date) < date ? formatMinute
- : d3.timeDay(date) < date ? formatHour
- : d3.timeMonth(date) < date ? (d3.timeWeek(date) < date ? formatDay : formatWeek)
- : d3.timeYear(date) < date ? formatMonth
- : formatYear)(date);
- }
+ get hasInvalidBuilds() {
+ return this.invalidBuildsCount !== 0;
+ }
- function dateRangePickerParam(data1, data2) {
- return {
- "opens" : 'left',
- "maxSpan": { "days": 7 },
- "locale": {
- "format": "DD/MM/YYYY", "separator": " - ", "applyLabel": "Apply", "cancelLabel": "Cancel",
- "fromLabel": "From", "toLabel": "To", "customRangeLabel": "Custom", "weekLabel": "W",
- "daysOfWeek": [ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" ],
- "monthNames": [ "January", "February", "March", "April", "May", "June", "July",
- "August", "September", "October", "November", "December" ],
- "firstDay": 1
- },
- "startDate": moment(data1).format("DD-MM-YYYY"), "endDate": moment(data2).format("DD-MM-YYYY")
+ get invalidBuildsCount() {
+ let count = 0;
+ for(let item of this.map.values()) {
+ if (!item.isValid)
+ count++
+ }
+
+ return count;
}
- }
- function getMinMaxMedian(arr) {
- let newArr = arr.slice();
- newArr = newArr.sort(function(a, b){ return a - b; });
+ get isEmpty() {
+ return this.builds.length === 0;
+ }
- let i = newArr.length / 2;
+ get builds() {
+ let builds = [];
+ for (let item of this.map.values()) {
+ if (invalidInclude || item.isValid) {
+ builds.push(item);
+ }
+ }
+ return builds;
+ }
- let result = {};
- result.median = i % 1 === 0 ? (newArr[i - 1] + newArr[i]) / 2 : newArr[Math.floor(i)];
- result.min = newArr[0];
- result.max = newArr[newArr.length - 1];
+ get realSinceDate() {
+ return this.builds[0].date;
+ }
- return result;
+ get realUntilDate() {
+ return this.builds[this.builds.length - 1].date;
+ }
+
+ get fieldsStatistics() {
+ let fieldsStatistics = new FieldsStatistics(this.num);
+ for(let build of this.builds) {
+ for (let fieldName of fieldNames.keys())
+ fieldsStatistics[fieldName].push(build.stat[fieldName]);
+
+ fieldsStatistics.dates.push(build.date);
+ fieldsStatistics.buildIds.push(build.date);
+ }
+
+ return fieldsStatistics;
+ }
}
- function parseMedian(string) {
- let stringMedian = string.substring(string.indexOf("-") + 2, string.lastIndexOf("-") - 1);
+ class FieldsStatistics {
+ constructor(num){
+ this.dates = [];
+ this.buildIds = [];
+ this.num = num;
+ this['Duration'] = [];
+ this['DurationF'] = [];
+ this['Count'] = [];
+ this['Passed'] = [];
+ this['Failed'] = [];
+ this['Ignored'] = [];
+ this['Muted'] = [];
+ this['OO'] = [];
+ this['TT'] = [];
+ this['JC'] = [];
+ this['EC'] = [];
+ this['ET'] = [];
+ }
+ }
- if (stringMedian.indexOf(":") !== -1)
- return (parseInt(stringMedian.split(":")[0]) * 60 + parseInt(stringMedian.split(":")[1])) * 60;
- else
- return parseFloat(stringMedian);
+ class BuildStatistics {
+ constructor(item) {
+ this.buildId = item.buildId;
+ this.date = parseTime(item.startDate);
+ this.stat = {
+ 'Duration' : item.duration,
+ 'DurationF' : parseDuration(parseInt(item.duration)),
+ 'Count' : item.testOccurrences.count,
+ 'Passed' : item.testOccurrences.passed,
+ 'Failed' : item.testOccurrences.failed,
+ 'Ignored' : item.testOccurrences.ignored,
+ 'Muted' : item.testOccurrences.muted,
+ 'OO' : item.totalProblems.OO,
+ 'TT' : item.totalProblems.TT,
+ 'JC' : item.totalProblems.JC,
+ 'EC' : item.totalProblems.EC,
+ 'ET' : item.totalProblems.ET
+ };
+ this.isValid = item.isValid;
+ this.conditionLocal = false;
+ }
+ }
+
+ class Statistic {
+ constructor(array, name, num) {
+ let newArr = array.slice();
+ newArr = newArr.sort(function(a, b){ return a - b; });
+
+ let i = newArr.length / 2;
+
+ this.median = i % 1 === 0 ? (newArr[i - 1] + newArr[i]) / 2 : newArr[Math.floor(i)];
+ this.min = newArr[0];
+ this.max = newArr[newArr.length - 1];
+ this.name = name;
+ this.num = num;
+ }
}
var changeDisplay = function(id) {
@@ -397,91 +474,129 @@
window.open(res);
}
- function printStatistics(num, map, sinceDate, untilDate) {
+ function printStatistics(data) {
clearBackgroundFromAllDataCells();
- clearGraphs(num);
-
- const anotherNum = (num === 1) ? 2 : 1;
+ clearGraphs(data.num);
+ fillAllDataCells(data.num, "");
- let statistics = {},
- dates = [],
- buildIds = [];
+ if (data.isEmpty) {
+ printImportantMessage(data.num, "#ff0000", "No data for the selected period");
+ fillTitleForData(data.num, 'mmm', "");
- statistics[duration] = [];
-
- for (let i = 0; i < prOcc.length; i++) {
- statistics[prOcc[i]] = [];
- statistics[tOcc[i]] = [];
- }
-
- for (let j = 0; j < map.length; j++) {
- dates[j] = parseTime(map[j].startDate);
- buildIds[j] = map[j].buildId;
- statistics[duration][j] = map[j].duration;
-
- for (let i = 0; i < prOcc.length; i++) {
- statistics[prOcc[i]][j] = map[j].totalProblems[prOcc[i]];
- statistics[tOcc[i]][j] = map[j].testOccurrences[tOcc[i].toLowerCase()];
- }
- }
-
- if (dates.length === 0) {
- printImportantMessage(num, "#ff0000", "No data for the selected period");
- fillAllDataCells(num, "");
- $('.title' + num).html("");
+ if (data.hasInvalidBuilds && !invalidInclude)
+ printRunsCount(data);
+ else
+ fillTitleForData(data.num, 'total', "");
return;
} else {
- let firstDate = moment(parseTime(map[0].startDate)).format("DD-MM-YYYY");
- let lastDate = moment(parseTime(map[map.length - 1].startDate)).format("DD-MM-YYYY");
+ let firstDate = moment(data.realSinceDate).format("DD-MM-YYYY");
+ let lastDate = moment(data.realUntilDate).format("DD-MM-YYYY");
- if ((sinceDate.format("DD-MM-YYYY") !== firstDate) || (untilDate.format("DD-MM-YYYY") !== lastDate)) {
- printImportantMessage(num, "#ffb856", "Data for " +
+ if ((data.sinceDate.format("DD-MM-YYYY") !== firstDate) || (data.untilDate.format("DD-MM-YYYY") !== lastDate)) {
+ printImportantMessage(data.num, "#ffb856", "Data for " +
(firstDate === lastDate ? firstDate : ("the period from " + firstDate + " to " + lastDate)) + "");
- } else {
- $("#info" + num).html("");
- if ($('#info' + anotherNum).text() === ''){
- $('#showInfo').css('display', 'none')
- }
- }
+ } else
+ $("#info" + data.num).html("");
}
- $('.title.mmm' + num).html(mmmTitle);
- $('.mmm' + num).prop('title', mmmTitle);
- $('.title.t' + num).html(tTitle);
- $('.t' + num).prop('title', tTitle);
+ fillNameForMinMedianMaxColumn(mmmTitle);
+ fillNameForTotalColumn(tTitle);
- fillCellWithStatistics(duration, num, statistics, dates, buildIds);
+ fillTitleForData(data.num, 'mmm', mmmTitle);
+ fillTitleForData(data.num, 'total', tTitle);
- for (let i = 0; i < prOcc.length; i++) {
- fillCellWithStatistics(prOcc[i], num, statistics, dates, buildIds);
- fillCellWithStatistics(tOcc[i], num, statistics, dates, buildIds);
+ let fieldsStatistics = data.fieldsStatistics;
+
+ for (let fieldName of fieldNames.keys()) {
+ let stat = new Statistic(fieldsStatistics[fieldName], fieldName, data.num);
+
+ fillCellWithStatistics(stat);
+
+ drawGraph(data.builds, fieldName, stat.median, data.num);
}
- $('#RunsCount' + num).html(map.length);
+ printRunsCount(data);
}
- function fillCellWithStatistics(prefix, num, statistics, dates, buildIds) {
- let result = getMinMaxMedian(statistics[prefix]);
+ function printRunsCount(data){
+ $('#RunsCount' + data.num).html(data.builds.length + "<br>" + (invalidInclude || (!data.hasInvalidBuilds) ? "" :
+ "<span class='compare title'>(" + data.invalidBuildsCount +
+ " invalid builds hide)</span>"));
+ }
- if (prefix === duration)
- $('#' + prefix + num).html(time(result.min) + " - " + time(result.median) + " - " + time(result.max));
+ function fillCellWithStatistics(stat) {
+ if (stat.name === duration)
+ $('#' + stat.name + stat.num).html(time(stat.min) + " - " + time(stat.median) + " - " + time(stat.max));
else
- $('#' + prefix + num).html(result.min + " - " + result.median + " - " + result.max);
-
- compareAndHighlight(prefix, num, result.median);
+ $('#' + stat.name + stat.num).html(stat.min + " - " + stat.median + " - " + stat.max);
- drawGraph(prefix, num, dates, statistics[prefix], buildIds);
+ compareAndHighlight(stat);
function time(ms){
return moment(ms * 1000).utcOffset(0).format("H:mm");
}
}
- function compareAndHighlight(prefix, thisNum, thisMedian){
- let anotherNum = (thisNum === 1) ? 2 : 1,
- thisElement = $('#' + prefix + thisNum),
- anotherElement = $('#' + prefix + anotherNum);
+ function fillNameForTotalColumn(num, title) {
+ $('.title.total.' + num).html(title);
+ }
+
+ function fillNameForMinMedianMaxColumn(num, title) {
+ $('.title.mmm .' + num).html(title);
+ }
+
+ function fillTitleForData(num, cssClass, title) {
+ $('.' + cssClass + '.data.' + num).prop('title', title);
+ }
+
+ function loadData(num, sinceDate, untilDate, testsTrigger) {
+ loadGif(num);
+
+ 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) {
+ data[num] = new Data(num, result.buildsStatistics, sinceDate, untilDate);
+
+ printStatistics(data[num]);
+
+ try {
+ mergedTestsResults[num] = JSON.parse(result.mergedTestsJson);
+ } catch (e) {
+ printImportantMessage(num, "#ff0000", "Invalid server response. Unable to parse JSON");
+ }
+
+ printTests(generateCompareTestsResults(mergedTestsResults));
+ },
+ error: showErrInLoadStatus,
+ timeout: 1800000
+ }
+ );
+ }
+
+ function parseMedian(string) {
+ let stringMedian = string.substring(string.indexOf("-") + 2, string.lastIndexOf("-") - 1);
+
+ if (stringMedian.indexOf(":") !== -1)
+ return (parseInt(stringMedian.split(":")[0]) * 60 + parseInt(stringMedian.split(":")[1])) * 60;
+ else
+ return parseFloat(stringMedian);
+ }
+
+ function compareAndHighlight(stat){
+
+ let anotherNum = (stat.num === 1) ? 2 : 1,
+ thisElement = $('#' + stat.name + stat.num),
+ anotherElement = $('#' + stat.name + anotherNum),
+ thisMedian = stat.median;
if (thisElement.data('allowHighlight').toString() === "true") {
let anotherMedian = parseMedian(anotherElement.text());
@@ -502,44 +617,26 @@
loadData(2, moment(twoWeekAgo), moment(oneWeekAgo), testsTrigger);
$.ajax({ url: "rest/branches/version", success: showVersionInfo, error: showErrInLoadStatus });
-
- if(g_updTimer == null) {
- g_updTimer = setTimeout(tstTimeout, 3200);
- }
- setInterval(tstTimeout, 10000);
});
- function tstTimeout() {
- if (g_updTimer != null) {
- clearTimeout(g_updTimer);
- g_updTimer = null;
- }
-
- if (g_updTimer == null) {
- g_updTimer = setTimeout(tstTimeout, 3200);
- }
- }
-
function clearBackgroundFromAllDataCells(num){
- $('.data' + (num == null ? '1, .data2' : num)).css('background', '');
+ $('.data.' + (num == null ? '1, .data.2' : num)).css('background', '');
}
-
+
function fillAllDataCells(num, message) {
- $('.data' + num).html(message);
+ $(".data." + num).html(message);
$('.testsCntCell').html(message);
}
function clearGraphs(num) {
- $("#graph" + duration + num).empty();
- for (let i = 0; i < prOcc.length; i++) {
- $("#graph" + prOcc[i] + num).empty();
- $("#graph" + tOcc[i] + num).empty();
- }
+ $(".graph." + num).empty();
}
function loadGif(num) {
clearBackgroundFromAllDataCells();
+
clearGraphs(num);
+
fillAllDataCells(num, "<img src='/img/loading.gif' width=15px height=15px>");
}
@@ -549,102 +646,51 @@
$('#showInfo').css('display', '');
}
- function loadData(num, sinceDate, untilDate, testsTrigger) {
- loadGif(num);
-
- 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.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,
- timeout: 1800000
- }
- );
- }
-
$(function() {
- $('input[name="daterange1"]').daterangepicker(
- dateRangePickerParam(oneWeekAgo, new Date()), function (start, end, label) {
- dateIntervals[1].start = start;
-
- dateIntervals[1].end = end;
+ for (let i = 1; i <= 2; i++) {
+ $('input[name="daterange' + i + '"]').daterangepicker(
+ dateRangePickerParam(getDateFewWeeksAgo(i), getDateFewWeeksAgo(i - 1), i), function (start, end) {
+ dateIntervals[i].start = start;
- loadData(1, start, end, testsTrigger);
- });
- });
-
- $(function() {
- $('input[name="daterange2"]').daterangepicker(
- dateRangePickerParam(twoWeekAgo, oneWeekAgo), function (start, end, label) {
- dateIntervals[2].start = start;
+ dateIntervals[i].end = end;
- dateIntervals[2].end = end;
-
- loadData(2, start, end, testsTrigger);
- });
+ loadData(i, start, end, testsTrigger);
+ });
+ }
});
- graphSpoiler(duration);
+ for (let fieldName of fieldNames.keys())
+ graphSpoiler(fieldName);
- for (let i = 0; i < prOcc.length; i++) {
- graphSpoiler(prOcc[i]);
- graphSpoiler(tOcc[i]);
- }
-
- function graphSpoiler(prefix) {
- $("#clickGraph" + prefix).click(function() {
- let element = $('#showGraph' + prefix);
+ function graphSpoiler(fieldName) {
+ $("#clickGraph" + fieldName).click(function() {
+ let element = $('#showGraph' + fieldName);
element.css('display') === 'none' ? element.css('display', '') : element.css('display', 'none');
});
}
- function drawGraph(prefix, num, dates, counts, buildIds) {
- let data = [],
- isDuration = prefix === duration;
-
- for (let i = 0; i < dates.length; i++) {
- data[i] = {};
- data[i].date = dates[i];
- data[i].value = (isDuration ? parseDuration(parseInt(counts[i])) : counts[i]);
- data[i].buildId = buildIds[i];
- }
-
- let svg = d3.select("#graph" + prefix + num).append("svg:svg"),
+ function drawGraph(builds, fieldName, median, num) {
+ let isDuration = (fieldName === duration),
+ F = isDuration ? "F" : "",
+ svg = d3.select("#graph" + fieldName + num).append("svg:svg"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 500 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
- let div = d3.select("body").append("div")
- .attr("class", "tooltip")
- .style("opacity", 0);
+ let buildDate = d3.select("body").append("div").attr("class", "tooltip buildDate").style("opacity", 0),
+ condition = d3.select("body").append("div").attr("class", "tooltip condition").style("opacity", 0),
+ mark = d3.select("body").append("div").attr("class", "tooltip mark").style("opacity", 0);
let y = (isDuration ? d3.scaleTime() : d3.scaleLinear()).range([height, 0]),
x = d3.scaleTime().rangeRound([0, width]);
let line = d3.line()
.x(function(d) { return x(d.date); })
- .y(function(d) { return y(d.value); });
+ .y(function(d) { return y(d.stat[fieldName + F]); });
- x.domain(d3.extent(data, function(d) { return d.date; }));
- y.domain(d3.extent(data, function(d) { return d.value; }));
+ x.domain(d3.extent(builds, function(d) { return d.date; }));
+ y.domain(d3.extent(builds, function(d) { return d.stat[fieldName + F]; }));
g.append("svg:g").attr("transform", "translate(0," + (height) + ")")
.call(d3.axisBottom(x).tickFormat(multiFormat));
@@ -657,10 +703,22 @@
.attr("x2",400)
.attr("y1",200)
.attr("y2",200)
- .style("display", "None");
+ .attr("stroke", "#4682B4")
+ .attr("stroke-width", "1")
+ .style("display", "none");
+
+ let median_line = g.append("line")
+ .attr("class", "line")
+ .attr("x1",0)
+ .attr("x2",width)
+ .attr("y1",y(isDuration ? parseDuration(parseInt(median)) : median))
+ .attr("y2",y(isDuration ? parseDuration(parseInt(median)) : median))
+ .attr("stroke", "red")
+ .attr("stroke-width", "0.2")
+ .style("display", "block");
g.append("path")
- .datum(data)
+ .datum(builds)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
@@ -668,46 +726,186 @@
.attr("stroke-width", 1.5)
.attr("d", line);
- g.selectAll("dot").data(data)
+ g.selectAll("dot").data(builds)
.enter()
.append("circle")
.attr("r", 3)
.attr("cx", function(d){return x(d.date); })
- .attr("cy", function(d){return y(d.value); })
- .attr("class", "dot")
+ .attr("cy", function(d){return y(d.stat[fieldName + F]); })
+ .attr("class", function(d) {
+ return getDotClass(d);
+ })
.on("mouseover", function(d) {
d3.select(this).transition().duration(100)
- .style("fill", "green")
+ .style("fill", "#12AD5E")
.attr("r", 5)
.attr("onclick", "checkAvailable(" + d.buildId + "," + d.date.getTime() + ");" +
- "return false;");
- div.transition()
- .duration(200)
- .style("opacity", .8);
+ "return false;");
- let graphTd = document.getElementById("graph" + prefix + num).getBoundingClientRect(),
- scrollTop = window.pageYOffset || document.documentElement.scrollTop;
-
- div .html(formatTime(d.date))
- .style("left", graphTd.left + x(d.date) + (x(d.date) > (500 - 150) ? -150 : 0) + "px")
- .style("top", scrollTop + graphTd.top - 10 + "px" );
svg_aline.transition().duration(10)
.style("display", "block")
.attr("x1", x(d.date))
- .attr("y1", y(d.value))
+ .attr("y1", y(d.stat[fieldName + F]))
.attr("x2", x(d.date))
- .attr("y2", height)
+ .attr("y2", height);
+
+ let graphTd = document.getElementById("graph" + fieldName + num).getBoundingClientRect(),
+ scrollTop = window.pageYOffset || document.documentElement.scrollTop,
+ top = scrollTop + graphTd.top + y(d.stat[fieldName + F]) - 10,
+ partLeft = graphTd.left + x(d.date),
+ buildDateNearTheBorder = x(d.date) > (440 - 140);
+
+ buildDate.html(formatTime(d.date))
+ .style("left", partLeft + (buildDateNearTheBorder ? -99 : 49) + "px")
+ .style("top", top + "px" ).transition()
+ .duration(200).style("opacity", .8).style("display", "block");
+
+ mark.html('<i class="fas fa-' + (markBuildId === d.buildId ? 'eraser' : 'highlighter') + '"></i>')
+ .style("left", partLeft + (buildDateNearTheBorder ? 49 : 23) + "px")
+ .style("top", top + "px" )
+ .attr("onclick", "markBuild(" + d.buildId + "); return false;").transition()
+ .duration(200).style("opacity", .8).style("display", "block");
+
+ condition.html('<i class="' + (!d.isValid ? 'fas fa-undo' : 'far fa-trash')
+ + '-alt"></i>')
+ .style("left", partLeft + (buildDateNearTheBorder ? 75 : -3) + "px")
+ .style("top", top + "px" )
+ .attr("onclick", "setCondition(" + num + ", " + d.buildId + ", " +
+ (isDuration ? d.stat[fieldName + F].getTime() / 1000 : d.stat[fieldName + F]) +
+ ", " + median + ", '" + (fieldName + F) + "'); return false;").transition()
+ .duration(200).style("opacity", .8).style("display", "block");
})
- .on("mouseout", function(d) {
+ .on("mouseout", function() {
d3.select(this).transition().duration(100)
- .style("fill", "grey")
+ .style("fill", null)
.attr("r", 3);
- div.transition()
- .duration(500)
- .style("opacity", 0);
- svg_aline.style("display","None")
+
+ buildDate.transition().duration(2000)
+ .style("opacity", 0).on("end", function() { buildDate.style("display", "none"); });
+
+ condition.transition().duration(2000)
+ .style("opacity", 0).on("end", function() { condition.style("display", "none"); });
+
+ mark.transition().duration(2000)
+ .style("opacity", 0).on("end", function() { mark.style("display", "none"); });
+
+ svg_aline.style("display","none")
});
+
+ function getDotClass(build) {
+ if (markBuildId === build.buildId)
+ return "mark-dot";
+ else
+ return ((invalidInclude && !build.isValid) ? "hidden-dot" : "dot");
+ }
+ }
+
+ function setCondition(num, buildId, value, median, prefix) {
+ if (!isDefinedAndFilled(buildId) || buildId === "") {
+ showDialog("<i class='fas fa-exclamation-circle'></i><br><br>BuildId must be defined and filled!");
+
+ return;
+ }
+
+ let isValid = !data[num].map.get(buildId).isValid;
+ let conditionLocal = data[num].map.get(buildId).conditionLocal;
+ let modalDialog = $("#modalDialog");
+ let diff = ((value / median - 1) * 100 | 0);
+ let message = "<p style='text-align:center;opacity: 0.7;'><img src='img/tc.svg' width='100' height='100'></p>" +
+ "<br><p style='text-align:justify'><b>Build [" + buildId + "]</b> will be marked as «" +
+ (isValid ? "" : "in") + "valid». Value differs by <b>" + diff + "%</b> from the median.</p>" +
+ "<br><b>Field:</b> <select id='selectField' style='width: 200px'>";
+
+ for (let fieldName of fieldNames.keys())
+ message += getHtmlOption(fieldName, false, fieldName === prefix, fieldNames.get(fieldName));
+
+ message += "</select><br><br><b>Mark: </b><select id='selectExclusionArea' style='width: 200px'>" +
+ getHtmlOption(false, isValid && !conditionLocal, isValid && conditionLocal, "For me") +
+ getHtmlOption(true, isValid && conditionLocal, isValid && !conditionLocal, "For all") + "</select>";
+
+ message += "<br><br><span style='color:grey;font-size:smaller;text-align:justify'><hr>If you select «" +
+ "Mark for me», build will be hidden from the results only for this dates interval until the page " +
+ "is updated.<br><hr>If you select «Mark for all», your request will be hidden " +
+ "from the results for all users, and will not be taken into account when analyzing tests.</span>";
+
+ modalDialog.html(message);
+ modalDialog.dialog({
+ modal: true,
+ buttons: {
+ "Confirm": function () {
+ $(this).dialog("close");
+
+ let forAll = $('#selectExclusionArea').val() === 'true';
+ let text = "";
+
+ if (forAll) {
+ $.ajax({
+ url: 'rest/build/condition',
+ data: {
+ "buildId": buildId,
+ "isValid": isValid,
+ "field": $('#selectField').val()
+ },
+ success: function (res) {
+ if (isDefinedAndFilled(res)) {
+ text = "<i class='fas fa-" + (res ? "check" : "exclamation") + "-circle'></i>" +
+ "<br><br>";
+
+ if (isValid) {
+ text += res ? "Build <b>remove</b> from invalid list!" :
+ "Invalid list <b>doesn't contain</b> build!";
+ }
+ else {
+ text += "Build " + (res ? "<b>add</b> in" :
+ "<b>is already</b> on the") + " invalid list!";
+ }
+ } else {
+ text += "<i class='fas fa-exclamation-circle'><br><br></i>" +
+ "BuildId or condition is null!";
+ }
+
+ setBuildConditionOnFrontAndShowDialog(buildId, isValid, text, false);
+ },
+ error: showErrInLoadStatus
+ });
+ } else {
+ text = "<i class='fas fa-check-circle'></i><br><br>Build <b>" + (isValid ? "remove</b> from" :
+ "add</b> in") +" <i>temporary</i> invalid list!";
+
+ setBuildConditionOnFrontAndShowDialog(buildId, isValid, text, !isValid);
+ }
+ },
+ "Cancel": function () {
+ $(this).dialog("close");
+ }
+ }
+ });
+
+ function getHtmlOption(value, disabled, selected, text){
+ return "<option value='" + value + "' " + (disabled ? "disabled" : "") + (selected ? "selected" : "") +
+ ">" + text + "</option>";
+ }
+ }
+
+ function setBuildConditionOnFrontAndShowDialog(buildId, isValid, text, conditionLocal) {
+ setBuildCondition(buildId, isValid, conditionLocal);
+
+ showDialog(text);
+ }
+
+ function showDialog(text) {
+ let resultDialog = $("#resultDialog");
+ resultDialog.html("<p style='text-align:center'><br>" + text + "</p>");
+
+ resultDialog.dialog({
+ modal: true,
+ buttons: {
+ "Ok": function () {
+ $(this).dialog("close");
+ }
+ }
+ });
}
function getBuildLink(buildId) { return "https://ci.ignite.apache.org/viewLog.html?buildId=" + buildId
@@ -755,7 +953,84 @@
modal.hide();
})
+
+ $('.switch-btn.builds').click(function(){
+ $(this).toggleClass('switch-on');
+
+ invalidInclude = $(this).hasClass('switch-on');
+
+ updateAll();
+ });
+
+ $('.switch-btn.tests').click(function(){
+ $(this).toggleClass('switch-on');
+
+ toggleTests();
+ });
+
+ function setBuildCondition(buildId, isValid, conditionLocal) {
+ setBuildConditionForColumn(buildId, isValid, 1, conditionLocal);
+ setBuildConditionForColumn(buildId, isValid, 2, conditionLocal);
+ }
+
+ function setBuildConditionForColumn(buildId, isValid, num, conditionLocal) {
+ if (isDefinedAndFilled(data[num])) {
+ if (data[num].map.has(buildId)) {
+ data[num].map.get(buildId).isValid = isValid;
+ data[num].map.get(buildId).conditionLocal = conditionLocal;
+ }
+
+ updateColumn(num);
+ }
+ }
+
+ function markBuild(buildId) {
+ markBuildId = (markBuildId === buildId) ? 0 : buildId;
+
+ updateAll();
+ }
+
+ function updateColumn(num) {
+ if (isDefinedAndFilled(data[num]))
+ printStatistics(data[num]);
+ }
+
+ function updateAll() {
+ updateColumn(1);
+ updateColumn(2);
+
+ if (isDefinedAndFilled(mergedTestsResults))
+ printTests(generateCompareTestsResults(mergedTestsResults));
+ }
+
+ function multiFormat(date) {
+ return (d3.timeSecond(date) < date ? formatMillisecond
+ : d3.timeMinute(date) < date ? formatSecond
+ : d3.timeHour(date) < date ? formatMinute
+ : d3.timeDay(date) < date ? formatHour
+ : d3.timeMonth(date) < date ? (d3.timeWeek(date) < date ? formatDay : formatWeek)
+ : d3.timeYear(date) < date ? formatMonth
+ : formatYear)(date);
+ }
+
+ function dateRangePickerParam(startDate, endDate, num) {
+ return {
+ "opens" : (num === 1 ? 'right' : 'left'),
+ "maxSpan": { "days": 7 },
+ "locale": {
+ "format": "DD/MM/YYYY", "separator": " - ", "applyLabel": "Apply", "cancelLabel": "Cancel",
+ "fromLabel": "From", "toLabel": "To", "customRangeLabel": "Custom", "weekLabel": "W",
+ "daysOfWeek": [ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" ],
+ "monthNames": [ "January", "February", "March", "April", "May", "June", "July",
+ "August", "September", "October", "November", "December" ],
+ "firstDay": 1
+ },
+ "startDate": moment(startDate).format("DD-MM-YYYY"), "endDate": moment(endDate).format("DD-MM-YYYY")
+ }
+ }
</script>
-<div style="visibility:hidden"><div id="modalDialog" title="Information"></div></div>
+<div style="visibility:hidden">
+ <div id="modalDialog" title="Information"></div><div id="resultDialog" title="Result"></div>
+</div>
</body>
</html>
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 c5aa999..309328c 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
@@ -219,16 +219,16 @@ form li:after
padding: 10px 5px 10px 5px;
}
-.data1, .data2{
+.data {
text-align: center;
vertical-align: middle;
}
-.section{
+.section {
font-weight: bold;
}
-.compare .field{
+.compare .field {
padding-left: 15px;
}
@@ -306,6 +306,7 @@ tr.shown td.details-control {
//url('../resources/details_close.png') no-repeat center center;
}
+
rect {
fill: red;
stroke: black;
@@ -314,18 +315,26 @@ rect {
.dot {
opacity: 0.5;
- fill: grey;
+ fill: #4682B4;
+}
+
+.hidden-dot {
+ opacity: 0.5;
+ fill: #E60000;
+}
+
+.mark-dot {
+ opacity: 0.5;
+ fill: #FF9800;
}
line {
fill: none;
- stroke: green;
- stroke-width: 1;
shape-rendering: crispEdges;
+ pointer-events: none;
}
-.axis path,
-.axis line {
+.axis path {
fill: none;
stroke: grey;
stroke-width: 1;
@@ -335,68 +344,71 @@ line {
div.tooltip {
position: absolute;
text-align: center;
- width: 150px;
- height: 15px;
padding: 2px;
- margin-left: 50px;
- margin-top: 5px;
+ border-radius: 8px;
font: 14px sans-serif;
- background: #12AD5E;
border: 1px;
- border-radius: 8px;
- pointer-events: none;
color: white;
+ height: 15px;
}
-.ui-dialog button{
- height: auto;
+.buildDate {
+ width: 140px;
+ background: #12AD5E;
}
-.switch {
- position: relative;
- display: inline-block;
- width: 60px;
- height: 34px;
+.condition {
+ width: 18px;
+ background: #E60000;
}
-.switch input {display:none;}
+.mark {
+ width: 18px;
+ background: #FF9800;
+}
-.slider {
- position: absolute;
+.switch-btn {
+ display: inline-block;
+ width: 23px;
+ height: 14px;
+ border-radius: 9px;
+ background: #bfbfbf;
+ z-index: 0;
+ margin: 0;
+ padding: 0;
+ border: none;
cursor: pointer;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: #ccc;
- -webkit-transition: .4s;
- transition: .4s;
+ position: relative;
+ transition-duration: 300ms;
}
-.slider:before {
- position: absolute;
+.switch-btn::after {
content: "";
- height: 26px;
- width: 26px;
- left: 4px;
- bottom: 4px;
- background-color: white;
- -webkit-transition: .4s;
- transition: .4s;
+ height: 12px;
+ width: 12px;
+ border-radius: 7px;
+ background: #fff;
+ top: 1px;
+ left: 1px;
+ transition-duration: 300ms;
+ position: absolute;
+ z-index: 1;
}
-input:checked + .slider {
- background-color: #12AD5E;
+.switch-on {
+ background: #12AD5E;
}
-input:focus + .slider {
- box-shadow: 0 0 1px #12AD5E;
+.switch-on::after {
+ left: 10px;
}
-input:checked + .slider:before {
- -webkit-transform: translateX(26px);
- -ms-transform: translateX(26px);
- transform: translateX(26px);
+.icon {
+ text-align: center;
+}
+
+.ui-dialog button{
+ height: auto;
}
.modal {
@@ -434,3 +446,12 @@ input:checked + .slider:before {
cursor: pointer;
}
+.fas.fa-check-circle {
+ font-size: 48px;
+ color: #12AD5E;
+}
+
+.fas.fa-exclamation-circle {
+ font-size: 48px;
+ color: #E60000;
+}