blob: d7651306e693f274d28e4ee5123271232a86c99d [file] [log] [blame]
/*
* 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.issue;
import com.google.common.base.MoreObjects;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.Map;
import java.util.TreeSet;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.ignite.tcbot.engine.issue.IssueType;
import org.apache.ignite.tcbot.persistence.Persisted;
import org.apache.ignite.tcbot.common.util.TimeUtil;
/**
* Issue used both for saving into DB and in UI (in issue history).
* Issue is any detected failure of test or suite.
*/
@SuppressWarnings({"WeakerAccess", "PublicField"})
@Persisted
public class Issue {
/** Type code. Null of older versions of issue */
@Nullable
public String type;
/** Display type. for issue. Kept for backward compatibilty with older records without type code. */
private String displayType;
public double flakyRate;
@Nullable
public String trackedBranchName;
public IssueKey issueKey;
public List<ChangeUi> changes = new ArrayList<>();
public Set<String> addressNotified = new TreeSet<>();
@Nullable
public String webUrl;
@Nullable
public String displayName;
/** Build start timestamp. Builds which is older that 10 days not notified. */
@Nullable public Long buildStartTs;
/** Detected timestamp. */
@Nullable public Long detectedTs;
/** Set of build tags detected. */
public Set<String> buildTags = new TreeSet<>();
/** Statistics of subscribers for this issue. Filled accordignly recent update. */
public Map<String, Object> stat = new HashMap<>();
/** Notification failed: Map from address to exception text */
@Nullable public Map<String, String> notificationFailed = new HashMap<>();
public int notificationRetry = 0;
public Issue(IssueKey issueKey, IssueType type,
@Nullable Long buildStartTs) {
this.issueKey = issueKey;
this.detectedTs = System.currentTimeMillis();
this.type = type.code();
this.displayType = type.displayName();
this.buildStartTs = buildStartTs;
}
public void addChange(String username, String webUrl) {
changes.add(new ChangeUi(username, webUrl));
}
public String toHtml(boolean includeChangesInfo) {
StringBuilder sb = new StringBuilder();
sb.append(displayType);
if (trackedBranchName != null)
sb.append(" in ").append(trackedBranchName);
sb.append(" ");
if (webUrl != null)
sb.append("<a href='").append(webUrl).append("'>").append(getDisplayName()).append("</a>");
else
sb.append(getDisplayName());
if(includeChangesInfo) {
if (changes.isEmpty())
sb.append(" No changes in the build");
else {
sb.append(" Changes may lead to failure were done by ");
for (Iterator<ChangeUi> iter = changes.iterator(); iter.hasNext(); ) {
ChangeUi next = iter.next();
sb.append(next.toHtml());
if (iter.hasNext())
sb.append(", ");
}
}
}
return sb.toString();
}
public IssueKey issueKey() {
return issueKey;
}
public String toSlackMarkup(boolean includeChangesInfo) {
StringBuilder sb = new StringBuilder();
sb.append(displayType);
if (trackedBranchName != null)
sb.append(" in ").append(trackedBranchName);
sb.append(" ");
String name = getDisplayName();
if (webUrl != null)
sb.append("<").append(webUrl).append("|").append(name).append(">");
else
sb.append(name);
if(includeChangesInfo) {
if (changes.isEmpty())
sb.append("\n No changes in the build");
else {
sb.append("\n Changes may led to failure were done by ");
for (Iterator<ChangeUi> iter = changes.iterator(); iter.hasNext(); ) {
ChangeUi next = iter.next();
sb.append(next.toSlackMarkup());
if (iter.hasNext())
sb.append(", ");
}
}
}
return sb.toString();
}
public String getDisplayName() {
if (displayName == null)
return issueKey.getTestOrBuildName();
return displayName;
}
/**
* @return Set of build tags detected.
*/
@Nonnull public Set<String> buildTags() {
return buildTags == null && buildTags.isEmpty()
? Collections.emptySet()
: Collections.unmodifiableSet(buildTags);
}
/** {@inheritDoc} */
@Override public String toString() {
String tsStart = buildStartTs == null ? null : TimeUtil.timestampToDateTimePrintable(buildStartTs);
String tsDetect = detectedTs == null ? null : TimeUtil.timestampToDateTimePrintable(detectedTs);
return MoreObjects.toStringHelper(this)
.add("type", type)
.add("displayType", displayType)
.add("trackedBranchName", trackedBranchName)
.add("issueKey", issueKey)
.add("changes", changes)
.add("addressNotified", addressNotified)
.add("webUrl", webUrl)
.add("displayName", displayName)
.add("buildStartTs", tsStart)
.add("detectedTs", tsDetect)
.toString();
}
public String toPlainText(boolean includeChangesInfo) {
StringBuilder sb = new StringBuilder();
sb.append(displayType);
if (trackedBranchName != null)
sb.append(" in ").append(trackedBranchName);
sb.append(" ");
if (webUrl != null)
sb.append("").append(getDisplayName()).append(" ").append(webUrl).append("");
else
sb.append(getDisplayName());
sb.append("\n");
if(includeChangesInfo) {
if (changes.isEmpty())
sb.append(" No changes in the build");
else {
sb.append(" Changes may lead to failure were done by \n");
for (Iterator<ChangeUi> iter = changes.iterator(); iter.hasNext(); ) {
ChangeUi next = iter.next();
sb.append("\t - ");
sb.append(next.toPlainText());
if (iter.hasNext())
sb.append("\n");
}
}
}
return sb.toString();
}
public String type() {
return type;
}
/** {@inheritDoc} */
@Override public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Issue issue = (Issue)o;
return notificationRetry == issue.notificationRetry &&
Objects.equals(type, issue.type) &&
Objects.equals(displayType, issue.displayType) &&
Objects.equals(trackedBranchName, issue.trackedBranchName) &&
Objects.equals(issueKey, issue.issueKey) &&
Objects.equals(changes, issue.changes) &&
Objects.equals(addressNotified, issue.addressNotified) &&
Objects.equals(webUrl, issue.webUrl) &&
Objects.equals(displayName, issue.displayName) &&
Objects.equals(buildStartTs, issue.buildStartTs) &&
Objects.equals(detectedTs, issue.detectedTs) &&
Objects.equals(buildTags, issue.buildTags) &&
Objects.equals(stat, issue.stat) &&
Objects.equals(notificationFailed, issue.notificationFailed);
}
/** {@inheritDoc} */
@Override public int hashCode() {
return Objects.hash(type, displayType, trackedBranchName, issueKey, changes, addressNotified, webUrl, displayName, buildStartTs, detectedTs, buildTags, stat, notificationFailed, notificationRetry);
}
}