mirror of
https://github.com/theonedev/onedev.git
synced 2025-12-08 18:26:30 +00:00
Post build action implementation
This commit is contained in:
parent
39292b1c99
commit
c2d7fa80de
@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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);
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
@ -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:_/\\+\-;]+
|
||||
;
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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:_/\\+\-;]+
|
||||
;
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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() {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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")
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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) + "'");
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package io.onedev.server.ci.job.retry;
|
||||
package io.onedev.server.ci.job.retry.condition;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -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'
|
||||
;
|
||||
@ -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) + "'");
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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>() {
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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'
|
||||
;
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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: '(' ('\\'.|~[\\()])+? ')';
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
@ -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 {};
|
||||
|
||||
}
|
||||
@ -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 {};
|
||||
|
||||
}
|
||||
@ -20,6 +20,4 @@ public @interface RetryCondition {
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
|
||||
boolean noLoginSupport() default false;
|
||||
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ public class BooleanPropertyEditor extends PropertyEditor<Boolean> {
|
||||
}
|
||||
|
||||
});
|
||||
input.setLabel(Model.of(getDescriptor().getDisplayName(this)));
|
||||
input.setLabel(Model.of(getDescriptor().getDisplayName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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"){
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"){
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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"){
|
||||
|
||||
@ -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"){
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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() {
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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") {
|
||||
|
||||
|
||||
@ -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"){
|
||||
|
||||
|
||||
@ -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"){
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"){
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@ -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">×</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>
|
||||
@ -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);
|
||||
|
||||
}
|
||||
@ -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>
|
||||
@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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>
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user