Post build action implementation

This commit is contained in:
Robin Shen 2019-10-15 22:42:52 +08:00
parent 39292b1c99
commit c2d7fa80de
115 changed files with 2586 additions and 413 deletions

View File

@ -19,6 +19,7 @@ import com.google.common.collect.Lists;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.ci.job.Job;
import io.onedev.server.ci.job.JobDependency;
import io.onedev.server.ci.job.action.PostBuildAction;
import io.onedev.server.ci.job.param.JobParam;
import io.onedev.server.ci.job.trigger.JobTrigger;
import io.onedev.server.migration.VersionedDocument;
@ -72,13 +73,15 @@ public class CISpec implements Serializable, Validatable {
for (int i=0; i<jobs.size(); i++) {
Job job = jobs.get(i);
int j=1;
for (JobDependency dependency: job.getJobDependencies()) {
Job dependencyJob = getJobMap().get(dependency.getJobName());
if (dependencyJob != null) {
try {
JobParam.validateParams(dependencyJob.getParamSpecs(), dependency.getJobParams());
} catch (ValidationException e) {
String message = "Error validating job parameters of dependency '"
String message = "Item #" + j + ": Error validating parameters of dependency job '"
+ dependencyJob.getName() + "': " + e.getMessage();
context.buildConstraintViolationWithTemplate(message)
.addPropertyNode("jobs").addPropertyNode("dependencies")
@ -101,18 +104,36 @@ public class CISpec implements Serializable, Validatable {
.addConstraintViolation();
valid = false;
}
j++;
}
j=1;
for (JobTrigger trigger: job.getTriggers()) {
try {
JobParam.validateParams(job.getParamSpecs(), trigger.getParams());
} catch (Exception e) {
String message = "Error validating job parameters: " + e.getMessage();
String message = "Item #" + j + ": Error validating job parameters: " + e.getMessage();
context.buildConstraintViolationWithTemplate(message)
.addPropertyNode("jobs").addPropertyNode("triggers")
.inIterable().atIndex(i)
.addConstraintViolation();
valid = false;
}
j++;
}
j=1;
for (PostBuildAction action: job.getPostBuildActions()) {
try {
action.validateWithContext(this, job);
} catch (Exception e) {
context.buildConstraintViolationWithTemplate("Item #" + j + ": " + e.getMessage())
.addPropertyNode("jobs").addPropertyNode("postBuildActions")
.inIterable().atIndex(i)
.addConstraintViolation();
valid = false;
}
j++;
}
}

View File

@ -53,6 +53,8 @@ import io.onedev.k8shelper.CacheInstance;
import io.onedev.server.OneDev;
import io.onedev.server.OneException;
import io.onedev.server.ci.CISpec;
import io.onedev.server.ci.job.action.PostBuildAction;
import io.onedev.server.ci.job.action.condition.ActionCondition;
import io.onedev.server.ci.job.log.LogManager;
import io.onedev.server.ci.job.param.JobParam;
import io.onedev.server.ci.job.paramspec.ParamSpec;
@ -519,28 +521,35 @@ public class DefaultJobManager implements JobManager, Runnable, CodePullAuthoriz
Long projectId = event.getProject().getId();
// run asynchrously as session may get closed due to exception
sessionManager.runAsync(new Runnable() {
OneDev.getInstance(TransactionManager.class).runAfterCommit(new Runnable() {
@Override
public void run() {
Project project = projectManager.load(projectId);
try {
new MatrixRunner<List<String>>(paramMatrix) {
@Override
public void run(Map<String, List<String>> paramMap) {
submit(project, commitId, job.getName(), paramMap, null);
sessionManager.runAsync(new Runnable() {
@Override
public void run() {
Project project = projectManager.load(projectId);
try {
new MatrixRunner<List<String>>(paramMatrix) {
@Override
public void run(Map<String, List<String>> paramMap) {
submit(project, commitId, job.getName(), paramMap, null);
}
}.run();
} catch (Exception e) {
String message = String.format("Error submitting build (project: %s, commit: %s, job: %s)",
project.getName(), commitId.name(), job.getName());
logger.error(message, e);
}
}.run();
} catch (Exception e) {
String message = String.format("Error submitting build (project: %s, commit: %s, job: %s)",
project.getName(), commitId.name(), job.getName());
logger.error(message, e);
}
}
}, SecurityUtils.getSubject());
}
}, SecurityUtils.getSubject());
});
}
}
}
@ -772,7 +781,18 @@ public class DefaultJobManager implements JobManager, Runnable, CodePullAuthoriz
}
}, SecurityUtils.getSubject());
} else {
}
try {
for (PostBuildAction action: build.getJob().getPostBuildActions()) {
if (ActionCondition.parse(action.getCondition()).test(build))
action.execute(build);
}
} catch (Exception e) {
logger.error("Error processing post build actions", e);
}
if (!build.willRetryNow()) {
for (BuildParam param: build.getParams()) {
if (param.getType().equals(ParamSpec.SECRET))
param.setValue(null);

View File

@ -23,6 +23,9 @@ import org.eclipse.jgit.lib.ObjectId;
import org.hibernate.validator.constraints.NotEmpty;
import io.onedev.commons.codeassist.InputSuggestion;
import io.onedev.server.ci.CISpec;
import io.onedev.server.ci.CISpecAware;
import io.onedev.server.ci.job.action.PostBuildAction;
import io.onedev.server.ci.job.param.JobParam;
import io.onedev.server.ci.job.paramspec.ParamSpec;
import io.onedev.server.ci.job.retry.JobRetry;
@ -86,6 +89,8 @@ public class Job implements Serializable, Validatable {
private long timeout = 3600;
private List<PostBuildAction> postBuildActions = new ArrayList<>();
private JobRetry retry;
private transient Map<String, ParamSpec> paramSpecMap;
@ -305,6 +310,7 @@ public class Job implements Serializable, Validatable {
@Editable(order=10600, name="Retry When Failed", group="More Settings", description="Check to re-run the job upon build failure")
@NameOfEmptyValue("Do not retry")
@Valid
public JobRetry getRetry() {
return retry;
}
@ -313,6 +319,16 @@ public class Job implements Serializable, Validatable {
this.retry = retry;
}
@Editable(order=10600, name="Post Build Actions", group="More Settings")
@Valid
public List<PostBuildAction> getPostBuildActions() {
return postBuildActions;
}
public void setPostBuildActions(List<PostBuildAction> postBuildActions) {
this.postBuildActions = postBuildActions;
}
public JobTrigger getMatchedTrigger(ProjectEvent event) {
for (JobTrigger trigger: getTriggers()) {
if (trigger.matches(event, this))
@ -401,4 +417,26 @@ public class Job implements Serializable, Validatable {
+ quote(FIELD_JOB) + " " + getRuleName(Is) + " " + quote(jobName);
}
public static List<String> getChoices() {
List<String> choices = new ArrayList<>();
Component component = ComponentContext.get().getComponent();
CISpecAware ciSpecAware = WicketUtils.findInnermost(component, CISpecAware.class);
if (ciSpecAware != null) {
CISpec ciSpec = ciSpecAware.getCISpec();
if (ciSpec != null) {
for (Job eachJob: ciSpec.getJobs()) {
if (eachJob.getName() != null)
choices.add(eachJob.getName());
}
}
JobAware jobAware = WicketUtils.findInnermost(component, JobAware.class);
if (jobAware != null) {
Job job = jobAware.getJob();
if (job != null)
choices.remove(job.getName());
}
}
return choices;
}
}

View File

@ -57,7 +57,7 @@ public class JobDependency implements Serializable {
this.requireSuccessful = requireSuccessful;
}
@Editable(order=200)
@Editable(order=200, name="Job Parameters")
@ParamSpecProvider("getParamSpecs")
@OmitName
public List<JobParam> getJobParams() {
@ -89,25 +89,7 @@ public class JobDependency implements Serializable {
@SuppressWarnings("unused")
private static List<String> getJobChoices() {
List<String> choices = new ArrayList<>();
Component component = ComponentContext.get().getComponent();
CISpecAware ciSpecAware = WicketUtils.findInnermost(component, CISpecAware.class);
if (ciSpecAware != null) {
CISpec ciSpec = ciSpecAware.getCISpec();
if (ciSpec != null) {
for (Job eachJob: ciSpec.getJobs()) {
if (eachJob.getName() != null)
choices.add(eachJob.getName());
}
}
JobAware jobAware = WicketUtils.findInnermost(component, JobAware.class);
if (jobAware != null) {
Job job = jobAware.getJob();
if (job != null)
choices.remove(job.getName());
}
}
return choices;
return Job.getChoices();
}
@SuppressWarnings("unused")

View File

@ -0,0 +1,37 @@
package io.onedev.server.ci.job.action;
import java.io.Serializable;
import org.hibernate.validator.constraints.NotEmpty;
import io.onedev.server.ci.CISpec;
import io.onedev.server.ci.job.Job;
import io.onedev.server.model.Build;
import io.onedev.server.web.editable.annotation.ActionCondition;
import io.onedev.server.web.editable.annotation.Editable;
@Editable
public abstract class PostBuildAction implements Serializable {
private static final long serialVersionUID = 1L;
private String condition;
@Editable(order=100, description="Specify the condition current build must satisfy to execute this action")
@ActionCondition
@NotEmpty
public String getCondition() {
return condition;
}
public void setCondition(String condition) {
this.condition = condition;
}
public abstract void execute(Build build);
public abstract String getDescription();
public abstract void validateWithContext(CISpec ciSpec, Job job);
}

View File

@ -0,0 +1,152 @@
package io.onedev.server.ci.job.action;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.validation.Valid;
import javax.validation.ValidationException;
import org.apache.shiro.SecurityUtils;
import org.apache.wicket.Component;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.onedev.commons.utils.MatrixRunner;
import io.onedev.server.OneDev;
import io.onedev.server.ci.CISpec;
import io.onedev.server.ci.CISpecAware;
import io.onedev.server.ci.job.Job;
import io.onedev.server.ci.job.JobManager;
import io.onedev.server.ci.job.param.JobParam;
import io.onedev.server.ci.job.paramspec.ParamSpec;
import io.onedev.server.entitymanager.BuildManager;
import io.onedev.server.model.Build;
import io.onedev.server.persistence.SessionManager;
import io.onedev.server.persistence.TransactionManager;
import io.onedev.server.util.ComponentContext;
import io.onedev.server.util.EditContext;
import io.onedev.server.web.editable.annotation.ChoiceProvider;
import io.onedev.server.web.editable.annotation.Editable;
import io.onedev.server.web.editable.annotation.OmitName;
import io.onedev.server.web.editable.annotation.ParamSpecProvider;
import io.onedev.server.web.util.WicketUtils;
@Editable(name="Run job", order=100)
public class RunJobAction extends PostBuildAction {
private static final long serialVersionUID = 1L;
private static final Logger logger = LoggerFactory.getLogger(RunJobAction.class);
private String jobName;
private List<JobParam> jobParams = new ArrayList<>();
@Editable(order=900, name="Job")
@ChoiceProvider("getJobChoices")
@NotEmpty
public String getJobName() {
return jobName;
}
public void setJobName(String jobName) {
this.jobName = jobName;
}
@SuppressWarnings("unused")
private static List<String> getJobChoices() {
return Job.getChoices();
}
@Editable(name="Job Parameters", order=1000)
@ParamSpecProvider("getParamSpecs")
@OmitName
@Valid
public List<JobParam> getJobParams() {
return jobParams;
}
public void setJobParams(List<JobParam> jobParams) {
this.jobParams = jobParams;
}
@SuppressWarnings("unused")
private static List<ParamSpec> getParamSpecs() {
String jobName = (String) EditContext.get().getInputValue("jobName");
if (jobName != null) {
Component component = ComponentContext.get().getComponent();
CISpecAware ciSpecAware = WicketUtils.findInnermost(component, CISpecAware.class);
if (ciSpecAware != null) {
CISpec ciSpec = ciSpecAware.getCISpec();
if (ciSpec != null) {
Job job = ciSpec.getJobMap().get(jobName);
if (job != null)
return job.getParamSpecs();
}
}
}
return new ArrayList<>();
}
@Override
public void execute(Build build) {
Long buildId = build.getId();
OneDev.getInstance(TransactionManager.class).runAfterCommit(new Runnable() {
@Override
public void run() {
OneDev.getInstance(SessionManager.class).runAsync(new Runnable() {
@Override
public void run() {
Build build = OneDev.getInstance(BuildManager.class).load(buildId);
Build.push(build);
try {
new MatrixRunner<List<String>>(JobParam.getParamMatrix(getJobParams())) {
@Override
public void run(Map<String, List<String>> paramMap) {
OneDev.getInstance(JobManager.class).submit(build.getProject(),
build.getCommitId(), jobName, paramMap, null);
}
}.run();
} catch (Exception e) {
String message = String.format("Error submitting build (project: %s, commit: %s, job: %s)",
build.getProject().getName(), build.getCommitHash(), jobName);
logger.error(message, e);
} finally {
Build.pop();
}
}
}, SecurityUtils.getSubject());
}
});
}
@Override
public String getDescription() {
return "Run job '" + jobName + "'";
}
@Override
public void validateWithContext(CISpec ciSpec, Job job) {
Job jobToRun = ciSpec.getJobMap().get(jobName);
if (jobToRun != null) {
try {
JobParam.validateParams(jobToRun.getParamSpecs(), jobParams);
} catch (ValidationException e) {
throw new ValidationException("Error validating parameters of run job '"
+ jobToRun.getName() + "': " + e.getMessage());
}
} else {
throw new ValidationException("Run job not found: " + jobName);
}
}
}

View File

@ -0,0 +1,46 @@
package io.onedev.server.ci.job.action;
import org.hibernate.validator.constraints.NotEmpty;
import io.onedev.server.OneDev;
import io.onedev.server.ci.CISpec;
import io.onedev.server.ci.job.Job;
import io.onedev.server.model.Build;
import io.onedev.server.notification.BuildNotificationManager;
import io.onedev.server.web.editable.annotation.Editable;
import io.onedev.server.web.editable.annotation.NotificationReceiver;
@Editable(name="Send notification", order=200)
public class SendNotificationAction extends PostBuildAction {
private static final long serialVersionUID = 1L;
private String receivers;
@Editable(order=1000)
@NotificationReceiver
@NotEmpty
public String getReceivers() {
return receivers;
}
public void setReceivers(String receivers) {
this.receivers = receivers;
}
@Override
public void execute(Build build) {
OneDev.getInstance(BuildNotificationManager.class).notify(build,
io.onedev.server.ci.job.action.notificationreceiver.NotificationReceiver.fromString(receivers, build).getEmails());
}
@Override
public String getDescription() {
return "Send notification to " + receivers;
}
@Override
public void validateWithContext(CISpec ciSpec, Job job) {
}
}

View File

@ -0,0 +1,111 @@
grammar ActionCondition;
condition
: WS* (criteria|Always) WS* EOF
;
criteria
: operator=(Successful|Failed|InError|Cancelled|TimedOut|PreviousIsSuccessful|PreviousIsFailed|PreviousIsInError|PreviousIsCancelled|PreviousIsTimedOut|WillRetry|AssociatedWithPullRequests|RequiredByPullRequests) #OperatorCriteria
| operator=OnBranch WS+ criteriaValue=Quoted #OperatorValueCriteria
| criteriaField=Quoted WS+ operator=Is WS+ criteriaValue=Quoted #FieldOperatorValueCriteria
| criteria WS+ And WS+ criteria #AndCriteria
| criteria WS+ Or WS+ criteria #OrCriteria
| Not WS* LParens WS* criteria WS* RParens #NotCriteria
| LParens WS* criteria WS* RParens #ParensCriteria
;
Always
: 'always'
;
Successful
: 'successful'
;
Failed
: 'failed'
;
InError
: 'in' WS+ 'error'
;
Cancelled
: 'cancelled'
;
TimedOut
: 'timed' WS+ 'out'
;
PreviousIsSuccessful
: 'previous' WS+ 'is' WS+ 'successful'
;
PreviousIsFailed
: 'previous' WS+ 'is' WS+ 'failed'
;
PreviousIsInError
: 'previous' WS+ 'is' WS+ 'in' WS+ 'error'
;
PreviousIsCancelled
: 'previous' WS+ 'is' WS+ 'cancelled'
;
PreviousIsTimedOut
: 'previous' WS+ 'is' WS+ 'timed' WS+ 'out'
;
OnBranch
: 'on' WS+ 'branch'
;
WillRetry
: 'will' WS+ 'retry'
;
AssociatedWithPullRequests
: 'associated' WS+ 'with' WS+ 'pull' WS+ 'requests'
;
RequiredByPullRequests
: 'required' WS+ 'by' WS+ 'pull' WS+ 'requests'
;
Is
: 'is'
;
And
: 'and'
;
Or
: 'or'
;
Not
: 'not'
;
LParens
: '('
;
RParens
: ')'
;
Quoted
: '"' ('\\'.|~[\\"])+? '"'
;
WS
: ' '
;
Identifier
: [a-zA-Z0-9:_/\\+\-;]+
;

View File

@ -0,0 +1,171 @@
package io.onedev.server.ci.job.action.condition;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import io.onedev.commons.codeassist.FenceAware;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.OneException;
import io.onedev.server.ci.job.Job;
import io.onedev.server.ci.job.action.condition.ActionConditionBaseVisitor;
import io.onedev.server.ci.job.action.condition.ActionConditionLexer;
import io.onedev.server.ci.job.action.condition.ActionConditionParser;
import io.onedev.server.ci.job.action.condition.ActionConditionParser.AndCriteriaContext;
import io.onedev.server.ci.job.action.condition.ActionConditionParser.ConditionContext;
import io.onedev.server.ci.job.action.condition.ActionConditionParser.CriteriaContext;
import io.onedev.server.ci.job.action.condition.ActionConditionParser.FieldOperatorValueCriteriaContext;
import io.onedev.server.ci.job.action.condition.ActionConditionParser.NotCriteriaContext;
import io.onedev.server.ci.job.action.condition.ActionConditionParser.OperatorCriteriaContext;
import io.onedev.server.ci.job.action.condition.ActionConditionParser.OperatorValueCriteriaContext;
import io.onedev.server.ci.job.action.condition.ActionConditionParser.OrCriteriaContext;
import io.onedev.server.ci.job.action.condition.ActionConditionParser.ParensCriteriaContext;
import io.onedev.server.model.Build;
import io.onedev.server.util.criteria.AndCriteria;
import io.onedev.server.util.criteria.NotCriteria;
import io.onedev.server.util.criteria.OrCriteria;
public class ActionCondition implements Predicate<Build> {
private final Predicate<Build> criteria;
public ActionCondition(Predicate<Build> criteria) {
this.criteria = criteria;
}
private static String getValue(String token) {
return StringUtils.unescape(FenceAware.unfence(token));
}
public boolean test(Build build) {
return criteria.test(build);
}
public static ActionCondition parse(String conditionString) {
CharStream is = CharStreams.fromString(conditionString);
ActionConditionLexer lexer = new ActionConditionLexer(is);
lexer.removeErrorListeners();
lexer.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line,
int charPositionInLine, String msg, RecognitionException e) {
throw new OneException("Malformed condition syntax", e);
}
});
CommonTokenStream tokens = new CommonTokenStream(lexer);
ActionConditionParser parser = new ActionConditionParser(tokens);
parser.removeErrorListeners();
parser.setErrorHandler(new BailErrorStrategy());
ConditionContext conditionContext;
try {
conditionContext = parser.condition();
} catch (Exception e) {
if (e instanceof OneException)
throw e;
else
throw new OneException("Malformed condition syntax", e);
}
Predicate<Build> criteria;
if (conditionContext.Always() != null) {
criteria = new AlwaysCriteria();
} else {
criteria = new ActionConditionBaseVisitor<Predicate<Build>>() {
@Override
public Predicate<Build> visitParensCriteria(ParensCriteriaContext ctx) {
return visit(ctx.criteria());
}
@Override
public Predicate<Build> visitOperatorCriteria(OperatorCriteriaContext ctx) {
switch (ctx.operator.getType()) {
case ActionConditionLexer.Successful:
return new SuccessfulCriteria();
case ActionConditionLexer.Failed:
return new FailedCriteria();
case ActionConditionLexer.Cancelled:
return new CancelledCriteria();
case ActionConditionLexer.TimedOut:
return new TimedOutCriteria();
case ActionConditionLexer.InError:
return new InErrorCriteria();
case ActionConditionLexer.PreviousIsSuccessful:
return new PreviousIsSuccessfulCriteria();
case ActionConditionLexer.PreviousIsFailed:
return new PreviousIsFailedCriteria();
case ActionConditionLexer.PreviousIsCancelled:
return new PreviousIsCancelledCriteria();
case ActionConditionLexer.PreviousIsTimedOut:
return new PreviousIsTimedOutCriteria();
case ActionConditionLexer.PreviousIsInError:
return new PreviousInErrorCriteria();
case ActionConditionLexer.WillRetry:
return new WillRetryCriteria();
case ActionConditionLexer.AssociatedWithPullRequests:
return new AssociatedWithPullRequestsCriteria();
case ActionConditionLexer.RequiredByPullRequests:
return new RequiredByPullRequestsCriteria();
default:
throw new OneException("Unexpected operator: " + ctx.operator.getText());
}
}
@Override
public Predicate<Build> visitFieldOperatorValueCriteria(FieldOperatorValueCriteriaContext ctx) {
String fieldName = getValue(ctx.Quoted(0).getText());
String fieldValue = getValue(ctx.Quoted(1).getText());
int operator = ctx.operator.getType();
checkField(Build.get().getJob(), fieldName, operator);
return new ParamCriteria(fieldName, fieldValue);
}
@Override
public Predicate<Build> visitOperatorValueCriteria(OperatorValueCriteriaContext ctx) {
String fieldValue = getValue(ctx.Quoted().getText());
return new OnBranchCriteria(fieldValue);
}
@Override
public Predicate<Build> visitOrCriteria(OrCriteriaContext ctx) {
List<Predicate<Build>> childCriterias = new ArrayList<>();
for (CriteriaContext childCtx: ctx.criteria())
childCriterias.add(visit(childCtx));
return new OrCriteria<Build>(childCriterias);
}
@Override
public Predicate<Build> visitAndCriteria(AndCriteriaContext ctx) {
List<Predicate<Build>> childCriterias = new ArrayList<>();
for (CriteriaContext childCtx: ctx.criteria())
childCriterias.add(visit(childCtx));
return new AndCriteria<Build>(childCriterias);
}
@Override
public Predicate<Build> visitNotCriteria(NotCriteriaContext ctx) {
return new NotCriteria<Build>(visit(ctx.criteria()));
}
}.visit(conditionContext.criteria());
}
return new ActionCondition(criteria);
}
public static void checkField(Job job, String fieldName, int operator) {
if (!job.getParamSpecMap().containsKey(fieldName))
throw new OneException("Param not found: " + fieldName);
}
}

View File

@ -0,0 +1,14 @@
package io.onedev.server.ci.job.action.condition;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class AlwaysCriteria implements Predicate<Build> {
@Override
public boolean test(Build build) {
return true;
}
}

View File

@ -0,0 +1,14 @@
package io.onedev.server.ci.job.action.condition;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class AssociatedWithPullRequestsCriteria implements Predicate<Build> {
@Override
public boolean test(Build build) {
return !build.getPullRequestBuilds().isEmpty();
}
}

View File

@ -0,0 +1,14 @@
package io.onedev.server.ci.job.action.condition;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class CancelledCriteria implements Predicate<Build> {
@Override
public boolean test(Build build) {
return build.getStatus() == Build.Status.CANCELLED;
}
}

View File

@ -0,0 +1,14 @@
package io.onedev.server.ci.job.action.condition;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class FailedCriteria implements Predicate<Build> {
@Override
public boolean test(Build build) {
return build.getStatus() == Build.Status.FAILED;
}
}

View File

@ -0,0 +1,14 @@
package io.onedev.server.ci.job.action.condition;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class InErrorCriteria implements Predicate<Build> {
@Override
public boolean test(Build build) {
return build.getStatus() == Build.Status.IN_ERROR;
}
}

View File

@ -0,0 +1,20 @@
package io.onedev.server.ci.job.action.condition;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class OnBranchCriteria implements Predicate<Build> {
private final String branch;
public OnBranchCriteria(String branch) {
this.branch = branch;
}
@Override
public boolean test(Build build) {
return build.getProject().isCommitOnBranches(build.getCommitId(), branch);
}
}

View File

@ -0,0 +1,28 @@
package io.onedev.server.ci.job.action.condition;
import java.util.List;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class ParamCriteria implements Predicate<Build> {
private String name;
private String value;
public ParamCriteria(String name, String value) {
this.name = name;
this.value = value;
}
@Override
public boolean test(Build build) {
List<String> paramValues = build.getParamMap().get(name);
if (paramValues == null || paramValues.isEmpty())
return value == null;
else
return paramValues.contains(value);
}
}

View File

@ -0,0 +1,15 @@
package io.onedev.server.ci.job.action.condition;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class PreviousInErrorCriteria implements Predicate<Build> {
@Override
public boolean test(Build build) {
return build.getStreamPrevious(null) != null
&& build.getStreamPrevious(null).getStatus() == Build.Status.IN_ERROR;
}
}

View File

@ -0,0 +1,15 @@
package io.onedev.server.ci.job.action.condition;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class PreviousIsCancelledCriteria implements Predicate<Build> {
@Override
public boolean test(Build build) {
return build.getStreamPrevious(null) != null
&& build.getStreamPrevious(null).getStatus() == Build.Status.CANCELLED;
}
}

View File

@ -0,0 +1,15 @@
package io.onedev.server.ci.job.action.condition;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class PreviousIsFailedCriteria implements Predicate<Build> {
@Override
public boolean test(Build build) {
return build.getStreamPrevious(null) != null
&& build.getStreamPrevious(null).getStatus() == Build.Status.FAILED;
}
}

View File

@ -0,0 +1,15 @@
package io.onedev.server.ci.job.action.condition;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class PreviousIsSuccessfulCriteria implements Predicate<Build> {
@Override
public boolean test(Build build) {
return build.getStreamPrevious(null) != null
&& build.getStreamPrevious(null).getStatus() == Build.Status.SUCCESSFUL;
}
}

View File

@ -0,0 +1,15 @@
package io.onedev.server.ci.job.action.condition;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class PreviousIsTimedOutCriteria implements Predicate<Build> {
@Override
public boolean test(Build build) {
return build.getStreamPrevious(null) != null
&& build.getStreamPrevious(null).getStatus() == Build.Status.TIMED_OUT;
}
}

View File

@ -0,0 +1,13 @@
package io.onedev.server.ci.job.action.condition;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class RequiredByPullRequestsCriteria implements Predicate<Build> {
@Override
public boolean test(Build build) {
return build.getPullRequestBuilds().stream().anyMatch(it->it.isRequired());
}
}

View File

@ -0,0 +1,14 @@
package io.onedev.server.ci.job.action.condition;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class SuccessfulCriteria implements Predicate<Build> {
@Override
public boolean test(Build build) {
return build.getStatus() == Build.Status.SUCCESSFUL;
}
}

View File

@ -0,0 +1,14 @@
package io.onedev.server.ci.job.action.condition;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class TimedOutCriteria implements Predicate<Build> {
@Override
public boolean test(Build build) {
return build.getStatus() == Build.Status.TIMED_OUT;
}
}

View File

@ -0,0 +1,14 @@
package io.onedev.server.ci.job.action.condition;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class WillRetryCriteria implements Predicate<Build> {
@Override
public boolean test(Build build) {
return build.willRetryNow();
}
}

View File

@ -0,0 +1,29 @@
grammar NotificationReceiver;
receiver: WS* criteria (WS+ 'and' WS+ criteria)* WS* EOF;
criteria: userCriteria | groupCriteria | Committers | Authors | Submitter | AuthorsSincePreviousSuccessful | CommittersSincePreviousSuccessful;
userCriteria: USER Value;
groupCriteria: GROUP Value;
Committers: 'committers';
CommittersSincePreviousSuccessful: 'committers-since-previous-successful';
Authors: 'authors';
AuthorsSincePreviousSuccessful: 'authors-since-previous-successful';
Submitter: 'submitter';
USER: 'user';
GROUP: 'group';
WS: ' ';
Value: '(' ('\\'.|~[\\()])+? ')';
Identifier
: [a-zA-Z0-9:_/\\+\-;]+
;

View File

@ -0,0 +1,131 @@
package io.onedev.server.ci.job.action.notificationreceiver;
import java.util.Collection;
import java.util.HashSet;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
import io.onedev.commons.codeassist.FenceAware;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.OneDev;
import io.onedev.server.OneException;
import io.onedev.server.ci.job.action.notificationreceiver.NotificationReceiverParser.CriteriaContext;
import io.onedev.server.ci.job.action.notificationreceiver.NotificationReceiverParser.ReceiverContext;
import io.onedev.server.entitymanager.GroupManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.Build;
import io.onedev.server.model.Group;
import io.onedev.server.model.User;
public class NotificationReceiver {
private final Collection<String> emails;
public NotificationReceiver(Collection<String> emails) {
this.emails = emails;
}
public static NotificationReceiver fromString(String receiverString, @Nullable Build build) {
Collection<String> emails = new HashSet<>();
ReceiverContext receiver = parse(receiverString);
for (CriteriaContext criteria: receiver.criteria()) {
if (criteria.userCriteria() != null) {
String userName = getValue(criteria.userCriteria().Value());
User user = OneDev.getInstance(UserManager.class).findByName(userName);
if (user != null)
emails.add(user.getEmail());
else
throw new OneException("Unable to find user '" + userName + "'");
} else if (criteria.groupCriteria() != null) {
String groupName = getValue(criteria.groupCriteria().Value());
Group group = OneDev.getInstance(GroupManager.class).find(groupName);
if (group != null)
emails.addAll(group.getMembers().stream().map(it->it.getEmail()).collect(Collectors.toList()));
else
throw new OneException("Unable to find group '" + groupName + "'");
} else if (criteria.Committers() != null) {
if (build != null) {
for (RevCommit commit: build.getCommits(null)) {
PersonIdent committer = commit.getCommitterIdent();
if (committer != null && committer.getEmailAddress() != null)
emails.add(committer.getEmailAddress());
}
}
} else if (criteria.Authors() != null) {
if (build != null) {
for (RevCommit commit: build.getCommits(null)) {
PersonIdent author = commit.getAuthorIdent();
if (author != null && author.getEmailAddress() != null)
emails.add(author.getEmailAddress());
}
}
} else if (criteria.CommittersSincePreviousSuccessful() != null) {
if (build != null) {
for (RevCommit commit: build.getCommits(Build.Status.SUCCESSFUL)) {
PersonIdent committer = commit.getCommitterIdent();
if (committer != null && committer.getEmailAddress() != null)
emails.add(committer.getEmailAddress());
}
}
} else if (criteria.AuthorsSincePreviousSuccessful() != null) {
if (build != null) {
for (RevCommit commit: build.getCommits(Build.Status.SUCCESSFUL)) {
PersonIdent author = commit.getAuthorIdent();
if (author != null && author.getEmailAddress() != null)
emails.add(author.getEmailAddress());
}
}
} else if (criteria.Submitter() != null) {
if (build != null && build.getSubmitter() != null)
emails.add(build.getSubmitter().getEmail());
} else {
throw new RuntimeException("Unexpected notification receiver criteria");
}
}
return new NotificationReceiver(emails);
}
public static ReceiverContext parse(String requirementString) {
CharStream is = CharStreams.fromString(requirementString);
NotificationReceiverLexer lexer = new NotificationReceiverLexer(is);
lexer.removeErrorListeners();
lexer.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line,
int charPositionInLine, String msg, RecognitionException e) {
throw new OneException("Malformed notification receiver");
}
});
CommonTokenStream tokens = new CommonTokenStream(lexer);
NotificationReceiverParser parser = new NotificationReceiverParser(tokens);
parser.removeErrorListeners();
parser.setErrorHandler(new BailErrorStrategy());
return parser.receiver();
}
private static String getValue(TerminalNode terminal) {
return StringUtils.unescape(FenceAware.unfence(terminal.getText()));
}
public Collection<String> getEmails() {
return emails;
}
}

View File

@ -6,7 +6,7 @@ import org.hibernate.validator.constraints.NotEmpty;
import io.onedev.server.model.support.inputspec.InputSpec;
import io.onedev.server.model.support.inputspec.showcondition.ShowCondition;
import io.onedev.server.util.validation.annotation.FieldName;
import io.onedev.server.util.validation.annotation.ParamName;
import io.onedev.server.web.editable.annotation.Editable;
import io.onedev.server.web.editable.annotation.NameOfEmptyValue;
@ -16,7 +16,7 @@ public abstract class ParamSpec extends InputSpec {
private static final long serialVersionUID = 1L;
@Editable(order=10)
@FieldName
@ParamName
@NotEmpty
@Override
public String getName() {

View File

@ -1,26 +0,0 @@
package io.onedev.server.ci.job.retry;
import java.util.List;
import io.onedev.server.model.Build;
public class AndCriteria extends Criteria {
private static final long serialVersionUID = 1L;
private final List<? extends Criteria> criterias;
public AndCriteria(List<? extends Criteria> criterias) {
this.criterias = criterias;
}
@Override
public boolean satisfied(Build build) {
for (Criteria criteria: criterias) {
if (!criteria.satisfied(build))
return false;
}
return true;
}
}

View File

@ -14,7 +14,7 @@ public class JobRetry implements Serializable {
private static final long serialVersionUID = 1L;
private String retryCondition;
private String condition;
private int maxRetries = 3;
@ -23,12 +23,12 @@ public class JobRetry implements Serializable {
@Editable(order=100, description="Specify the condition to retry the failed build")
@NotEmpty
@RetryCondition
public String getRetryCondition() {
return retryCondition;
public String getCondition() {
return condition;
}
public void setRetryCondition(String retryCondition) {
this.retryCondition = retryCondition;
public void setCondition(String condition) {
this.condition = condition;
}
@Editable(order=200, description="Maximum of retries before giving up")

View File

@ -1,20 +0,0 @@
package io.onedev.server.ci.job.retry;
import io.onedev.server.model.Build;
public class NotCriteria extends Criteria {
private static final long serialVersionUID = 1L;
private final Criteria criteria;
public NotCriteria(Criteria criteria) {
this.criteria = criteria;
}
@Override
public boolean satisfied(Build build) {
return !criteria.satisfied(build);
}
}

View File

@ -1,26 +0,0 @@
package io.onedev.server.ci.job.retry;
import java.util.List;
import io.onedev.server.model.Build;
public class OrCriteria extends Criteria {
private static final long serialVersionUID = 1L;
private final List<? extends Criteria> criterias;
public OrCriteria(List<? extends Criteria> criterias) {
this.criterias = criterias;
}
@Override
public boolean satisfied(Build build) {
for (Criteria criteria: criterias) {
if (criteria.satisfied(build))
return true;
}
return false;
}
}

View File

@ -1,148 +0,0 @@
package io.onedev.server.ci.job.retry;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import io.onedev.commons.codeassist.AntlrUtils;
import io.onedev.commons.codeassist.FenceAware;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.OneException;
import io.onedev.server.ci.job.retry.RetryConditionParser.AndCriteriaContext;
import io.onedev.server.ci.job.retry.RetryConditionParser.ConditionContext;
import io.onedev.server.ci.job.retry.RetryConditionParser.CriteriaContext;
import io.onedev.server.ci.job.retry.RetryConditionParser.FieldOperatorValueCriteriaContext;
import io.onedev.server.ci.job.retry.RetryConditionParser.NotCriteriaContext;
import io.onedev.server.ci.job.retry.RetryConditionParser.OrCriteriaContext;
import io.onedev.server.ci.job.retry.RetryConditionParser.ParensCriteriaContext;
import io.onedev.server.model.Build;
public class RetryCondition implements Serializable {
private static final long serialVersionUID = 1L;
public static final String FIELD_LOG = "Log";
public static final String FIELD_ERROR_MESSAGE = "Error Message";
private final Criteria criteria;
public RetryCondition(Criteria criteria) {
this.criteria = criteria;
}
private static String getValue(String token) {
return StringUtils.unescape(FenceAware.unfence(token));
}
public boolean satisfied(Build build) {
return criteria.satisfied(build);
}
public static RetryCondition parse(String conditionString) {
CharStream is = CharStreams.fromString(conditionString);
RetryConditionLexer lexer = new RetryConditionLexer(is);
lexer.removeErrorListeners();
lexer.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line,
int charPositionInLine, String msg, RecognitionException e) {
throw new OneException("Malformed condition syntax", e);
}
});
CommonTokenStream tokens = new CommonTokenStream(lexer);
RetryConditionParser parser = new RetryConditionParser(tokens);
parser.removeErrorListeners();
parser.setErrorHandler(new BailErrorStrategy());
ConditionContext conditionContext;
try {
conditionContext = parser.condition();
} catch (Exception e) {
if (e instanceof OneException)
throw e;
else
throw new OneException("Malformed condition syntax", e);
}
Criteria criteria = new RetryConditionBaseVisitor<Criteria>() {
@Override
public Criteria visitParensCriteria(ParensCriteriaContext ctx) {
return visit(ctx.criteria());
}
@Override
public Criteria visitFieldOperatorValueCriteria(FieldOperatorValueCriteriaContext ctx) {
String fieldName = getValue(ctx.Quoted(0).getText());
String value = getValue(ctx.Quoted(1).getText());
int operator = ctx.operator.getType();
checkField(fieldName, operator);
switch (operator) {
case RetryConditionLexer.Contains:
switch (fieldName) {
case FIELD_LOG:
return new LogCriteria(value);
case FIELD_ERROR_MESSAGE:
return new ErrorMessageCriteria(value);
default:
throw new IllegalStateException();
}
default:
throw new IllegalStateException();
}
}
@Override
public Criteria visitOrCriteria(OrCriteriaContext ctx) {
List<Criteria> childCriterias = new ArrayList<>();
for (CriteriaContext childCtx: ctx.criteria())
childCriterias.add(visit(childCtx));
return new OrCriteria(childCriterias);
}
@Override
public Criteria visitAndCriteria(AndCriteriaContext ctx) {
List<Criteria> childCriterias = new ArrayList<>();
for (CriteriaContext childCtx: ctx.criteria())
childCriterias.add(visit(childCtx));
return new AndCriteria(childCriterias);
}
@Override
public Criteria visitNotCriteria(NotCriteriaContext ctx) {
return new NotCriteria(visit(ctx.criteria()));
}
}.visit(conditionContext.criteria());
return new RetryCondition(criteria);
}
public static void checkField(String fieldName, int operator) {
if (!fieldName.equals(FIELD_ERROR_MESSAGE) && !fieldName.equals(FIELD_LOG))
throw new OneException("Field not found: " + fieldName);
switch (operator) {
case RetryConditionLexer.Contains:
if (!fieldName.equals(FIELD_LOG) && !fieldName.equals(FIELD_ERROR_MESSAGE))
throw newOperatorException(fieldName, operator);
break;
}
}
private static OneException newOperatorException(String fieldName, int operator) {
return new OneException("Field '" + fieldName + "' is not applicable for operator '"
+ AntlrUtils.getLexerRuleName(RetryConditionLexer.ruleNames, operator) + "'");
}
}

View File

@ -0,0 +1,14 @@
package io.onedev.server.ci.job.retry.condition;
import java.util.function.Predicate;
import io.onedev.server.model.Build;
public class AlwaysCriteria implements Predicate<Build> {
@Override
public boolean test(Build build) {
return true;
}
}

View File

@ -1,4 +1,4 @@
package io.onedev.server.ci.job.retry;
package io.onedev.server.ci.job.retry.condition;
import java.io.Serializable;

View File

@ -1,21 +1,20 @@
package io.onedev.server.ci.job.retry;
package io.onedev.server.ci.job.retry.condition;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import io.onedev.server.model.Build;
public class ErrorMessageCriteria extends Criteria {
private static final long serialVersionUID = 1L;
public class ErrorMessageCriteria implements Predicate<Build> {
private final String value;
public ErrorMessageCriteria(String value) {
this.value = value;
}
@Override
public boolean satisfied(Build build) {
public boolean test(Build build) {
return Pattern.compile(value).matcher(build.getStatusMessage()).find();
}

View File

@ -1,14 +1,13 @@
package io.onedev.server.ci.job.retry;
package io.onedev.server.ci.job.retry.condition;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import io.onedev.server.OneDev;
import io.onedev.server.ci.job.log.LogManager;
import io.onedev.server.model.Build;
public class LogCriteria extends Criteria {
private static final long serialVersionUID = 1L;
public class LogCriteria implements Predicate<Build> {
private final String value;
@ -17,7 +16,7 @@ public class LogCriteria extends Criteria {
}
@Override
public boolean satisfied(Build build) {
public boolean test(Build build) {
Pattern pattern = Pattern.compile(value);
return OneDev.getInstance(LogManager.class).matches(build, pattern);
}

View File

@ -1,7 +1,7 @@
grammar RetryCondition;
condition
: WS* criteria WS* EOF
: WS* (criteria|Always) WS* EOF
;
criteria
@ -12,6 +12,10 @@ criteria
| LParens WS* criteria WS* RParens #ParensCriteria
;
Always
: 'always'
;
Contains
: 'contains'
;

View File

@ -0,0 +1,154 @@
package io.onedev.server.ci.job.retry.condition;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import io.onedev.commons.codeassist.AntlrUtils;
import io.onedev.commons.codeassist.FenceAware;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.OneException;
import io.onedev.server.ci.job.retry.condition.RetryConditionParser.AndCriteriaContext;
import io.onedev.server.ci.job.retry.condition.RetryConditionParser.ConditionContext;
import io.onedev.server.ci.job.retry.condition.RetryConditionParser.CriteriaContext;
import io.onedev.server.ci.job.retry.condition.RetryConditionParser.FieldOperatorValueCriteriaContext;
import io.onedev.server.ci.job.retry.condition.RetryConditionParser.NotCriteriaContext;
import io.onedev.server.ci.job.retry.condition.RetryConditionParser.OrCriteriaContext;
import io.onedev.server.ci.job.retry.condition.RetryConditionParser.ParensCriteriaContext;
import io.onedev.server.model.Build;
import io.onedev.server.util.criteria.AndCriteria;
import io.onedev.server.util.criteria.NotCriteria;
import io.onedev.server.util.criteria.OrCriteria;
public class RetryCondition implements Predicate<Build> {
public static final String FIELD_LOG = "Log";
public static final String FIELD_ERROR_MESSAGE = "Error Message";
private final Predicate<Build> criteria;
public RetryCondition(Predicate<Build> criteria) {
this.criteria = criteria;
}
private static String getValue(String token) {
return StringUtils.unescape(FenceAware.unfence(token));
}
public boolean test(Build build) {
return criteria.test(build);
}
public static RetryCondition parse(String conditionString) {
CharStream is = CharStreams.fromString(conditionString);
RetryConditionLexer lexer = new RetryConditionLexer(is);
lexer.removeErrorListeners();
lexer.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line,
int charPositionInLine, String msg, RecognitionException e) {
throw new OneException("Malformed condition syntax", e);
}
});
CommonTokenStream tokens = new CommonTokenStream(lexer);
RetryConditionParser parser = new RetryConditionParser(tokens);
parser.removeErrorListeners();
parser.setErrorHandler(new BailErrorStrategy());
ConditionContext conditionContext;
try {
conditionContext = parser.condition();
} catch (Exception e) {
if (e instanceof OneException)
throw e;
else
throw new OneException("Malformed condition syntax", e);
}
Predicate<Build> criteria;
if (conditionContext.Always() != null) {
criteria = new AlwaysCriteria();
} else {
criteria = new RetryConditionBaseVisitor<Predicate<Build>>() {
@Override
public Predicate<Build> visitParensCriteria(ParensCriteriaContext ctx) {
return visit(ctx.criteria());
}
@Override
public Predicate<Build> visitFieldOperatorValueCriteria(FieldOperatorValueCriteriaContext ctx) {
String fieldName = getValue(ctx.Quoted(0).getText());
String fieldValue = getValue(ctx.Quoted(1).getText());
int operator = ctx.operator.getType();
checkField(fieldName, operator);
switch (operator) {
case RetryConditionLexer.Contains:
switch (fieldName) {
case FIELD_LOG:
return new LogCriteria(fieldValue);
case FIELD_ERROR_MESSAGE:
return new ErrorMessageCriteria(fieldValue);
default:
throw new IllegalStateException();
}
default:
throw new IllegalStateException();
}
}
@Override
public Predicate<Build> visitOrCriteria(OrCriteriaContext ctx) {
List<Predicate<Build>> childCriterias = new ArrayList<>();
for (CriteriaContext childCtx: ctx.criteria())
childCriterias.add(visit(childCtx));
return new OrCriteria<Build>(childCriterias);
}
@Override
public Predicate<Build> visitAndCriteria(AndCriteriaContext ctx) {
List<Predicate<Build>> childCriterias = new ArrayList<>();
for (CriteriaContext childCtx: ctx.criteria())
childCriterias.add(visit(childCtx));
return new AndCriteria<Build>(childCriterias);
}
@Override
public Predicate<Build> visitNotCriteria(NotCriteriaContext ctx) {
return new NotCriteria<Build>(visit(ctx.criteria()));
}
}.visit(conditionContext.criteria());
}
return new RetryCondition(criteria);
}
public static void checkField(String fieldName, int operator) {
if (!fieldName.equals(FIELD_ERROR_MESSAGE) && !fieldName.equals(FIELD_LOG))
throw new OneException("Field not found: " + fieldName);
switch (operator) {
case RetryConditionLexer.Contains:
if (!fieldName.equals(FIELD_LOG) && !fieldName.equals(FIELD_ERROR_MESSAGE))
throw newOperatorException(fieldName, operator);
break;
}
}
private static OneException newOperatorException(String fieldName, int operator) {
return new OneException("Field '" + fieldName + "' is not applicable for operator '"
+ AntlrUtils.getLexerRuleName(RetryConditionLexer.ruleNames, operator) + "'");
}
}

View File

@ -26,7 +26,7 @@ public abstract class JobTrigger implements Serializable {
private List<JobParam> params = new ArrayList<>();
@Editable(name="Trigger Parameters", order=1000)
@Editable(name="Job Parameters", order=1000)
@ParamSpecProvider("getParamSpecs")
@OmitName
@Valid

View File

@ -21,7 +21,7 @@ public interface BuildManager extends EntityManager<Build> {
@Nullable
Build find(Project project, long number);
Build findStreamlinePrev(Build build, @Nullable Build.Status status);
Build findStreamPrevious(Build build, @Nullable Build.Status status);
Collection<Build> query(Project project, ObjectId commitId, @Nullable String jobName, Map<String, List<String>> params);

View File

@ -399,7 +399,7 @@ public class DefaultBuildManager extends AbstractEntityManager<Build> implements
@Sessional
@Override
public Build findStreamlinePrev(Build build, Status status) {
public Build findStreamPrevious(Build build, Status status) {
CriteriaBuilder builder = getSession().getCriteriaBuilder();
CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);
Root<Build> root = query.from(Build.class);

View File

@ -57,7 +57,7 @@ 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.retry.condition.RetryCondition;
import io.onedev.server.entitymanager.BuildManager;
import io.onedev.server.git.GitUtils;
import io.onedev.server.git.RefInfo;
@ -188,10 +188,14 @@ public class Build extends AbstractEntity implements Referenceable {
private transient Collection<Long> fixedIssueNumbers;
private transient Map<Build.Status, Collection<RevCommit>> commitsCache;
private transient CISpec ciSpec;
private transient Job job;
private transient Map<Build.Status, Build> streamPreviousCache = new HashMap<>();
public Project getProject() {
return project;
}
@ -346,7 +350,7 @@ public class Build extends AbstractEntity implements Referenceable {
willRetryNow = getStatus() == Build.Status.FAILED
&& retry != null
&& getRetried() < retry.getMaxRetries()
&& RetryCondition.parse(retry.getRetryCondition()).satisfied(this);
&& RetryCondition.parse(retry.getCondition()).test(this);
}
return willRetryNow;
}
@ -423,7 +427,7 @@ public class Build extends AbstractEntity implements Referenceable {
public Collection<Long> getFixedIssueNumbers() {
if (fixedIssueNumbers == null) {
fixedIssueNumbers = new HashSet<>();
Build prevBuild = OneDev.getInstance(BuildManager.class).findStreamlinePrev(this, null);
Build prevBuild = getStreamPrevious(null);
if (prevBuild != null) {
Repository repository = project.getRepository();
try (RevWalk revWalk = new RevWalk(repository)) {
@ -441,6 +445,30 @@ public class Build extends AbstractEntity implements Referenceable {
return fixedIssueNumbers;
}
public Collection<RevCommit> getCommits(@Nullable Build.Status sincePrevStatus) {
if (commitsCache == null)
commitsCache = new HashMap<>();
if (!commitsCache.containsKey(sincePrevStatus)) {
Collection<RevCommit> commits = new ArrayList<>();
Build prevBuild = getStreamPrevious(sincePrevStatus);
if (prevBuild != null) {
Repository repository = project.getRepository();
try (RevWalk revWalk = new RevWalk(repository)) {
revWalk.markStart(revWalk.parseCommit(ObjectId.fromString(getCommitHash())));
revWalk.markUninteresting(revWalk.parseCommit(prevBuild.getCommitId()));
RevCommit commit;
while ((commit = revWalk.next()) != null)
commits.add(commit);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
commitsCache.put(sincePrevStatus, commits);
}
return commitsCache.get(sincePrevStatus);
}
public Map<String, Input> getParamInputs() {
Map<String, Input> inputs = new LinkedHashMap<>();
List<BuildParam> params = new ArrayList<>(getParams());
@ -598,6 +626,15 @@ public class Build extends AbstractEntity implements Referenceable {
});
}
@Nullable
public Build getStreamPrevious(@Nullable Status status) {
if (streamPreviousCache == null)
streamPreviousCache = new HashMap<>();
if (!streamPreviousCache.containsKey(status))
streamPreviousCache.put(status, OneDev.getInstance(BuildManager.class).findStreamPrevious(this, status));
return streamPreviousCache.get(status);
}
public void retrieveArtifacts(Build dependency, String artifacts, File workspaceDir) {
LockUtils.read(dependency.getArtifactsLockKey(), new Callable<Void>() {

View File

@ -44,6 +44,20 @@ public class BuildNotificationManager {
}
}
public void notify(Build build, Collection<String> emails) {
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(emails, subject, body.toString());
}
@Sessional
@Listen
public void on(BuildEvent event) {
@ -70,18 +84,7 @@ public class BuildNotificationManager {
}
}
}
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());
notify(build, notifyEmails);
}
}

View File

@ -32,7 +32,7 @@ public class AssociatedWithPullRequestCriteria extends EntityCriteria<Build> {
@Override
public boolean matches(Build build, User user) {
return build.getPullRequestBuilds().stream().anyMatch(it -> it.getRequest().equals(value) && it.isRequired());
return build.getPullRequestBuilds().stream().anyMatch(it -> it.getRequest().equals(value));
}
@Override

View File

@ -0,0 +1,37 @@
package io.onedev.server.search.entity.build;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import io.onedev.server.model.Build;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.search.entity.EntityCriteria;
import io.onedev.server.util.BuildConstants;
public class AssociatedWithPullRequestsCriteria extends EntityCriteria<Build> {
private static final long serialVersionUID = 1L;
@Override
public Predicate getPredicate(Project project, Root<Build> root, CriteriaBuilder builder, User user) {
return builder.isNotEmpty(root.get(BuildConstants.ATTR_PULL_REQUEST_BUILDS));
}
@Override
public boolean matches(Build build, User user) {
return !build.getPullRequestBuilds().isEmpty();
}
@Override
public boolean needsLogin() {
return false;
}
@Override
public String toString() {
return BuildQuery.getRuleName(BuildQueryLexer.AssociatedWithPullRequests);
}
}

View File

@ -7,7 +7,7 @@ query
;
criteria
: operator=(Successful|Failed|InError|Cancelled|Running|Waiting|Pending|TimedOut|SubmittedByMe|CancelledByMe|WillRetry) #OperatorCriteria
: operator=(Successful|Failed|InError|Cancelled|Running|Waiting|Pending|TimedOut|SubmittedByMe|CancelledByMe|WillRetry|AssociatedWithPullRequests|RequiredByPullRequests) #OperatorCriteria
| operator=(FixedIssue|SubmittedBy|CancelledBy|DependsOn|DependenciesOf|AssociatedWithPullRequest|RequiredByPullRequest) WS+ criteriaValue=Quoted #OperatorValueCriteria
| criteriaField=Quoted WS+ operator=(Is|IsGreaterThan|IsLessThan|IsBefore|IsAfter) WS+ criteriaValue=Quoted #FieldOperatorValueCriteria
| criteria WS+ And WS+ criteria #AndCriteria
@ -96,6 +96,14 @@ RequiredByPullRequest
: 'required' WS+ 'by' WS+ 'pull' WS+ 'request'
;
AssociatedWithPullRequests
: 'associated' WS+ 'with' WS+ 'pull' WS+ 'requests'
;
RequiredByPullRequests
: 'required' WS+ 'by' WS+ 'pull' WS+ 'requests'
;
OrderBy
: 'order by'
;

View File

@ -123,6 +123,10 @@ public class BuildQuery extends EntityQuery<Build> {
return new CancelledByMeCriteria();
case BuildQueryLexer.WillRetry:
return new WillRetryCriteria();
case BuildQueryLexer.AssociatedWithPullRequests:
return new AssociatedWithPullRequestsCriteria();
case BuildQueryLexer.RequiredByPullRequests:
return new RequiredByPullRequestsCriteria();
default:
throw new OneException("Unexpected operator: " + ctx.operator.getText());
}

View File

@ -0,0 +1,41 @@
package io.onedev.server.search.entity.build;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import io.onedev.server.model.Build;
import io.onedev.server.model.Project;
import io.onedev.server.model.PullRequestBuild;
import io.onedev.server.model.User;
import io.onedev.server.search.entity.EntityCriteria;
import io.onedev.server.util.BuildConstants;
public class RequiredByPullRequestsCriteria extends EntityCriteria<Build> {
private static final long serialVersionUID = 1L;
@Override
public Predicate getPredicate(Project project, Root<Build> root, CriteriaBuilder builder, User user) {
From<?, ?> join = root.join(BuildConstants.ATTR_PULL_REQUEST_BUILDS, JoinType.LEFT);
return builder.equal(join.get(PullRequestBuild.ATTR_REQUIRED), true);
}
@Override
public boolean matches(Build build, User user) {
return build.getPullRequestBuilds().stream().anyMatch(it->it.isRequired());
}
@Override
public boolean needsLogin() {
return false;
}
@Override
public String toString() {
return BuildQuery.getRuleName(BuildQueryLexer.RequiredByPullRequests);
}
}

View File

@ -0,0 +1,23 @@
package io.onedev.server.util.criteria;
import java.util.List;
import java.util.function.Predicate;
public class AndCriteria<T> implements Predicate<T> {
private final List<? extends Predicate<T>> predicates;
public AndCriteria(List<? extends Predicate<T>> predicates) {
this.predicates = predicates;
}
@Override
public boolean test(T t) {
for (Predicate<T> predicate: predicates) {
if (!predicate.test(t))
return false;
}
return true;
}
}

View File

@ -0,0 +1,18 @@
package io.onedev.server.util.criteria;
import java.util.function.Predicate;
public class NotCriteria<T> implements Predicate<T> {
private final Predicate<T> predicate;
public NotCriteria(Predicate<T> predicate) {
this.predicate = predicate;
}
@Override
public boolean test(T t) {
return !predicate.test(t);
}
}

View File

@ -0,0 +1,23 @@
package io.onedev.server.util.criteria;
import java.util.List;
import java.util.function.Predicate;
public class OrCriteria<T> implements Predicate<T> {
private final List<? extends Predicate<T>> predicates;
public OrCriteria(List<? extends Predicate<T>> predicates) {
this.predicates = predicates;
}
@Override
public boolean test(T t) {
for (Predicate<T> predicate: predicates) {
if (predicate.test(t))
return true;
}
return false;
}
}

View File

@ -1,6 +1,6 @@
grammar ReviewRequirement;
requirement: ' '* criteria (' '+ criteria)* ' '* EOF;
requirement: WS* criteria (WS+ 'and' WS+ criteria)* WS* EOF;
criteria: userCriteria | groupCriteria;
@ -14,4 +14,6 @@ DIGIT: [1-9][0-9]*;
USER: 'user';
GROUP: 'group';
WS: ' ';
Value: '(' ('\\'.|~[\\()])+? ')';

View File

@ -0,0 +1,42 @@
package io.onedev.server.util.validation;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.web.editable.annotation.ActionCondition;
public class ActionConditionValidator implements ConstraintValidator<ActionCondition, String> {
private String message;
@Override
public void initialize(ActionCondition constaintAnnotation) {
message = constaintAnnotation.message();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintContext) {
if (value == null) {
return true;
} else {
try {
io.onedev.server.ci.job.action.condition.ActionCondition.parse(value);
return true;
} catch (Exception e) {
constraintContext.disableDefaultConstraintViolation();
String message = this.message;
if (message.length() == 0) {
if (StringUtils.isNotBlank(e.getMessage()))
message = e.getMessage();
else
message = "Malformed action condition";
}
constraintContext.buildConstraintViolationWithTemplate(message).addConstraintViolation();
return false;
}
}
}
}

View File

@ -0,0 +1,41 @@
package io.onedev.server.util.validation;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.apache.commons.lang3.StringUtils;
import io.onedev.server.web.editable.annotation.NotificationReceiver;
public class NotificationReceiverValidator implements ConstraintValidator<NotificationReceiver, String> {
private String message;
@Override
public void initialize(NotificationReceiver constaintAnnotation) {
message = constaintAnnotation.message();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintContext) {
if (value == null) {
return true;
} else {
try {
io.onedev.server.ci.job.action.notificationreceiver.NotificationReceiver.fromString(value, null);
return true;
} catch (Exception e) {
constraintContext.disableDefaultConstraintViolation();
String message = this.message;
if (message.length() == 0) {
if (StringUtils.isNotBlank(e.getMessage()))
message = e.getMessage();
else
message = "Malformed notification receiver";
}
constraintContext.buildConstraintViolationWithTemplate(message).addConstraintViolation();
return false;
}
}
}
}

View File

@ -21,7 +21,7 @@ public class RetryConditionValidator implements ConstraintValidator<RetryConditi
return true;
} else {
try {
io.onedev.server.ci.job.retry.RetryCondition.parse(value);
io.onedev.server.ci.job.retry.condition.RetryCondition.parse(value);
return true;
} catch (Exception e) {
constraintContext.disableDefaultConstraintViolation();

View File

@ -0,0 +1,74 @@
package io.onedev.server.web.behavior;
import java.util.ArrayList;
import java.util.List;
import io.onedev.commons.codeassist.FenceAware;
import io.onedev.commons.codeassist.InputSuggestion;
import io.onedev.commons.codeassist.grammar.LexerRuleRefElementSpec;
import io.onedev.commons.codeassist.parser.TerminalExpect;
import io.onedev.server.ci.job.Job;
import io.onedev.server.ci.job.JobAware;
import io.onedev.server.ci.job.action.condition.ActionConditionParser;
import io.onedev.server.git.GitUtils;
import io.onedev.server.git.RefInfo;
import io.onedev.server.model.Project;
import io.onedev.server.web.behavior.inputassist.ANTLRAssistBehavior;
import io.onedev.server.web.util.SuggestionUtils;
@SuppressWarnings("serial")
public class ActionConditionBehavior extends ANTLRAssistBehavior {
public ActionConditionBehavior() {
super(ActionConditionParser.class, "condition", false);
}
@Override
protected List<InputSuggestion> suggest(TerminalExpect terminalExpect) {
if (terminalExpect.getElementSpec() instanceof LexerRuleRefElementSpec) {
LexerRuleRefElementSpec spec = (LexerRuleRefElementSpec) terminalExpect.getElementSpec();
if (spec.getRuleName().equals("Quoted")) {
return new FenceAware(codeAssist.getGrammar(), '"', '"') {
@Override
protected List<InputSuggestion> match(String matchWith) {
if ("criteriaField".equals(spec.getLabel())) {
JobAware jobAware = getComponent().findParent(JobAware.class);
Job job = jobAware.getJob();
return SuggestionUtils.suggest(new ArrayList<>(job.getParamSpecMap().keySet()), matchWith);
} else if ("criteriaValue".equals(spec.getLabel())) {
List<String> branchNames = new ArrayList<>();
for (RefInfo refInfo: Project.get().getBranches())
branchNames.add(GitUtils.ref2branch(refInfo.getRef().getName()));
return SuggestionUtils.suggest(branchNames, matchWith);
} else {
return null;
}
}
@Override
protected String getFencingDescription() {
return "quote as literal value";
}
}.suggest(terminalExpect);
}
}
return null;
}
@Override
protected List<String> getHints(TerminalExpect terminalExpect) {
List<String> hints = new ArrayList<>();
if (terminalExpect.getElementSpec() instanceof LexerRuleRefElementSpec) {
LexerRuleRefElementSpec spec = (LexerRuleRefElementSpec) terminalExpect.getElementSpec();
if ("criteriaValue".equals(spec.getLabel())) {
String unmatched = terminalExpect.getUnmatchedText();
if (unmatched.indexOf('"') == unmatched.lastIndexOf('"')) // only when we input criteria value
hints.add("Use * for wildcard match");
}
}
return hints;
}
}

View File

@ -0,0 +1,77 @@
package io.onedev.server.web.behavior;
import java.util.List;
import org.apache.wicket.Component;
import org.apache.wicket.model.IModel;
import com.google.common.base.Optional;
import io.onedev.commons.codeassist.FenceAware;
import io.onedev.commons.codeassist.InputSuggestion;
import io.onedev.commons.codeassist.grammar.LexerRuleRefElementSpec;
import io.onedev.commons.codeassist.parser.ParseExpect;
import io.onedev.commons.codeassist.parser.TerminalExpect;
import io.onedev.server.ci.job.action.notificationreceiver.NotificationReceiverParser;
import io.onedev.server.model.Project;
import io.onedev.server.security.permission.ProjectPrivilege;
import io.onedev.server.web.behavior.inputassist.ANTLRAssistBehavior;
import io.onedev.server.web.util.SuggestionUtils;
@SuppressWarnings("serial")
public class NotificationReceiverBehavior extends ANTLRAssistBehavior {
private final IModel<Project> projectModel;
public NotificationReceiverBehavior(IModel<Project> projectModel) {
super(NotificationReceiverParser.class, "receiver", false);
this.projectModel = projectModel;
}
@Override
public void detach(Component component) {
super.detach(component);
projectModel.detach();
}
@Override
protected List<InputSuggestion> suggest(TerminalExpect terminalExpect) {
if (terminalExpect.getElementSpec() instanceof LexerRuleRefElementSpec) {
LexerRuleRefElementSpec spec = (LexerRuleRefElementSpec) terminalExpect.getElementSpec();
if (spec.getRuleName().equals("Value")) {
return new FenceAware(codeAssist.getGrammar(), '(', ')') {
@Override
protected List<InputSuggestion> match(String matchWith) {
Project project = projectModel.getObject();
if (terminalExpect.findExpectByRule("userCriteria") != null)
return SuggestionUtils.suggestUsers(project, ProjectPrivilege.CODE_READ, matchWith);
else
return SuggestionUtils.suggestGroups(matchWith);
}
@Override
protected String getFencingDescription() {
return "value needs to be enclosed in brackets";
}
}.suggest(terminalExpect);
}
}
return null;
}
@Override
protected Optional<String> describe(ParseExpect parseExpect, String suggestedLiteral) {
String description;
switch (suggestedLiteral) {
case " ":
description = "space";
break;
default:
description = null;
}
return Optional.fromNullable(description);
}
}

View File

@ -9,8 +9,8 @@ import io.onedev.commons.codeassist.FenceAware;
import io.onedev.commons.codeassist.InputSuggestion;
import io.onedev.commons.codeassist.grammar.LexerRuleRefElementSpec;
import io.onedev.commons.codeassist.parser.TerminalExpect;
import io.onedev.server.ci.job.retry.RetryCondition;
import io.onedev.server.ci.job.retry.RetryConditionParser;
import io.onedev.server.ci.job.retry.condition.RetryConditionParser;
import io.onedev.server.ci.job.retry.condition.RetryCondition;
import io.onedev.server.web.behavior.inputassist.ANTLRAssistBehavior;
import io.onedev.server.web.util.SuggestionUtils;

View File

@ -408,7 +408,7 @@ public final class Settings implements Serializable {
if (propertyDescriptor.isPropertyRequired()) {
Method getter = propertyDescriptor.getPropertyGetter();
if (getter.getAnnotation(OmitName.class) != null)
setPlaceholder("Select " + propertyDescriptor.getDisplayName(component).toLowerCase() + "...");
setPlaceholder("Select " + propertyDescriptor.getDisplayName().toLowerCase() + "...");
} else if (propertyDescriptor.getNameOfEmptyValue() != null) {
setPlaceholder(propertyDescriptor.getNameOfEmptyValue());
}

View File

@ -0,0 +1,67 @@
package io.onedev.server.web.editable;
import java.lang.reflect.Method;
import org.apache.wicket.Component;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.IModel;
import io.onedev.server.web.behavior.ActionConditionBehavior;
import io.onedev.server.web.behavior.inputassist.InputAssistBehavior;
import io.onedev.server.web.editable.annotation.ActionCondition;
import io.onedev.server.web.editable.string.StringPropertyEditor;
@SuppressWarnings("serial")
public class ActionConditionEditSupport implements EditSupport {
@Override
public PropertyContext<?> getEditContext(PropertyDescriptor descriptor) {
Method propertyGetter = descriptor.getPropertyGetter();
if (propertyGetter.getAnnotation(ActionCondition.class) != null) {
if (propertyGetter.getReturnType() != String.class) {
throw new RuntimeException("Annotation 'ActionCondition' should be applied to property "
+ "with type 'String'");
}
return new PropertyContext<String>(descriptor) {
@Override
public PropertyViewer renderForView(String componentId, final IModel<String> model) {
return new PropertyViewer(componentId, descriptor) {
@Override
protected Component newContent(String id, PropertyDescriptor propertyDescriptor) {
String refMatch = model.getObject();
if (refMatch != null) {
return new Label(id, refMatch);
} else {
return new EmptyValueLabel(id, propertyDescriptor.getPropertyGetter());
}
}
};
}
@Override
public PropertyEditor<String> renderForEdit(String componentId, IModel<String> model) {
return new StringPropertyEditor(componentId, descriptor, model) {
@Override
protected InputAssistBehavior getInputAssistBehavior() {
return new ActionConditionBehavior();
}
};
}
};
} else {
return null;
}
}
@Override
public int getPriority() {
return DEFAULT_PRIORITY;
}
}

View File

@ -136,7 +136,7 @@ public class BeanEditor extends ValueEditor<Serializable> {
nameContainer = this;
valueContainer = this;
}
Label nameLabel = new Label("name", property.getDescriptor().getDisplayName(this));
Label nameLabel = new Label("name", property.getDescriptor().getDisplayName());
nameContainer.add(nameLabel);
OmitName omitName = property.getPropertyGetter().getAnnotation(OmitName.class);

View File

@ -91,7 +91,7 @@ public class BeanViewer extends Panel {
WebMarkupContainer valueTd = new WebMarkupContainer("value");
propertyItem.add(valueTd);
String displayName = property.getDisplayName(this);
String displayName = property.getDisplayName();
Component content = new Label("content", displayName);
nameTd.add(content);
OmitName omitName = propertyGetter.getAnnotation(OmitName.class);

View File

@ -0,0 +1,64 @@
package io.onedev.server.web.editable;
import java.lang.reflect.Method;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import io.onedev.server.model.Project;
import io.onedev.server.web.behavior.NotificationReceiverBehavior;
import io.onedev.server.web.behavior.inputassist.InputAssistBehavior;
import io.onedev.server.web.editable.annotation.NotificationReceiver;
import io.onedev.server.web.editable.string.StringPropertyEditor;
import io.onedev.server.web.editable.string.StringPropertyViewer;
import io.onedev.server.web.page.project.ProjectPage;
@SuppressWarnings("serial")
public class NotificationReceiverEditSupport implements EditSupport {
@Override
public PropertyContext<?> getEditContext(PropertyDescriptor descriptor) {
Method propertyGetter = descriptor.getPropertyGetter();
if (propertyGetter.getAnnotation(NotificationReceiver.class) != null) {
if (propertyGetter.getReturnType() != String.class) {
throw new RuntimeException("Annotation 'NotificationReceiver' should be applied to property "
+ "of type 'String'.");
}
return new PropertyContext<String>(descriptor) {
@Override
public PropertyViewer renderForView(String componentId, IModel<String> model) {
return new StringPropertyViewer(componentId, descriptor, model.getObject());
}
@Override
public PropertyEditor<String> renderForEdit(String componentId, IModel<String> model) {
return new StringPropertyEditor(componentId, descriptor, model) {
@Override
protected InputAssistBehavior getInputAssistBehavior() {
return new NotificationReceiverBehavior(new AbstractReadOnlyModel<Project>() {
@Override
public Project getObject() {
return ((ProjectPage) getPage()).getProject();
}
});
}
};
}
};
} else {
return null;
}
}
@Override
public int getPriority() {
return DEFAULT_PRIORITY;
}
}

View File

@ -110,10 +110,6 @@ public abstract class PropertyContext<T> implements Serializable {
return registry.getPropertyEditContext(propertyDescriptor);
}
public String getDisplayName(Component component) {
return descriptor.getDisplayName(component);
}
public String getDisplayName() {
return descriptor.getDisplayName();
}

View File

@ -169,11 +169,6 @@ public class PropertyDescriptor implements Serializable {
return EditableUtils.getDisplayName(getPropertyGetter());
}
public String getDisplayName(Component component) {
String displayName = getDisplayName();
return Application.get().getResourceSettings().getLocalizer().getString(displayName, component, displayName);
}
public String getDescription() {
return EditableUtils.getDescription(getPropertyGetter());
}

View File

@ -1,4 +1,4 @@
package io.onedev.server.web.editable.retrycondition;
package io.onedev.server.web.editable;
import java.lang.reflect.Method;
@ -6,13 +6,10 @@ import org.apache.wicket.Component;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.IModel;
import io.onedev.server.web.editable.EditSupport;
import io.onedev.server.web.editable.EmptyValueLabel;
import io.onedev.server.web.editable.PropertyContext;
import io.onedev.server.web.editable.PropertyDescriptor;
import io.onedev.server.web.editable.PropertyEditor;
import io.onedev.server.web.editable.PropertyViewer;
import io.onedev.server.web.behavior.RetryConditionBehavior;
import io.onedev.server.web.behavior.inputassist.InputAssistBehavior;
import io.onedev.server.web.editable.annotation.RetryCondition;
import io.onedev.server.web.editable.string.StringPropertyEditor;
@SuppressWarnings("serial")
public class RetryConditionEditSupport implements EditSupport {
@ -46,7 +43,14 @@ public class RetryConditionEditSupport implements EditSupport {
@Override
public PropertyEditor<String> renderForEdit(String componentId, IModel<String> model) {
return new RetryConditionEditor(componentId, descriptor, model);
return new StringPropertyEditor(componentId, descriptor, model) {
@Override
protected InputAssistBehavior getInputAssistBehavior() {
return new RetryConditionBehavior();
}
};
}
};

View File

@ -0,0 +1,23 @@
package io.onedev.server.web.editable.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import io.onedev.server.util.validation.ActionConditionValidator;
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=ActionConditionValidator.class)
public @interface ActionCondition {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -0,0 +1,24 @@
package io.onedev.server.web.editable.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import io.onedev.server.util.validation.NotificationReceiverValidator;
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=NotificationReceiverValidator.class)
public @interface NotificationReceiver {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@ -20,6 +20,4 @@ public @interface RetryCondition {
Class<? extends Payload>[] payload() default {};
boolean noLoginSupport() default false;
}

View File

@ -33,7 +33,7 @@ public class BooleanPropertyEditor extends PropertyEditor<Boolean> {
}
});
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
}
@Override

View File

@ -35,7 +35,7 @@ public class NullableBooleanPropertyEditor extends PropertyEditor<Boolean> {
stringValue = null;
}
input = new DropDownChoice<String>("input", Model.of(stringValue), Lists.newArrayList("yes", "no"));
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
input.setNullValid(true);

View File

@ -57,7 +57,7 @@ public class BranchMultiChoiceEditor extends PropertyEditor<List<String>> {
}
};
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
input.setRequired(descriptor.isPropertyRequired());
input.add(new AjaxFormComponentUpdatingBehavior("change"){

View File

@ -51,7 +51,7 @@ public class BranchSingleChoiceEditor extends PropertyEditor<String> {
// add this to control allowClear flag of select2
input.setRequired(descriptor.isPropertyRequired());
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
input.add(new AjaxFormComponentUpdatingBehavior("change"){
@Override

View File

@ -90,7 +90,7 @@ public class BuildChoiceEditor extends PropertyEditor<Long> {
// add this to control allowClear flag of select2
input.setRequired(descriptor.isPropertyRequired());
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
input.add(new AjaxFormComponentUpdatingBehavior("change"){

View File

@ -41,7 +41,7 @@ public class BuildQueryEditor extends PropertyEditor<String> {
}, noLoginSupport));
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
add(input);
input.add(new OnTypingDoneBehavior() {

View File

@ -90,7 +90,7 @@ public class MultiChoiceEditor extends PropertyEditor<List<String>> {
}
};
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
input.setRequired(descriptor.isPropertyRequired());
input.add(new AjaxFormComponentUpdatingBehavior("change"){

View File

@ -86,7 +86,7 @@ public class SingleChoiceEditor extends PropertyEditor<String> {
};
// add this to control allowClear flag of select2
input.setRequired(descriptor.isPropertyRequired());
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
input.add(new AjaxFormComponentUpdatingBehavior("change"){

View File

@ -53,7 +53,7 @@ public class CodePropertyEditor extends PropertyEditor<List<String>> {
add(container);
container.add(input = new TextArea<String>("input", Model.of(StringUtils.join(getModelObject(), "\n"))));
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
input.setOutputMarkupId(true);
input.add(new OnTypingDoneBehavior() {

View File

@ -37,7 +37,7 @@ public class CodeCommentQueryEditor extends PropertyEditor<String> {
}));
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
add(input);
input.add(new OnTypingDoneBehavior() {

View File

@ -27,7 +27,7 @@ public class ColorPropertyEditor extends PropertyEditor<String> {
input = new ColorPicker("input", Model.of(getModelObject()), !getDescriptor().isPropertyRequired());
add(input);
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
input.add(new OnTypingDoneBehavior() {

View File

@ -37,7 +37,7 @@ public class CommitQueryEditor extends PropertyEditor<String> {
}));
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
add(input);
input.add(new OnTypingDoneBehavior() {

View File

@ -41,7 +41,7 @@ public class DatePropertyEditor extends PropertyEditor<Date> {
if (propertyGetter.getAnnotation(OmitName.class) != null)
input.add(AttributeModifier.replace("placeholder", EditableUtils.getDisplayName(propertyGetter)));
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
add(input);
input.add(new OnTypingDoneBehavior() {

View File

@ -67,7 +67,7 @@ public class EnumPropertyEditor extends PropertyEditor<Enum<?>> {
};
// add this to control allowClear flag of select2
input.setRequired(descriptor.isPropertyRequired());
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
input.add(new AjaxFormComponentUpdatingBehavior("change") {

View File

@ -69,7 +69,7 @@ public class EnumListPropertyEditor extends PropertyEditor<List<Enum<?>>> {
};
input.setRequired(descriptor.isPropertyRequired());
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
input.add(new AjaxFormComponentUpdatingBehavior("change"){

View File

@ -72,7 +72,7 @@ public class GroupMultiChoiceEditor extends PropertyEditor<Collection<String>> {
};
input.setConvertEmptyInputStringToNull(true);
input.setRequired(descriptor.isPropertyRequired());
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
input.add(new AjaxFormComponentUpdatingBehavior("change"){

View File

@ -74,7 +74,7 @@ public class GroupSingleChoiceEditor extends PropertyEditor<String> {
// add this to control allowClear flag of select2
input.setRequired(descriptor.isPropertyRequired());
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
input.add(new AjaxFormComponentUpdatingBehavior("change"){
@Override

View File

@ -62,7 +62,7 @@ public class IssueChoiceEditor extends PropertyEditor<Long> {
// add this to control allowClear flag of select2
input.setRequired(descriptor.isPropertyRequired());
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
input.add(new AjaxFormComponentUpdatingBehavior("change"){

View File

@ -40,7 +40,7 @@ public class IssueQueryEditor extends PropertyEditor<String> {
}));
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
add(input);
input.add(new OnTypingDoneBehavior() {

View File

@ -34,7 +34,7 @@ public class JobMultiChoiceEditor extends PropertyEditor<List<String>> {
input = new JobMultiChoice("input", Model.of(jobNames));
input.setConvertEmptyInputStringToNull(true);
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
add(input);
}

View File

@ -24,7 +24,7 @@ public class JobSingleChoiceEditor extends PropertyEditor<String> {
input = new JobSingleChoice("input", Model.of(getModelObject()));
input.setConvertEmptyInputStringToNull(true);
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
input.setLabel(Model.of(getDescriptor().getDisplayName()));
add(input);
}

View File

@ -0,0 +1,27 @@
package io.onedev.server.web.editable.job.postbuildaction;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import io.onedev.server.ci.job.action.PostBuildAction;
import io.onedev.server.web.editable.annotation.Editable;
@Editable
public class ActionBean implements Serializable {
private static final long serialVersionUID = 1L;
private PostBuildAction action;
@Editable(name="Type", order=100)
@NotNull(message="may not be empty")
public PostBuildAction getAction() {
return action;
}
public void setAction(PostBuildAction action) {
this.action = action;
}
}

View File

@ -0,0 +1,13 @@
package io.onedev.server.web.editable.job.postbuildaction;
import io.onedev.server.web.page.base.BaseDependentCssResourceReference;
public class ActionCssResourceReference extends BaseDependentCssResourceReference {
private static final long serialVersionUID = 1L;
public ActionCssResourceReference() {
super(ActionCssResourceReference.class, "action.css");
}
}

View File

@ -0,0 +1,17 @@
<wicket:panel>
<form wicket:id="form" class="post-build-action-edit leave-confirm">
<div class="modal-header">
<button wicket:id="close" type="button" class="close">&times;</button>
<h4 id="modal-title" class="modal-title">
Post Build Action
</h4>
</div>
<div class="modal-body">
<div wicket:id="editor"></div>
</div>
<div class="modal-footer">
<input wicket:id="save" type="submit" class="dirty-aware btn btn-primary" value="Save">
<a wicket:id="cancel" class="btn btn-default">Cancel</a>
</div>
</form>
</wicket:panel>

View File

@ -0,0 +1,113 @@
package io.onedev.server.web.editable.job.postbuildaction;
import java.util.List;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.request.cycle.RequestCycle;
import io.onedev.server.ci.CISpecAware;
import io.onedev.server.ci.job.JobAware;
import io.onedev.server.ci.job.action.PostBuildAction;
import io.onedev.server.web.ajaxlistener.ConfirmLeaveListener;
import io.onedev.server.web.editable.BeanContext;
import io.onedev.server.web.editable.BeanEditor;
@SuppressWarnings("serial")
abstract class ActionEditPanel extends Panel implements CISpecAware, JobAware {
private final List<PostBuildAction> actions;
private final int actionIndex;
public ActionEditPanel(String id, List<PostBuildAction> actions, int actionIndex) {
super(id);
this.actions = actions;
this.actionIndex = actionIndex;
}
@Override
protected void onInitialize() {
super.onInitialize();
ActionBean bean = new ActionBean();
if (actionIndex != -1)
bean.setAction(actions.get(actionIndex));
Form<?> form = new Form<Void>("form") {
@Override
protected void onError() {
super.onError();
RequestCycle.get().find(AjaxRequestTarget.class).add(this);
}
};
form.add(new AjaxLink<Void>("close") {
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getAjaxCallListeners().add(new ConfirmLeaveListener(ActionEditPanel.this));
}
@Override
public void onClick(AjaxRequestTarget target) {
onCancel(target);
}
});
BeanEditor editor = BeanContext.edit("editor", bean);
form.add(editor);
form.add(new AjaxButton("save") {
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
super.onSubmit(target, form);
PostBuildAction action = bean.getAction();
if (editor.isValid()) {
if (actionIndex != -1) {
actions.set(actionIndex, action);
} else {
actions.add(action);
}
onSave(target);
} else {
target.add(form);
}
}
});
form.add(new AjaxLink<Void>("cancel") {
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getAjaxCallListeners().add(new ConfirmLeaveListener(ActionEditPanel.this));
}
@Override
public void onClick(AjaxRequestTarget target) {
onCancel(target);
}
});
form.setOutputMarkupId(true);
add(form);
}
protected abstract void onSave(AjaxRequestTarget target);
protected abstract void onCancel(AjaxRequestTarget target);
}

View File

@ -0,0 +1,12 @@
<wicket:panel>
<div class="action-list list">
<div class="head">
<a wicket:id="addNew" title="Add new" class="btn btn-default btn-xs"><i class="fa fa-plus"></i></a>
</div>
<table wicket:id="actions" class="table table-condensed table-hover"></table>
</div>
<wicket:fragment wicket:id="actionColumnFrag">
<a wicket:id="edit" title="Edit"><i class="fa fa-pencil"></i></a>
<a wicket:id="delete" title="Delete"><i class="fa fa-trash"></i></a>
</wicket:fragment>
</wicket:panel>

View File

@ -0,0 +1,258 @@
package io.onedev.server.web.editable.job.postbuildaction;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.event.IEvent;
import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable;
import org.apache.wicket.extensions.markup.html.repeater.data.table.HeadersToolbar;
import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
import org.apache.wicket.extensions.markup.html.repeater.data.table.NoRecordsToolbar;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.IDataProvider;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.util.convert.ConversionException;
import io.onedev.server.ci.CISpec;
import io.onedev.server.ci.CISpecAware;
import io.onedev.server.ci.job.Job;
import io.onedev.server.ci.job.JobAware;
import io.onedev.server.ci.job.action.PostBuildAction;
import io.onedev.server.web.behavior.sortable.SortBehavior;
import io.onedev.server.web.behavior.sortable.SortPosition;
import io.onedev.server.web.component.modal.ModalLink;
import io.onedev.server.web.component.modal.ModalPanel;
import io.onedev.server.web.editable.PropertyDescriptor;
import io.onedev.server.web.editable.PropertyEditor;
import io.onedev.server.web.editable.PropertyUpdating;
@SuppressWarnings("serial")
class ActionListEditPanel extends PropertyEditor<List<Serializable>> {
private final List<PostBuildAction> actions;
public ActionListEditPanel(String id, PropertyDescriptor propertyDescriptor, IModel<List<Serializable>> model) {
super(id, propertyDescriptor, model);
actions = new ArrayList<>();
for (Serializable each: model.getObject()) {
actions.add((PostBuildAction) each);
}
}
private Job getJob() {
JobAware jobAware = findParent(JobAware.class);
if (jobAware != null)
return jobAware.getJob();
else
return null;
}
@Override
protected void onInitialize() {
super.onInitialize();
add(new ModalLink("addNew") {
@Override
protected Component newContent(String id, ModalPanel modal) {
return new ActionEditPanel(id, actions, -1) {
@Override
protected void onCancel(AjaxRequestTarget target) {
modal.close();
}
@Override
protected void onSave(AjaxRequestTarget target) {
markFormDirty(target);
modal.close();
onPropertyUpdating(target);
target.add(ActionListEditPanel.this);
}
@Override
public Job getJob() {
return ActionListEditPanel.this.getJob();
}
@Override
public CISpec getCISpec() {
return ActionListEditPanel.this.getCISpec();
}
};
}
});
List<IColumn<PostBuildAction, Void>> columns = new ArrayList<>();
columns.add(new AbstractColumn<PostBuildAction, Void>(Model.of("")) {
@Override
public void populateItem(Item<ICellPopulator<PostBuildAction>> cellItem, String componentId, IModel<PostBuildAction> rowModel) {
cellItem.add(new Label(componentId, "<span class=\"drag-indicator fa fa-reorder\"></span>").setEscapeModelStrings(false));
}
@Override
public String getCssClass() {
return "minimum actions";
}
});
columns.add(new AbstractColumn<PostBuildAction, Void>(Model.of("Description")) {
@Override
public void populateItem(Item<ICellPopulator<PostBuildAction>> cellItem, String componentId, IModel<PostBuildAction> rowModel) {
cellItem.add(new Label(componentId, rowModel.getObject().getDescription()));
}
});
columns.add(new AbstractColumn<PostBuildAction, Void>(Model.of("Condition")) {
@Override
public void populateItem(Item<ICellPopulator<PostBuildAction>> cellItem, String componentId, IModel<PostBuildAction> rowModel) {
cellItem.add(new Label(componentId, rowModel.getObject().getCondition()));
}
});
columns.add(new AbstractColumn<PostBuildAction, Void>(Model.of("")) {
@Override
public void populateItem(Item<ICellPopulator<PostBuildAction>> cellItem, String componentId, IModel<PostBuildAction> rowModel) {
Fragment fragment = new Fragment(componentId, "actionColumnFrag", ActionListEditPanel.this);
fragment.add(new ModalLink("edit") {
@Override
protected Component newContent(String id, ModalPanel modal) {
return new ActionEditPanel(id, actions, cellItem.findParent(Item.class).getIndex()) {
@Override
protected void onCancel(AjaxRequestTarget target) {
modal.close();
}
@Override
protected void onSave(AjaxRequestTarget target) {
markFormDirty(target);
modal.close();
onPropertyUpdating(target);
target.add(ActionListEditPanel.this);
}
@Override
public Job getJob() {
return ActionListEditPanel.this.getJob();
}
@Override
public CISpec getCISpec() {
return ActionListEditPanel.this.getCISpec();
}
};
}
});
fragment.add(new AjaxLink<Void>("delete") {
@Override
public void onClick(AjaxRequestTarget target) {
markFormDirty(target);
actions.remove(rowModel.getObject());
onPropertyUpdating(target);
target.add(ActionListEditPanel.this);
}
});
cellItem.add(fragment);
}
@Override
public String getCssClass() {
return "actions";
}
});
IDataProvider<PostBuildAction> dataProvider = new ListDataProvider<PostBuildAction>() {
@Override
protected List<PostBuildAction> getData() {
return actions;
}
};
DataTable<PostBuildAction, Void> dataTable;
add(dataTable = new DataTable<PostBuildAction, Void>("actions", columns, dataProvider, Integer.MAX_VALUE));
dataTable.addTopToolbar(new HeadersToolbar<Void>(dataTable, null));
dataTable.addBottomToolbar(new NoRecordsToolbar(dataTable));
dataTable.add(new SortBehavior() {
@Override
protected void onSort(AjaxRequestTarget target, SortPosition from, SortPosition to) {
int fromIndex = from.getItemIndex();
int toIndex = to.getItemIndex();
if (fromIndex < toIndex) {
for (int i=0; i<toIndex-fromIndex; i++)
Collections.swap(actions, fromIndex+i, fromIndex+i+1);
} else {
for (int i=0; i<fromIndex-toIndex; i++)
Collections.swap(actions, fromIndex-i, fromIndex-i-1);
}
onPropertyUpdating(target);
target.add(ActionListEditPanel.this);
}
}.sortable("tbody"));
}
private CISpec getCISpec() {
CISpecAware ciSpecAware = findParent(CISpecAware.class);
if (ciSpecAware != null)
return ciSpecAware.getCISpec();
else
return null;
}
@Override
public void onEvent(IEvent<?> event) {
super.onEvent(event);
if (event.getPayload() instanceof PropertyUpdating) {
event.stop();
onPropertyUpdating(((PropertyUpdating)event.getPayload()).getHandler());
}
}
@Override
protected List<Serializable> convertInputToValue() throws ConversionException {
List<Serializable> value = new ArrayList<>();
for (PostBuildAction each: actions)
value.add(each);
return value;
}
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
response.render(CssHeaderItem.forReference(new ActionCssResourceReference()));
}
}

View File

@ -0,0 +1,60 @@
package io.onedev.server.web.editable.job.postbuildaction;
import java.io.Serializable;
import java.util.List;
import org.apache.wicket.Component;
import org.apache.wicket.model.IModel;
import io.onedev.commons.utils.ReflectionUtils;
import io.onedev.server.ci.job.action.PostBuildAction;
import io.onedev.server.web.editable.EditSupport;
import io.onedev.server.web.editable.EmptyValueLabel;
import io.onedev.server.web.editable.PropertyContext;
import io.onedev.server.web.editable.PropertyDescriptor;
import io.onedev.server.web.editable.PropertyEditor;
import io.onedev.server.web.editable.PropertyViewer;
@SuppressWarnings("serial")
public class ActionListEditSupport implements EditSupport {
@Override
public PropertyContext<?> getEditContext(PropertyDescriptor descriptor) {
if (List.class.isAssignableFrom(descriptor.getPropertyClass())) {
Class<?> elementClass = ReflectionUtils.getCollectionElementType(descriptor.getPropertyGetter().getGenericReturnType());
if (elementClass == PostBuildAction.class) {
return new PropertyContext<List<Serializable>>(descriptor) {
@Override
public PropertyViewer renderForView(String componentId, final IModel<List<Serializable>> model) {
return new PropertyViewer(componentId, descriptor) {
@Override
protected Component newContent(String id, PropertyDescriptor propertyDescriptor) {
if (model.getObject() != null) {
return new ActionListViewPanel(id, model.getObject());
} else {
return new EmptyValueLabel(id, propertyDescriptor.getPropertyGetter());
}
}
};
}
@Override
public PropertyEditor<List<Serializable>> renderForEdit(String componentId, IModel<List<Serializable>> model) {
return new ActionListEditPanel(componentId, descriptor, model);
}
};
}
}
return null;
}
@Override
public int getPriority() {
return 0;
}
}

View File

@ -0,0 +1,10 @@
<wicket:panel>
<div class="def-list action-list">
<div class="body">
<table wicket:id="actions" class="table table-condensed table-hover"></table>
</div>
</div>
<wicket:fragment wicket:id="columnFrag">
<a wicket:id="link"><span wicket:id="label"></span></a>
</wicket:fragment>
</wicket:panel>

View File

@ -0,0 +1,174 @@
package io.onedev.server.web.editable.job.postbuildaction;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable;
import org.apache.wicket.extensions.markup.html.repeater.data.table.HeadersToolbar;
import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
import org.apache.wicket.extensions.markup.html.repeater.data.table.NoRecordsToolbar;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.IDataProvider;
import org.apache.wicket.markup.repeater.data.ListDataProvider;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import io.onedev.server.ci.job.action.PostBuildAction;
import io.onedev.server.web.editable.BeanContext;
import io.onedev.server.web.editable.EditableUtils;
import io.onedev.server.web.page.layout.SideFloating;
@SuppressWarnings("serial")
class ActionListViewPanel extends Panel {
private final List<PostBuildAction> actions = new ArrayList<>();
public ActionListViewPanel(String id, List<Serializable> elements) {
super(id);
for (Serializable each: elements)
actions.add((PostBuildAction) each);
}
@Override
protected void onInitialize() {
super.onInitialize();
List<IColumn<PostBuildAction, Void>> columns = new ArrayList<>();
columns.add(new AbstractColumn<PostBuildAction, Void>(Model.of("Description")) {
@Override
public void populateItem(Item<ICellPopulator<PostBuildAction>> cellItem, String componentId, IModel<PostBuildAction> rowModel) {
cellItem.add(new ColumnFragment(componentId, cellItem.findParent(Item.class).getIndex()) {
@Override
protected Component newLabel(String componentId) {
return new Label(componentId, rowModel.getObject().getDescription());
}
});
}
});
columns.add(new AbstractColumn<PostBuildAction, Void>(Model.of("Condition")) {
@Override
public void populateItem(Item<ICellPopulator<PostBuildAction>> cellItem, String componentId, IModel<PostBuildAction> rowModel) {
cellItem.add(new ColumnFragment(componentId, cellItem.findParent(Item.class).getIndex()) {
@Override
protected Component newLabel(String componentId) {
return new Label(componentId, rowModel.getObject().getCondition());
}
});
}
});
columns.add(new AbstractColumn<PostBuildAction, Void>(Model.of("")) {
@Override
public void populateItem(Item<ICellPopulator<PostBuildAction>> cellItem, String componentId, IModel<PostBuildAction> rowModel) {
cellItem.add(new ColumnFragment(componentId, cellItem.findParent(Item.class).getIndex()) {
@Override
protected Component newLabel(String componentId) {
return new Label(componentId, "<i class='fa fa-ellipsis-h'></i>").setEscapeModelStrings(false);
}
});
}
@Override
public String getCssClass() {
return "ellipsis";
}
});
IDataProvider<PostBuildAction> dataProvider = new ListDataProvider<PostBuildAction>() {
@Override
protected List<PostBuildAction> getData() {
return actions;
}
};
add(new DataTable<PostBuildAction, Void>("actions", columns, dataProvider, Integer.MAX_VALUE) {
@Override
protected void onInitialize() {
super.onInitialize();
addTopToolbar(new HeadersToolbar<Void>(this, null));
addBottomToolbar(new NoRecordsToolbar(this));
}
});
}
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
response.render(CssHeaderItem.forReference(new ActionCssResourceReference()));
}
private abstract class ColumnFragment extends Fragment {
private final int index;
public ColumnFragment(String id, int index) {
super(id, "columnFrag", ActionListViewPanel.this);
this.index = index;
}
protected abstract Component newLabel(String componentId);
@Override
protected void onInitialize() {
super.onInitialize();
AjaxLink<Void> link = new AjaxLink<Void>("link") {
@Override
public void onClick(AjaxRequestTarget target) {
new SideFloating(target, SideFloating.Placement.RIGHT) {
@Override
protected String getTitle() {
return "Popst Build Action (type: " + EditableUtils.getDisplayName(actions.get(index).getClass()) + ")";
}
@Override
protected void onInitialize() {
super.onInitialize();
add(AttributeAppender.append("class", "post-build-action def-detail"));
}
@Override
protected Component newBody(String id) {
return BeanContext.view(id, actions.get(index));
}
};
}
};
link.add(newLabel("label"));
add(link);
}
}
}

Some files were not shown because too many files have changed in this diff Show More