Build subscription

This commit is contained in:
Robin Shen 2019-10-11 16:10:35 +08:00
parent 5f2a4590c5
commit 1e44c20eb3
11 changed files with 154 additions and 14 deletions

View File

@ -169,6 +169,7 @@ import io.onedev.server.migration.PersistentBagConverter;
import io.onedev.server.model.support.administration.authenticator.Authenticator;
import io.onedev.server.model.support.administration.jobexecutor.AutoDiscoveredJobExecutor;
import io.onedev.server.model.support.administration.jobexecutor.JobExecutor;
import io.onedev.server.notification.BuildNotificationManager;
import io.onedev.server.notification.CodeCommentNotificationManager;
import io.onedev.server.notification.CommitNotificationManager;
import io.onedev.server.notification.DefaultMailManager;
@ -340,6 +341,7 @@ public class CoreModule extends AbstractPluginModule {
bind(WorkExecutor.class).to(DefaultWorkExecutor.class);
bind(PullRequestNotificationManager.class);
bind(CommitNotificationManager.class);
bind(BuildNotificationManager.class);
bind(IssueNotificationManager.class);
bind(EntityReferenceManager.class);
bind(CodeCommentNotificationManager.class);

View File

@ -57,8 +57,6 @@ import io.onedev.server.ci.job.log.LogManager;
import io.onedev.server.ci.job.param.JobParam;
import io.onedev.server.ci.job.paramspec.ParamSpec;
import io.onedev.server.ci.job.paramspec.SecretParam;
import io.onedev.server.ci.job.retry.JobRetry;
import io.onedev.server.ci.job.retry.RetryCondition;
import io.onedev.server.ci.job.trigger.JobTrigger;
import io.onedev.server.entitymanager.BuildManager;
import io.onedev.server.entitymanager.BuildParamManager;
@ -554,6 +552,7 @@ public class DefaultJobManager implements JobManager, Runnable, CodePullAuthoriz
build.setRunningDate(null);
build.setSubmitDate(new Date());
build.setSubmitter(submitter);
build.setRetried(0);
buildParamManager.deleteParams(build);
for (Map.Entry<String, List<String>> entry: paramMap.entrySet()) {
ParamSpec paramSpec = build.getJob().getParamSpecMap().get(entry.getKey());
@ -724,6 +723,7 @@ public class DefaultJobManager implements JobManager, Runnable, CodePullAuthoriz
@Listen
public void on(BuildSubmitted event) {
Build build = event.getBuild();
build.setWillRetry(false);
FileUtils.deleteDir(build.getPublishDir());
}
@ -731,21 +731,17 @@ public class DefaultJobManager implements JobManager, Runnable, CodePullAuthoriz
@Listen
public void on(BuildFinished event) {
Build build = event.getBuild();
JobRetry retry = build.getJob().getRetry();
build.setWillRetry(build.getStatus() == Build.Status.FAILED
&& retry != null
&& build.getRetried() < retry.getMaxRetries()
&& RetryCondition.parse(retry.getRetryCondition()).satisfied(build));
if (build.isWillRetry()) {
build.setWillRetry(build.willRetryNow());
if (build.willRetryNow()) {
Long buildId = build.getId();
int retried = build.getRetried();
int retryDelay = build.getJob().getRetry().getRetryDelay();
transactionManager.runAsyncAfterCommit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(retry.getRetryDelay() * (long)(Math.pow(2, retried)) * 1000L);
Thread.sleep(retryDelay * (long)(Math.pow(2, retried)) * 1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
@ -762,6 +758,8 @@ public class DefaultJobManager implements JobManager, Runnable, CodePullAuthoriz
build.setPendingDate(null);
build.setRunningDate(null);
build.setSubmitDate(new Date());
build.setCanceller(null);
build.setCancellerName(null);
buildManager.save(build);
listenerRegistry.post(new BuildSubmitted(build));
}

View File

@ -4,6 +4,7 @@ import javax.annotation.Nullable;
import org.eclipse.jgit.lib.ObjectId;
import io.onedev.server.model.Build;
import io.onedev.server.model.CodeComment;
import io.onedev.server.model.CodeCommentReply;
import io.onedev.server.model.Issue;
@ -28,6 +29,8 @@ public interface UrlManager {
String urlFor(Issue issue);
String urlFor(Build build);
String urlFor(IssueComment comment);
String urlFor(IssueChange change);

View File

@ -1,5 +1,7 @@
package io.onedev.server.entitymanager.impl;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -35,7 +37,11 @@ public class DefaultBuildQuerySettingManager extends AbstractEntityManager<Build
@Transactional
@Override
public void save(BuildQuerySetting setting) {
if (setting.getUserQueries().isEmpty()) {
setting.getQuerySubscriptionSupport().getUserQuerySubscriptions().retainAll(
setting.getUserQueries().stream().map(it->it.getName()).collect(Collectors.toSet()));
setting.getQuerySubscriptionSupport().getProjectQuerySubscriptions().retainAll(
setting.getProject().getSavedBuildQueries().stream().map(it->it.getName()).collect(Collectors.toSet()));
if (setting.getQuerySubscriptionSupport().getProjectQuerySubscriptions().isEmpty() && setting.getUserQueries().isEmpty()) {
if (!setting.isNew())
delete(setting);
} else {

View File

@ -57,6 +57,8 @@ import io.onedev.server.ci.job.VariableInterpolator;
import io.onedev.server.ci.job.param.JobParam;
import io.onedev.server.ci.job.paramspec.ParamSpec;
import io.onedev.server.ci.job.paramspec.SecretParam;
import io.onedev.server.ci.job.retry.JobRetry;
import io.onedev.server.ci.job.retry.RetryCondition;
import io.onedev.server.git.GitUtils;
import io.onedev.server.git.RefInfo;
import io.onedev.server.model.support.inputspec.SecretInput;
@ -153,6 +155,8 @@ public class Build extends AbstractEntity implements Referenceable {
private boolean willRetry;
private transient Boolean willRetryNow;
@Column(length=MAX_STATUS_MESSAGE_LEN)
private String statusMessage;
@ -326,6 +330,17 @@ public class Build extends AbstractEntity implements Referenceable {
public void setWillRetry(boolean willRetry) {
this.willRetry = willRetry;
}
public boolean willRetryNow() {
if (willRetryNow == null) {
JobRetry retry = getJob().getRetry();
willRetryNow = getStatus() == Build.Status.FAILED
&& retry != null
&& getRetried() < retry.getMaxRetries()
&& RetryCondition.parse(retry.getRetryCondition()).satisfied(this);
}
return willRetryNow;
}
public Collection<BuildParam> getParams() {
return params;

View File

@ -1,6 +1,7 @@
package io.onedev.server.model;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import javax.persistence.Column;
import javax.persistence.Entity;
@ -38,6 +39,14 @@ public class BuildQuerySetting extends QuerySetting<NamedBuildQuery> {
@Column(nullable=false, length=65535)
private ArrayList<NamedBuildQuery> userQueries = new ArrayList<>();
@Lob
@Column(nullable=false, length=65535)
private LinkedHashSet<String> userQuerySubscriptions = new LinkedHashSet<>();
@Lob
@Column(nullable=false, length=65535)
private LinkedHashSet<String> projectQuerySubscriptions = new LinkedHashSet<>();
@Override
public Project getProject() {
return project;
@ -73,7 +82,19 @@ public class BuildQuerySetting extends QuerySetting<NamedBuildQuery> {
@Override
public QuerySubscriptionSupport<NamedBuildQuery> getQuerySubscriptionSupport() {
return null;
return new QuerySubscriptionSupport<NamedBuildQuery>() {
@Override
public LinkedHashSet<String> getUserQuerySubscriptions() {
return userQuerySubscriptions;
}
@Override
public LinkedHashSet<String> getProjectQuerySubscriptions() {
return projectQuerySubscriptions;
}
};
}
}

View File

@ -298,6 +298,7 @@ public class Project extends AbstractEntity {
savedBuildQueries.add(new NamedBuildQuery("All", "all"));
savedBuildQueries.add(new NamedBuildQuery("Successful", "successful"));
savedBuildQueries.add(new NamedBuildQuery("Failed", "failed"));
savedBuildQueries.add(new NamedBuildQuery("Failed eventually", "failed and not(will retry)"));
savedBuildQueries.add(new NamedBuildQuery("In error", "in error"));
savedBuildQueries.add(new NamedBuildQuery("Cancelled", "cancelled"));
savedBuildQueries.add(new NamedBuildQuery("Timed out", "timed out"));

View File

@ -0,0 +1,87 @@
package io.onedev.server.notification;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.onedev.commons.launcher.loader.Listen;
import io.onedev.server.entitymanager.UrlManager;
import io.onedev.server.event.build.BuildEvent;
import io.onedev.server.model.Build;
import io.onedev.server.model.BuildQuerySetting;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.model.support.NamedQuery;
import io.onedev.server.persistence.annotation.Sessional;
import io.onedev.server.search.entity.build.BuildQuery;
@Singleton
public class BuildNotificationManager {
private final MailManager mailManager;
private final UrlManager urlManager;
@Inject
public BuildNotificationManager(MailManager mailManager, UrlManager urlManager) {
this.mailManager = mailManager;
this.urlManager = urlManager;
}
private void fillSubscribedQueryStrings(Map<User, Collection<String>> subscribedQueryStrings, User user, @Nullable NamedQuery query) {
if (query != null) {
Collection<String> value = subscribedQueryStrings.get(user);
if (value == null) {
value = new HashSet<>();
subscribedQueryStrings.put(user, value);
}
value.add(query.getQuery());
}
}
@Sessional
@Listen
public void on(BuildEvent event) {
Project project = event.getProject();
Map<User, Collection<String>> subscribedQueryStrings = new HashMap<>();
for (BuildQuerySetting setting: project.getBuildQuerySettings()) {
for (String queryName: setting.getQuerySubscriptionSupport().getProjectQuerySubscriptions())
fillSubscribedQueryStrings(subscribedQueryStrings, setting.getUser(), project.getSavedBuildQuery(queryName));
for (String queryName: setting.getQuerySubscriptionSupport().getUserQuerySubscriptions())
fillSubscribedQueryStrings(subscribedQueryStrings, setting.getUser(), setting.getUserQuery(queryName));
}
Build build = event.getBuild();
Collection<String> notifyEmails = new HashSet<>();
for (Map.Entry<User, Collection<String>> entry: subscribedQueryStrings.entrySet()) {
User user = entry.getKey();
for (String queryString: entry.getValue()) {
try {
if (BuildQuery.parse(event.getProject(), queryString).matches(build, user)) {
notifyEmails.add(user.getEmail());
break;
}
} catch (Exception e) {
}
}
}
String subject;
if (build.getVersion() != null) {
subject = String.format("Build %s/%s/#%s (%s) is %s", build.getProject().getName(), build.getJobName(),
build.getNumber(), build.getVersion(), build.getStatus().getDisplayName().toLowerCase());
} else {
subject = String.format("Build %s/%s/#%s is %s", build.getProject().getName(), build.getJobName(),
build.getNumber(), build.getStatus().getDisplayName().toLowerCase());
}
String url = urlManager.urlFor(build);
String body = String.format("Visit <a href='%s'>%s</a> for details", url, url);
mailManager.sendMailAsync(notifyEmails, subject, body.toString());
}
}

View File

@ -23,7 +23,7 @@ public class WillRetryCriteria extends EntityCriteria<Build> {
@Override
public boolean matches(Build build, User user) {
return build.isWillRetry();
return build.willRetryNow();
}
@Override

View File

@ -13,6 +13,7 @@ import com.google.common.base.Splitter;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UrlManager;
import io.onedev.server.model.Build;
import io.onedev.server.model.CodeComment;
import io.onedev.server.model.CodeCommentReply;
import io.onedev.server.model.Issue;
@ -98,9 +99,14 @@ public class DefaultUrlManager implements UrlManager {
@Override
public String urlFor(Issue issue) {
return urlFor(issue.getProject()) + "/issues/" + issue.getNumber() + "/activities";
return urlFor(issue.getProject()) + "/issues/" + issue.getNumber();
}
@Override
public String urlFor(Build build) {
return urlFor(build.getProject()) + "/builds/" + build.getNumber();
}
@Override
public String urlFor(IssueComment comment) {
return urlFor(comment.getIssue()) + "#" + comment.getAnchor();

View File

@ -235,6 +235,7 @@ public class OneUrlMapper extends CompoundRequestMapper {
add(new OnePageMapper("projects/${project}/issue-boards", IssueBoardsPage.class));
add(new OnePageMapper("projects/${project}/issue-boards/${board}", IssueBoardsPage.class));
add(new OnePageMapper("projects/${project}/issue-list", IssueListPage.class));
add(new OnePageMapper("projects/${project}/issues/${issue}", IssueActivitiesPage.class));
add(new OnePageMapper("projects/${project}/issues/${issue}/activities", IssueActivitiesPage.class));
add(new OnePageMapper("projects/${project}/issues/${issue}/builds", FixingBuildsPage.class));
add(new OnePageMapper("projects/${project}/issues/new", NewIssuePage.class));