Fix various query issues

This commit is contained in:
Robin Shen 2019-12-03 08:53:46 +08:00
parent c1a84bd41c
commit efecf77571
47 changed files with 531 additions and 298 deletions

View File

@ -36,7 +36,6 @@ import io.onedev.server.util.ComponentContext;
import io.onedev.server.util.EditContext;
import io.onedev.server.util.validation.Validatable;
import io.onedev.server.util.validation.annotation.ClassValidating;
import io.onedev.server.util.validation.annotation.RegEx;
import io.onedev.server.web.editable.annotation.Code;
import io.onedev.server.web.editable.annotation.Editable;
import io.onedev.server.web.editable.annotation.Horizontal;
@ -103,7 +102,6 @@ public class Job implements Serializable, Validatable {
private transient Map<String, ParamSpec> paramSpecMap;
@Editable(order=100, description="Specify name of the job")
@RegEx(pattern="[^:]*", message="Character ':' is not allowed") // ':' will be used as project/job separator in build query
@NotEmpty
public String getName() {
return name;

View File

@ -53,4 +53,8 @@ public interface BuildManager extends EntityManager<Build> {
Collection<String> getJobNames(@Nullable Project project);
Collection<String> getBuildVersions(@Nullable Project project);
Map<Project, Collection<String>> getAccessibleJobNames(@Nullable Project project, @Nullable User user);
}

View File

@ -2,16 +2,17 @@ package io.onedev.server.entitymanager;
import java.util.Collection;
import javax.annotation.Nullable;
import io.onedev.server.model.Build;
import io.onedev.server.model.BuildParam;
import io.onedev.server.model.Project;
import io.onedev.server.persistence.dao.EntityManager;
public interface BuildParamManager extends EntityManager<BuildParam> {
void deleteParams(Build build);
Collection<String> getBuildParamNames();
Collection<String> getBuildParamValues(String paramName);
Collection<String> getBuildParamNames(@Nullable Project project);
}

View File

@ -25,7 +25,6 @@ import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.subject.Subject;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.ObjectId;
@ -85,6 +84,7 @@ import io.onedev.server.security.permission.ProjectPermission;
import io.onedev.server.security.permission.SystemAdministration;
import io.onedev.server.storage.StorageManager;
import io.onedev.server.util.BuildConstants;
import io.onedev.server.util.ProjectScopedNumber;
import io.onedev.server.util.facade.BuildFacade;
import io.onedev.server.util.patternset.PatternSet;
@ -119,6 +119,10 @@ public class DefaultBuildManager extends AbstractEntityManager<Build> implements
private final ReadWriteLock jobNamesLock = new ReentrantReadWriteLock();
private final Map<Long, Collection<String>> buildVersions = new HashMap<>();
private final ReadWriteLock buildVersionsLock = new ReentrantReadWriteLock();
private String taskId;
@Inject
@ -170,19 +174,8 @@ public class DefaultBuildManager extends AbstractEntityManager<Build> implements
@Sessional
@Override
public Build find(String buildFQN) {
String projectName = StringUtils.substringBefore(buildFQN, "#");
Project project = projectManager.find(projectName);
if (project != null) {
String buildNumberStr = StringUtils.substringAfter(buildFQN, "#");
try {
Long buildNumber = Long.valueOf(buildNumberStr);
return find(project, buildNumber);
} catch (NumberFormatException e) {
throw new OneException("Invalid build number: " + buildNumberStr);
}
} else {
return null;
}
ProjectScopedNumber number = ProjectScopedNumber.from(buildFQN);
return find(number.getProject(), number.getNumber());
}
@Transactional
@ -192,6 +185,7 @@ public class DefaultBuildManager extends AbstractEntityManager<Build> implements
BuildFacade facade = build.getFacade();
String jobName = build.getJobName();
String buildVersion = build.getVersion();
transactionManager.runAfterCommit(new Runnable() {
@Override
@ -208,6 +202,12 @@ public class DefaultBuildManager extends AbstractEntityManager<Build> implements
} finally {
jobNamesLock.writeLock().unlock();
}
buildVersionsLock.writeLock().lock();
try {
populateBuildVersions(facade.getProjectId(), buildVersion);
} finally {
buildVersionsLock.writeLock().unlock();
}
}
});
@ -241,6 +241,15 @@ public class DefaultBuildManager extends AbstractEntityManager<Build> implements
} finally {
jobNamesLock.writeLock().unlock();
}
buildVersionsLock.writeLock().lock();
try {
for (Iterator<Map.Entry<Long, Collection<String>>> it = buildVersions.entrySet().iterator(); it.hasNext();) {
if (it.next().getKey().equals(projectId))
it.remove();
}
} finally {
buildVersionsLock.writeLock().unlock();
}
}
});
}
@ -546,12 +555,13 @@ public class DefaultBuildManager extends AbstractEntityManager<Build> implements
public void on(SystemStarted event) {
logger.info("Caching build info...");
Query<?> query = dao.getSession().createQuery("select id, project.id, commitHash, jobName from Build");
Query<?> query = dao.getSession().createQuery("select id, project.id, commitHash, jobName, version from Build");
for (Object[] fields: (List<Object[]>)query.list()) {
Long buildId = (Long) fields[0];
Long projectId = (Long)fields[1];
builds.put(buildId, new BuildFacade(buildId, projectId, (String)fields[2]));
populateJobNames(projectId, (String)fields[3]);
populateBuildVersions(projectId, (String)fields[4]);
}
taskId = taskScheduler.schedule(this);
}
@ -678,6 +688,17 @@ public class DefaultBuildManager extends AbstractEntityManager<Build> implements
jobNamesOfProject.add(jobName);
}
private void populateBuildVersions(Long projectId, @Nullable String buildVersion) {
if (buildVersion != null) {
Collection<String> builldVersionsOfProject = buildVersions.get(projectId);
if (builldVersionsOfProject == null) {
builldVersionsOfProject = new HashSet<>();
buildVersions.put(projectId, builldVersionsOfProject);
}
builldVersionsOfProject.add(buildVersion);
}
}
private Collection<String> getAccessibleJobNames(Role role, Collection<String> jobNames) {
Collection<String> accessibleJobNames = new HashSet<>();
if (role.isManageProject()) {
@ -710,6 +731,21 @@ public class DefaultBuildManager extends AbstractEntityManager<Build> implements
}
}
@Override
public Collection<String> getBuildVersions(@Nullable Project project) {
buildVersionsLock.readLock().lock();
try {
Collection<String> buildVersions = new HashSet<>();
for (Map.Entry<Long, Collection<String>> entry: this.buildVersions.entrySet()) {
if (project == null || project.getId().equals(entry.getKey()))
buildVersions.addAll(entry.getValue());
}
return buildVersions;
} finally {
buildVersionsLock.readLock().unlock();
}
}
private void populateAccessibleJobNames(Map<Project, Collection<String>> accessibleJobNames,
Map<Long, Collection<String>> jobNames, Project project, Role role) {
Collection<String> jobNamesOfProject = jobNames.get(project.getId());
@ -724,7 +760,8 @@ public class DefaultBuildManager extends AbstractEntityManager<Build> implements
}
}
private Map<Project, Collection<String>> getAccessibleJobNames(@Nullable Project project, @Nullable User user) {
@Override
public Map<Project, Collection<String>> getAccessibleJobNames(@Nullable Project project, @Nullable User user) {
jobNamesLock.readLock().lock();
try {
Map<Project, Collection<String>> accessibleJobNames = new HashMap<>();

View File

@ -3,13 +3,13 @@ package io.onedev.server.entitymanager.impl;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -20,9 +20,11 @@ import org.slf4j.LoggerFactory;
import io.onedev.commons.launcher.loader.Listen;
import io.onedev.server.buildspec.job.paramspec.ParamSpec;
import io.onedev.server.entitymanager.BuildParamManager;
import io.onedev.server.event.entity.EntityRemoved;
import io.onedev.server.event.system.SystemStarted;
import io.onedev.server.model.Build;
import io.onedev.server.model.BuildParam;
import io.onedev.server.model.Project;
import io.onedev.server.persistence.TransactionManager;
import io.onedev.server.persistence.annotation.Sessional;
import io.onedev.server.persistence.annotation.Transactional;
@ -34,11 +36,9 @@ public class DefaultBuildParamManager extends AbstractEntityManager<BuildParam>
private static final Logger logger = LoggerFactory.getLogger(DefaultBuildParamManager.class);
private static final int MAX_PARAM_VALUES = 100;
private final TransactionManager transactionManager;
private final Map<String, Set<String>> buildParams = new HashMap<>();
private final Map<Long, Collection<String>> buildParams = new HashMap<>();
private final ReadWriteLock buildParamsLock = new ReentrantReadWriteLock();
@ -62,11 +62,18 @@ public class DefaultBuildParamManager extends AbstractEntityManager<BuildParam>
@Listen
public void on(SystemStarted event) {
logger.info("Caching build param info...");
Map<Long, Long> projectIds = new HashMap<>();
Query<?> query = dao.getSession().createQuery("select id, project.id from Build");
for (Object[] fields: (List<Object[]>)query.list())
projectIds.put((Long)fields[0], (Long)fields[1]);
Query<?> query = dao.getSession().createQuery("select distinct name, type, value, id from BuildParam order by id");
query = dao.getSession().createQuery("select build.id, name from BuildParam where type != :secret");
query.setParameter("secret", ParamSpec.SECRET);
for (Object[] fields: (List<Object[]>)query.list()) {
if (!fields[1].equals(ParamSpec.SECRET))
addBuildParam((String) fields[0], (String) fields[2]);
Long projectId = projectIds.get(fields[0]);
if (projectId != null)
addBuildParam(projectId, (String) fields[1]);
}
}
@ -76,8 +83,8 @@ public class DefaultBuildParamManager extends AbstractEntityManager<BuildParam>
super.save(param);
if (!param.getType().equals(ParamSpec.SECRET)) {
Long projectId = param.getBuild().getProject().getId();
String paramName = param.getName();
String paramValue = param.getValue();
transactionManager.runAfterCommit(new Runnable() {
@ -85,8 +92,7 @@ public class DefaultBuildParamManager extends AbstractEntityManager<BuildParam>
public void run() {
buildParamsLock.writeLock().lock();
try {
if (!param.getType().equals(ParamSpec.SECRET))
addBuildParam(paramName, paramValue);
addBuildParam(projectId, paramName);
} finally {
buildParamsLock.writeLock().unlock();
}
@ -96,39 +102,51 @@ public class DefaultBuildParamManager extends AbstractEntityManager<BuildParam>
}
}
private void addBuildParam(String name, String value) {
Set<String> values = buildParams.get(name);
if (values == null) {
values = new LinkedHashSet<>();
buildParams.put(name, values);
private void addBuildParam(Long projectId, String paramName) {
Collection<String> paramsOfProject = buildParams.get(projectId);
if (paramsOfProject == null) {
paramsOfProject = new HashSet<>();
buildParams.put(projectId, paramsOfProject);
}
values.add(value);
if (values.size() > MAX_PARAM_VALUES)
values.iterator().remove();
paramsOfProject.add(paramName);
}
@Override
public Collection<String> getBuildParamNames() {
public Collection<String> getBuildParamNames(@Nullable Project project) {
buildParamsLock.readLock().lock();
try {
return new HashSet<>(buildParams.keySet());
Collection<String> buildParams = new HashSet<>();
for (Map.Entry<Long, Collection<String>> entry: this.buildParams.entrySet()) {
if (project == null || project.getId().equals(entry.getKey()))
buildParams.addAll(entry.getValue());
}
return buildParams;
} finally {
buildParamsLock.readLock().unlock();
}
}
@Override
public Collection<String> getBuildParamValues(String paramName) {
buildParamsLock.readLock().lock();
try {
Set<String> paramValues = buildParams.get(paramName);
if (paramValues != null)
return new HashSet<>(paramValues);
else
return new HashSet<>();
} finally {
buildParamsLock.readLock().unlock();
}
}
@Transactional
@Listen
public void on(EntityRemoved event) {
if (event.getEntity() instanceof Project) {
Long projectId = event.getEntity().getId();
transactionManager.runAfterCommit(new Runnable() {
@Override
public void run() {
buildParamsLock.writeLock().lock();
try {
for (Iterator<Map.Entry<Long, Collection<String>>> it = buildParams.entrySet().iterator(); it.hasNext();) {
if (it.next().getKey().equals(projectId))
it.remove();
}
} finally {
buildParamsLock.writeLock().unlock();
}
}
});
}
}
}

View File

@ -21,7 +21,6 @@ import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Session;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
@ -34,7 +33,6 @@ import com.google.common.base.Preconditions;
import io.onedev.commons.launcher.loader.Listen;
import io.onedev.commons.launcher.loader.ListenerRegistry;
import io.onedev.server.OneException;
import io.onedev.server.entitymanager.IssueFieldManager;
import io.onedev.server.entitymanager.IssueManager;
import io.onedev.server.entitymanager.IssueQuerySettingManager;
@ -72,6 +70,7 @@ import io.onedev.server.search.entity.issue.MilestoneCriteria;
import io.onedev.server.security.permission.AccessProject;
import io.onedev.server.security.permission.SystemAdministration;
import io.onedev.server.util.IssueConstants;
import io.onedev.server.util.ProjectScopedNumber;
import io.onedev.server.util.ValueSetEdit;
import io.onedev.server.util.facade.IssueFacade;
import io.onedev.server.util.inputspec.choiceinput.choiceprovider.SpecifiedChoices;
@ -143,19 +142,8 @@ public class DefaultIssueManager extends AbstractEntityManager<Issue> implements
@Sessional
@Override
public Issue find(String issueFQN) {
String projectName = StringUtils.substringBefore(issueFQN, "#");
Project project = projectManager.find(projectName);
if (project != null) {
String issueNumberStr = StringUtils.substringAfter(issueFQN, "#");
try {
Long issueNumber = Long.valueOf(issueNumberStr);
return find(project, issueNumber);
} catch (NumberFormatException e) {
throw new OneException("Invalid issue number: " + issueNumberStr);
}
} else {
return null;
}
ProjectScopedNumber number = ProjectScopedNumber.from(issueFQN);
return find(number.getProject(), number.getNumber());
}
@Transactional

View File

@ -126,6 +126,7 @@ import io.onedev.server.security.permission.ReadCode;
import io.onedev.server.security.permission.SystemAdministration;
import io.onedev.server.security.permission.WriteCode;
import io.onedev.server.util.ProjectAndBranch;
import io.onedev.server.util.ProjectScopedNumber;
import io.onedev.server.util.PullRequestConstants;
import io.onedev.server.util.SecurityUtils;
import io.onedev.server.util.markdown.MarkdownManager;
@ -1071,19 +1072,8 @@ public class DefaultPullRequestManager extends AbstractEntityManager<PullRequest
@Sessional
@Override
public PullRequest find(String pullRequestFQN) {
String projectName = StringUtils.substringBefore(pullRequestFQN, "#");
Project project = projectManager.find(projectName);
if (project != null) {
String pullRequestNumberStr = StringUtils.substringAfter(pullRequestFQN, "#");
try {
Long pullRequestNumber = Long.valueOf(pullRequestNumberStr);
return find(project, pullRequestNumber);
} catch (NumberFormatException e) {
throw new OneException("Invalid pull request number: " + pullRequestNumberStr);
}
} else {
return null;
}
ProjectScopedNumber number = ProjectScopedNumber.from(pullRequestFQN);
return find(number.getProject(), number.getNumber());
}
@Sessional

View File

@ -111,8 +111,8 @@ public class BoardSpec implements Serializable {
this.columns = columns;
}
@Editable(order=400, description="Specify columns of the board. Each column corresponds to "
+ "a value of the issue field specified above")
@Editable(order=400, name="Board Columns", description="Specify columns of the board. "
+ "Each column corresponds to a value of the issue field specified above")
@Size(min=2, message="At least two columns need to be defined")
@ChoiceProvider("getColumnChoices")
public List<String> getEditColumns() {

View File

@ -18,7 +18,6 @@ import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.validator.constraints.NotEmpty;
import io.onedev.server.util.validation.annotation.RegEx;
import io.onedev.server.web.editable.annotation.Editable;
import io.onedev.server.web.editable.annotation.Multiline;
@ -65,7 +64,6 @@ public class Milestone extends AbstractEntity {
}
@Editable(order=100)
@RegEx(pattern="[^:]*", message="Character ':' is not allowed") // ':' will be used as project/milestone separator in issue query
@NotEmpty
public String getName() {
return name;

View File

@ -3,6 +3,7 @@ package io.onedev.server.search.entity;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import javax.persistence.criteria.Path;
@ -29,10 +30,13 @@ import io.onedev.server.model.User;
import io.onedev.server.util.DateUtils;
import io.onedev.server.util.ProjectAwareCommitId;
import io.onedev.server.util.ProjectAwareRevision;
import io.onedev.server.util.ProjectScopedNumber;
public abstract class EntityQuery<T extends AbstractEntity> implements Serializable {
private static final long serialVersionUID = 1L;
private static final Pattern INSIDE_QUOTE = Pattern.compile("\"([^\"\\\\]|\\\\.)*");
public abstract EntityCriteria<T> getCriteria();
@ -150,6 +154,16 @@ public abstract class EntityQuery<T extends AbstractEntity> implements Serializa
throw new OneException("Unable to find build: " + value);
}
public static ProjectScopedNumber getEntityNumber(@Nullable Project project, String value) {
if (project != null) {
if (value.startsWith("#"))
value = project.getName() + value;
else if (!value.contains("#"))
value = project.getName() + "#" + value;
}
return ProjectScopedNumber.from(value);
}
public static Milestone getMilestone(@Nullable Project project, String value) {
if (project != null && !value.contains(":"))
value = project.getName() + ":" + value;
@ -199,4 +213,8 @@ public abstract class EntityQuery<T extends AbstractEntity> implements Serializa
}
}
public static boolean isInsideQuote(String value) {
return INSIDE_QUOTE.matcher(value.trim()).matches();
}
}

View File

@ -183,7 +183,7 @@ public class BuildQuery extends EntityQuery<Build> {
case FIELD_JOB:
return new JobCriteria(value);
case FIELD_NUMBER:
return new NumberCriteria(getIntValue(value), operator);
return new NumberCriteria(project, value, operator);
case FIELD_VERSION:
return new VersionCriteria(value);
default:
@ -191,7 +191,7 @@ public class BuildQuery extends EntityQuery<Build> {
}
case BuildQueryLexer.IsLessThan:
case BuildQueryLexer.IsGreaterThan:
return new NumberCriteria(getIntValue(value), operator);
return new NumberCriteria(project, value, operator);
default:
throw new IllegalStateException();
}
@ -245,7 +245,7 @@ public class BuildQuery extends EntityQuery<Build> {
}
public static void checkField(Project project, String fieldName, int operator) {
Collection<String> paramNames = OneDev.getInstance(BuildParamManager.class).getBuildParamNames();
Collection<String> paramNames = OneDev.getInstance(BuildParamManager.class).getBuildParamNames(null);
if (!QUERY_FIELDS.contains(fieldName) && !paramNames.contains(fieldName))
throw new OneException("Field not found: " + fieldName);
switch (operator) {

View File

@ -5,6 +5,7 @@ import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import io.onedev.commons.utils.match.WildcardUtils;
import io.onedev.server.model.Build;
import io.onedev.server.model.User;
import io.onedev.server.search.entity.EntityCriteria;
@ -22,13 +23,15 @@ public class JobCriteria extends EntityCriteria<Build> {
@Override
public Predicate getPredicate(Root<Build> root, CriteriaBuilder builder, User user) {
Path<?> attribute = root.get(BuildConstants.ATTR_JOB);
return builder.equal(attribute, jobName);
Path<String> attribute = root.get(BuildConstants.ATTR_JOB);
String normalized = jobName.toLowerCase().replace("*", "%");
return builder.like(builder.lower(attribute), normalized);
}
@Override
public boolean matches(Build build, User user) {
return build.getJobName().equals(jobName);
String jobName = build.getJobName();
return jobName != null && WildcardUtils.matchString(this.jobName.toLowerCase(), jobName.toLowerCase());
}
@Override

View File

@ -1,15 +1,18 @@
package io.onedev.server.search.entity.build;
import javax.annotation.Nullable;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Path;
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.search.entity.EntityQuery;
import io.onedev.server.util.BuildConstants;
import io.onedev.server.util.IssueConstants;
import io.onedev.server.util.ProjectScopedNumber;
public class NumberCriteria extends EntityCriteria<Build> {
@ -17,32 +20,43 @@ public class NumberCriteria extends EntityCriteria<Build> {
private final int operator;
private final long value;
private final String value;
public NumberCriteria(long value, int operator) {
private final ProjectScopedNumber number;
public NumberCriteria(@Nullable Project project, String value, int operator) {
this.operator = operator;
this.value = value;
number = EntityQuery.getEntityNumber(project, value);
}
@Override
public Predicate getPredicate(Root<Build> root, CriteriaBuilder builder, User user) {
Path<Long> attribute = root.get(IssueConstants.ATTR_NUMBER);
Path<Long> attribute = root.get(BuildConstants.ATTR_NUMBER);
Predicate numberPredicate;
if (operator == BuildQueryLexer.Is)
return builder.equal(attribute, value);
numberPredicate = builder.equal(attribute, number.getNumber());
else if (operator == BuildQueryLexer.IsGreaterThan)
return builder.greaterThan(attribute, value);
numberPredicate = builder.greaterThan(attribute, number.getNumber());
else
return builder.lessThan(attribute, value);
numberPredicate = builder.lessThan(attribute, number.getNumber());
return builder.and(
builder.equal(root.get(BuildConstants.ATTR_PROJECT), number.getProject()),
numberPredicate);
}
@Override
public boolean matches(Build build, User user) {
if (operator == BuildQueryLexer.Is)
return build.getNumber() == value;
else if (operator == BuildQueryLexer.IsGreaterThan)
return build.getNumber() > value;
else
return build.getNumber() < value;
if (build.getProject().equals(number.getProject())) {
if (operator == BuildQueryLexer.Is)
return build.getNumber() == number.getNumber();
else if (operator == BuildQueryLexer.IsGreaterThan)
return build.getNumber() > number.getNumber();
else
return build.getNumber() < number.getNumber();
} else {
return false;
}
}
@Override
@ -52,7 +66,9 @@ public class NumberCriteria extends EntityCriteria<Build> {
@Override
public String toString() {
return BuildQuery.quote(BuildConstants.FIELD_NUMBER) + " " + BuildQuery.getRuleName(operator) + " " + BuildQuery.quote(String.valueOf(value));
return BuildQuery.quote(BuildConstants.FIELD_NUMBER) + " "
+ BuildQuery.getRuleName(operator) + " "
+ BuildQuery.quote(value);
}
}

View File

@ -141,7 +141,7 @@ public class IssueQuery extends EntityQuery<Issue> {
if (validate)
checkField(fieldName, operator);
if (fieldName.equals(IssueConstants.FIELD_MILESTONE))
return new MilestoneCriteria(null);
return new MilestoneIsEmptyCriteria();
else
return new FieldOperatorCriteria(fieldName, operator);
}
@ -221,7 +221,7 @@ public class IssueQuery extends EntityQuery<Issue> {
} else if (fieldName.equals(IssueConstants.FIELD_COMMENT_COUNT)) {
return new CommentCountCriteria(getIntValue(value), operator);
} else if (fieldName.equals(IssueConstants.FIELD_NUMBER)) {
return new NumberCriteria(getIntValue(value), operator);
return new NumberCriteria(project, value, operator);
} else {
FieldSpec field = getGlobalIssueSetting().getFieldSpec(fieldName);
if (field instanceof IssueChoiceField) {
@ -251,7 +251,7 @@ public class IssueQuery extends EntityQuery<Issue> {
} else if (fieldName.equals(IssueConstants.FIELD_COMMENT_COUNT)) {
return new CommentCountCriteria(getIntValue(value), operator);
} else if (fieldName.equals(IssueConstants.FIELD_NUMBER)) {
return new NumberCriteria(getIntValue(value), operator);
return new NumberCriteria(project, value, operator);
} else {
FieldSpec field = getGlobalIssueSetting().getFieldSpec(fieldName);
if (field instanceof NumberField) {

View File

@ -2,13 +2,13 @@ package io.onedev.server.search.entity.issue;
import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import io.onedev.commons.utils.match.WildcardUtils;
import io.onedev.server.model.Issue;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.User;
@ -20,29 +20,23 @@ public class MilestoneCriteria extends IssueCriteria {
private final String milestoneName;
public MilestoneCriteria(@Nullable String milestoneName) {
public MilestoneCriteria(String milestoneName) {
this.milestoneName = milestoneName;
}
public String getValue() {
return milestoneName;
}
@Override
public Predicate getPredicate(Root<Issue> root, CriteriaBuilder builder, User user) {
Path<?> attribute = root.join(IssueConstants.ATTR_MILESTONE, JoinType.LEFT).get(Milestone.ATTR_NAME);
if (milestoneName != null)
return builder.equal(attribute, milestoneName);
else
return builder.isNull(attribute);
Path<String> attribute = root.join(IssueConstants.ATTR_MILESTONE, JoinType.LEFT).get(Milestone.ATTR_NAME);
String normalized = milestoneName.toLowerCase().replace("*", "%");
return builder.like(builder.lower(attribute), normalized);
}
@Override
public boolean matches(Issue issue, User user) {
if (milestoneName != null)
return issue.getMilestone() != null && issue.getMilestone().getName().equals(milestoneName);
else
return issue.getMilestone() == null;
if (issue.getMilestone() != null)
return WildcardUtils.matchString(milestoneName.toLowerCase(), issue.getMilestone().getName().toLowerCase());
else
return false;
}
@Override
@ -52,20 +46,14 @@ public class MilestoneCriteria extends IssueCriteria {
@Override
public String toString() {
if (milestoneName != null) {
return IssueQuery.quote(IssueConstants.FIELD_MILESTONE) + " "
+ IssueQuery.getRuleName(IssueQueryLexer.Is) + " "
+ IssueQuery.quote(milestoneName);
} else {
return IssueQuery.quote(IssueConstants.FIELD_MILESTONE) + " "
+ IssueQuery.getRuleName(IssueQueryLexer.IsEmpty);
}
return IssueQuery.quote(IssueConstants.FIELD_MILESTONE) + " "
+ IssueQuery.getRuleName(IssueQueryLexer.Is) + " "
+ IssueQuery.quote(milestoneName);
}
@Override
public void fill(Issue issue, Set<String> initedLists) {
if (milestoneName != null)
issue.setMilestone(issue.getProject().getMilestone(milestoneName));
issue.setMilestone(issue.getProject().getMilestone(milestoneName));
}
}

View File

@ -0,0 +1,37 @@
package io.onedev.server.search.entity.issue;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import io.onedev.server.model.Issue;
import io.onedev.server.model.User;
import io.onedev.server.util.IssueConstants;
public class MilestoneIsEmptyCriteria extends IssueCriteria {
private static final long serialVersionUID = 1L;
@Override
public Predicate getPredicate(Root<Issue> root, CriteriaBuilder builder, User user) {
return builder.isNull(root.join(IssueConstants.ATTR_MILESTONE, JoinType.LEFT));
}
@Override
public boolean matches(Issue issue, User user) {
return issue.getMilestone() == null;
}
@Override
public boolean needsLogin() {
return false;
}
@Override
public String toString() {
return IssueQuery.quote(IssueConstants.FIELD_MILESTONE) + " "
+ IssueQuery.getRuleName(IssueQueryLexer.IsEmpty);
}
}

View File

@ -1,13 +1,17 @@
package io.onedev.server.search.entity.issue;
import javax.annotation.Nullable;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import io.onedev.server.model.Issue;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.search.entity.EntityQuery;
import io.onedev.server.util.IssueConstants;
import io.onedev.server.util.ProjectScopedNumber;
public class NumberCriteria extends IssueCriteria {
@ -15,32 +19,45 @@ public class NumberCriteria extends IssueCriteria {
private final int operator;
private final long value;
private final String value;
public NumberCriteria(long value, int operator) {
private final ProjectScopedNumber number;
public NumberCriteria(@Nullable Project project, String value, int operator) {
this.operator = operator;
this.value = value;
number = EntityQuery.getEntityNumber(project, value);
}
@Override
public Predicate getPredicate(Root<Issue> root, CriteriaBuilder builder, User user) {
Path<Long> attribute = root.get(IssueConstants.ATTR_NUMBER);
Predicate numberPredicate;
if (operator == IssueQueryLexer.Is)
return builder.equal(attribute, value);
numberPredicate = builder.equal(attribute, number.getNumber());
else if (operator == IssueQueryLexer.IsGreaterThan)
return builder.greaterThan(attribute, value);
numberPredicate = builder.greaterThan(attribute, number.getNumber());
else
return builder.lessThan(attribute, value);
numberPredicate = builder.lessThan(attribute, number.getNumber());
return builder.and(
builder.equal(root.get(IssueConstants.ATTR_PROJECT), number.getProject()),
numberPredicate);
}
@Override
public boolean matches(Issue issue, User user) {
if (operator == IssueQueryLexer.Is)
return issue.getNumber() == value;
else if (operator == IssueQueryLexer.IsGreaterThan)
return issue.getNumber() > value;
else
return issue.getNumber() < value;
if (issue.getProject().equals(number.getProject())) {
if (operator == IssueQueryLexer.Is)
return issue.getNumber() == number.getNumber();
else if (operator == IssueQueryLexer.IsGreaterThan)
return issue.getNumber() > number.getNumber();
else
return issue.getNumber() < number.getNumber();
} else {
return false;
}
}
@Override
@ -52,7 +69,7 @@ public class NumberCriteria extends IssueCriteria {
public String toString() {
return IssueQuery.quote(IssueConstants.FIELD_NUMBER) + " "
+ IssueQuery.getRuleName(operator) + " "
+ IssueQuery.quote(String.valueOf(value));
+ IssueQuery.quote(value);
}
}

View File

@ -1,12 +1,16 @@
package io.onedev.server.search.entity.pullrequest;
import javax.annotation.Nullable;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import io.onedev.server.model.Project;
import io.onedev.server.model.PullRequest;
import io.onedev.server.model.User;
import io.onedev.server.search.entity.EntityQuery;
import io.onedev.server.util.ProjectScopedNumber;
import io.onedev.server.util.PullRequestConstants;
public class NumberCriteria extends PullRequestCriteria {
@ -15,32 +19,45 @@ public class NumberCriteria extends PullRequestCriteria {
private final int operator;
private final long value;
private final String value;
public NumberCriteria(long value, int operator) {
private final ProjectScopedNumber number;
public NumberCriteria(@Nullable Project project, String value, int operator) {
this.operator = operator;
this.value = value;
number = EntityQuery.getEntityNumber(project, value);
}
@Override
public Predicate getPredicate(Root<PullRequest> root, CriteriaBuilder builder, User user) {
Path<Long> attribute = root.get(PullRequestConstants.ATTR_NUMBER);
Predicate numberPredicate;
if (operator == PullRequestQueryLexer.Is)
return builder.equal(attribute, value);
numberPredicate = builder.equal(attribute, number.getNumber());
else if (operator == PullRequestQueryLexer.IsGreaterThan)
return builder.greaterThan(attribute, value);
numberPredicate = builder.greaterThan(attribute, number.getNumber());
else
return builder.lessThan(attribute, value);
numberPredicate = builder.lessThan(attribute, number.getNumber());
return builder.and(
builder.equal(root.get(PullRequestConstants.ATTR_TARGET_PROJECT), number.getProject()),
numberPredicate);
}
@Override
public boolean matches(PullRequest request, User user) {
if (operator == PullRequestQueryLexer.Is)
return request.getNumber() == value;
else if (operator == PullRequestQueryLexer.IsGreaterThan)
return request.getNumber() > value;
else
return request.getNumber() < value;
if (request.getTargetProject().equals(number.getProject())) {
if (operator == PullRequestQueryLexer.Is)
return request.getNumber() == number.getNumber();
else if (operator == PullRequestQueryLexer.IsGreaterThan)
return request.getNumber() > number.getNumber();
else
return request.getNumber() < number.getNumber();
} else {
return false;
}
}
@Override
@ -50,7 +67,9 @@ public class NumberCriteria extends PullRequestCriteria {
@Override
public String toString() {
return PullRequestQuery.quote(PullRequestConstants.FIELD_NUMBER) + " " + PullRequestQuery.getRuleName(operator) + " " + PullRequestQuery.quote(String.valueOf(value));
return PullRequestQuery.quote(PullRequestConstants.FIELD_NUMBER) + " "
+ PullRequestQuery.getRuleName(operator) + " "
+ PullRequestQuery.quote(value);
}
}

View File

@ -180,7 +180,7 @@ public class PullRequestQuery extends EntityQuery<PullRequest> {
case PullRequestQueryLexer.Is:
switch (fieldName) {
case PullRequestConstants.FIELD_NUMBER:
return new NumberCriteria(getIntValue(value), operator);
return new NumberCriteria(project, value, operator);
case PullRequestConstants.FIELD_MERGE_STRATEGY:
return new MergeStrategyCriteria(MergeStrategy.fromString(value));
case PullRequestConstants.FIELD_SOURCE_BRANCH:
@ -201,7 +201,7 @@ public class PullRequestQuery extends EntityQuery<PullRequest> {
case PullRequestQueryLexer.IsGreaterThan:
switch (fieldName) {
case PullRequestConstants.FIELD_NUMBER:
return new NumberCriteria(getIntValue(value), operator);
return new NumberCriteria(project, value, operator);
case PullRequestConstants.FIELD_COMMENT_COUNT:
return new CommentCountCriteria(getIntValue(value), operator);
default:

View File

@ -0,0 +1,48 @@
package io.onedev.server.util;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.OneDev;
import io.onedev.server.OneException;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.model.Project;
public class ProjectScopedNumber {
private final Project project;
private final Long number;
public ProjectScopedNumber(Project project, Long number) {
this.project = project;
this.number = number;
}
public static ProjectScopedNumber from(String fqn) {
if (fqn.contains("#")) {
String projectName = StringUtils.substringBefore(fqn, "#");
if (projectName.length() != 0) {
Project project = OneDev.getInstance(ProjectManager.class).find(projectName);
if (project != null) {
String numberStr = StringUtils.substringAfter(fqn, "#");
try {
return new ProjectScopedNumber(project, Long.valueOf(numberStr));
} catch (NumberFormatException e) {
throw new OneException("Invalid number: " + numberStr);
}
} else {
throw new OneException("Unable to find project: " + projectName);
}
}
}
throw new OneException("Project is not specified");
}
public Project getProject() {
return project;
}
public Long getNumber() {
return number;
}
}

View File

@ -26,6 +26,7 @@ import io.onedev.server.model.Project;
import io.onedev.server.search.entity.build.BuildQuery;
import io.onedev.server.search.entity.build.BuildQueryLexer;
import io.onedev.server.search.entity.build.BuildQueryParser;
import io.onedev.server.search.entity.project.ProjectQuery;
import io.onedev.server.util.BuildConstants;
import io.onedev.server.util.DateUtils;
import io.onedev.server.util.SecurityUtils;
@ -67,9 +68,13 @@ public class BuildQueryBehavior extends ANTLRAssistBehavior {
if ("criteriaField".equals(spec.getLabel())) {
List<String> fields = new ArrayList<>(BuildConstants.QUERY_FIELDS);
BuildParamManager buildParamManager = OneDev.getInstance(BuildParamManager.class);
List<String> paramNames = new ArrayList<>(buildParamManager.getBuildParamNames());
List<String> paramNames = new ArrayList<>(buildParamManager.getBuildParamNames(project));
Collections.sort(paramNames);
fields.addAll(paramNames);
for (String paramName: paramNames) {
fields.add(paramName);
if (fields.size() >= InputAssistBehavior.MAX_SUGGESTIONS)
break;
}
return SuggestionUtils.suggest(fields, matchWith);
} else if ("orderField".equals(spec.getLabel())) {
return SuggestionUtils.suggest(new ArrayList<>(BuildConstants.ORDER_FIELDS.keySet()), matchWith);
@ -99,15 +104,19 @@ public class BuildQueryBehavior extends ANTLRAssistBehavior {
List<InputSuggestion> suggestions = SuggestionUtils.suggest(DateUtils.RELAX_DATE_EXAMPLES, matchWith);
return !suggestions.isEmpty()? suggestions: null;
} else if (fieldName.equals(BuildConstants.FIELD_JOB)) {
return SuggestionUtils.suggestJobs(project, matchWith);
if (project != null && !matchWith.contains("*"))
return SuggestionUtils.suggestJobs(project, matchWith);
else
return null;
} else if (fieldName.equals(BuildConstants.FIELD_NUMBER)) {
return SuggestionUtils.suggestBuilds(project, matchWith, InputAssistBehavior.MAX_SUGGESTIONS);
} else if (fieldName.equals(BuildConstants.FIELD_VERSION)) {
if (project != null && !matchWith.contains("*"))
return SuggestionUtils.suggestBuildVersions(project, matchWith);
else
return null;
} else {
BuildParamManager buildParamManager = OneDev.getInstance(BuildParamManager.class);
List<String> paramValues = new ArrayList<>(buildParamManager.getBuildParamValues(fieldName));
Collections.sort(paramValues);
List<InputSuggestion> suggestions = SuggestionUtils.suggest(paramValues, matchWith);
return !suggestions.isEmpty()? suggestions: null;
return null;
}
} catch (OneException ex) {
}
@ -155,12 +164,15 @@ public class BuildQueryBehavior extends ANTLRAssistBehavior {
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
List<Element> elements = terminalExpect.getState().findMatchedElementsByLabel("criteriaField", true);
if (!elements.isEmpty() && elements.get(0).getFirstMatchedToken().getText().equals("\"" + BuildConstants.FIELD_VERSION + "\""))
hints.add("Use * to match any part of version");
if ("criteriaValue".equals(spec.getLabel()) && ProjectQuery.isInsideQuote(terminalExpect.getUnmatchedText())) {
List<Element> fieldElements = terminalExpect.getState().findMatchedElementsByLabel("criteriaField", true);
if (!fieldElements.isEmpty()) {
String fieldName = ProjectQuery.getValue(fieldElements.get(0).getMatchedText());
if (fieldName.equals(BuildConstants.FIELD_VERSION)
|| fieldName.equals(BuildConstants.FIELD_JOB)) {
hints.add("Use * for wildcard match");
hints.add("Use '\\' to escape quotes");
}
}
}
}

View File

@ -21,6 +21,7 @@ import io.onedev.server.model.Project;
import io.onedev.server.search.entity.codecomment.CodeCommentQuery;
import io.onedev.server.search.entity.codecomment.CodeCommentQueryLexer;
import io.onedev.server.search.entity.codecomment.CodeCommentQueryParser;
import io.onedev.server.search.entity.project.ProjectQuery;
import io.onedev.server.util.CodeCommentConstants;
import io.onedev.server.util.DateUtils;
import io.onedev.server.web.behavior.inputassist.ANTLRAssistBehavior;
@ -119,4 +120,23 @@ public class CodeCommentQueryBehavior extends ANTLRAssistBehavior {
return super.describe(parseExpect, suggestedLiteral);
}
@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()) && ProjectQuery.isInsideQuote(terminalExpect.getUnmatchedText())) {
List<Element> fieldElements = terminalExpect.getState().findMatchedElementsByLabel("criteriaField", true);
if (!fieldElements.isEmpty()) {
String fieldName = ProjectQuery.getValue(fieldElements.get(0).getMatchedText());
if (fieldName.equals(CodeCommentConstants.FIELD_CONTENT)) {
hints.add("Use * for wildcard match");
hints.add("Use '\\' to escape quotes");
}
}
}
}
return hints;
}
}

View File

@ -122,16 +122,12 @@ public class CommitQueryBehavior extends ANTLRAssistBehavior {
LexerRuleRefElementSpec spec = (LexerRuleRefElementSpec) terminalExpect.getElementSpec();
if (spec.getRuleName().equals("Value") && !terminalExpect.getUnmatchedText().contains(")")) {
int tokenType = terminalExpect.getState().getLastMatchedToken().getType();
if (tokenType == CommitQueryParser.COMMITTER) {
hints.add("Use * to match any part of committer");
} else if (tokenType == CommitQueryParser.AUTHOR) {
hints.add("Use * to match any part of author");
} else if (tokenType == CommitQueryParser.PATH) {
hints.add("Use * to match any part of path");
} else if (tokenType == CommitQueryParser.MESSAGE) {
hints.add("Use * to match any part of message");
hints.add("Use '\\\\' to escape special characters in regular expression");
hints.add("Use '\\(' and '\\)' to represent brackets in message");
if (tokenType == CommitQueryParser.COMMITTER
|| tokenType == CommitQueryParser.AUTHOR
|| tokenType == CommitQueryParser.PATH
|| tokenType == CommitQueryParser.MESSAGE) {
hints.add("Use * for wildcard match");
hints.add("Use '\\' to escape brackets");
}
}
}

View File

@ -56,6 +56,7 @@ import io.onedev.server.issue.fieldspec.UserChoiceField;
import io.onedev.server.model.Project;
import io.onedev.server.model.support.administration.GlobalIssueSetting;
import io.onedev.server.search.entity.issue.IssueQueryParser;
import io.onedev.server.search.entity.project.ProjectQuery;
import io.onedev.server.util.ComponentContext;
import io.onedev.server.util.DateUtils;
import io.onedev.server.util.IssueConstants;
@ -165,6 +166,8 @@ public class IssueQueryBehavior extends ANTLRAssistBehavior {
} finally {
ComponentContext.pop();
}
} else if (fieldName.equals(FIELD_NUMBER)) {
return SuggestionUtils.suggestIssues(project, matchWith, InputAssistBehavior.MAX_SUGGESTIONS);
} else if (fieldName.equals(FIELD_MILESTONE)) {
if (project != null)
return SuggestionUtils.suggestMilestones(project, matchWith);
@ -172,8 +175,8 @@ public class IssueQueryBehavior extends ANTLRAssistBehavior {
return null;
} else if (fieldName.equals(FIELD_TITLE) || fieldName.equals(FIELD_DESCRIPTION)
|| fieldName.equals(FIELD_COMMENT) || fieldName.equals(FIELD_VOTE_COUNT)
|| fieldName.equals(FIELD_COMMENT_COUNT) || fieldName.equals(FIELD_NUMBER)
|| fieldSpec instanceof NumberField || fieldSpec instanceof TextField) {
|| fieldName.equals(FIELD_COMMENT_COUNT) || fieldSpec instanceof NumberField
|| fieldSpec instanceof TextField) {
return null;
}
} catch (OneException ex) {
@ -217,4 +220,26 @@ public class IssueQueryBehavior extends ANTLRAssistBehavior {
return super.describe(parseExpect, suggestedLiteral);
}
@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()) && ProjectQuery.isInsideQuote(terminalExpect.getUnmatchedText())) {
List<Element> fieldElements = terminalExpect.getState().findMatchedElementsByLabel("criteriaField", true);
if (!fieldElements.isEmpty()) {
String fieldName = ProjectQuery.getValue(fieldElements.get(0).getMatchedText());
if (fieldName.equals(IssueConstants.FIELD_TITLE)
|| fieldName.equals(IssueConstants.FIELD_DESCRIPTION)
|| fieldName.equals(IssueConstants.FIELD_COMMENT)
|| fieldName.equals(IssueConstants.FIELD_MILESTONE)) {
hints.add("Use * for wildcard match");
hints.add("Use '\\' to escape quotes");
}
}
}
}
return hints;
}
}

View File

@ -53,12 +53,16 @@ public class ProjectQueryBehavior extends ANTLRAssistBehavior {
String fieldName = ProjectQuery.getValue(fieldElements.get(0).getMatchedText());
try {
ProjectQuery.checkField(fieldName, operator);
if (fieldName.equals(ProjectConstants.FIELD_NAME))
return SuggestionUtils.suggestProjects(matchWith);
else if (fieldName.equals(ProjectConstants.FIELD_OWNER))
if (fieldName.equals(ProjectConstants.FIELD_NAME)) {
if (!matchWith.contains("*"))
return SuggestionUtils.suggestProjects(matchWith);
else
return null;
} else if (fieldName.equals(ProjectConstants.FIELD_OWNER)) {
return SuggestionUtils.suggestUsers(matchWith);
else
} else {
return null;
}
} catch (OneException ex) {
}
}
@ -94,4 +98,24 @@ public class ProjectQueryBehavior extends ANTLRAssistBehavior {
return super.describe(parseExpect, suggestedLiteral);
}
@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()) && ProjectQuery.isInsideQuote(terminalExpect.getUnmatchedText())) {
List<Element> fieldElements = terminalExpect.getState().findMatchedElementsByLabel("criteriaField", true);
if (!fieldElements.isEmpty()) {
String fieldName = ProjectQuery.getValue(fieldElements.get(0).getMatchedText());
if (fieldName.equals(ProjectConstants.FIELD_NAME)
|| fieldName.equals(ProjectConstants.FIELD_DESCRIPTION)) {
hints.add("Use * for wildcard match");
hints.add("Use '\\' to escape quotes");
}
}
}
}
return hints;
}
}

View File

@ -6,6 +6,7 @@ import static io.onedev.server.search.entity.pullrequest.PullRequestQueryLexer.D
import static io.onedev.server.search.entity.pullrequest.PullRequestQueryLexer.RequestedForChangesByMe;
import static io.onedev.server.search.entity.pullrequest.PullRequestQueryLexer.SubmittedByMe;
import static io.onedev.server.search.entity.pullrequest.PullRequestQueryLexer.ToBeReviewedByMe;
import static io.onedev.server.util.IssueConstants.FIELD_NUMBER;
import java.util.ArrayList;
import java.util.List;
@ -26,6 +27,7 @@ import io.onedev.commons.codeassist.parser.TerminalExpect;
import io.onedev.server.OneException;
import io.onedev.server.model.Project;
import io.onedev.server.model.support.pullrequest.MergeStrategy;
import io.onedev.server.search.entity.project.ProjectQuery;
import io.onedev.server.search.entity.pullrequest.PullRequestQuery;
import io.onedev.server.search.entity.pullrequest.PullRequestQueryLexer;
import io.onedev.server.search.entity.pullrequest.PullRequestQueryParser;
@ -100,6 +102,8 @@ public class PullRequestQueryBehavior extends ANTLRAssistBehavior {
return SuggestionUtils.suggestBranches(project, matchWith);
else
return null;
} else if (fieldName.equals(FIELD_NUMBER)) {
return SuggestionUtils.suggestPullRequests(project, matchWith, InputAssistBehavior.MAX_SUGGESTIONS);
} else if (fieldName.equals(PullRequestConstants.FIELD_MERGE_STRATEGY)) {
List<String> candidates = new ArrayList<>();
for (MergeStrategy strategy: MergeStrategy.values())
@ -107,7 +111,6 @@ public class PullRequestQueryBehavior extends ANTLRAssistBehavior {
return SuggestionUtils.suggest(candidates, matchWith);
} else if (fieldName.equals(PullRequestConstants.FIELD_TITLE)
|| fieldName.equals(PullRequestConstants.FIELD_DESCRIPTION)
|| fieldName.equals(PullRequestConstants.FIELD_NUMBER)
|| fieldName.equals(PullRequestConstants.FIELD_COMMENT_COUNT)
|| fieldName.equals(PullRequestConstants.FIELD_COMMENT)) {
return null;
@ -155,4 +158,25 @@ public class PullRequestQueryBehavior extends ANTLRAssistBehavior {
return super.describe(parseExpect, suggestedLiteral);
}
@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()) && ProjectQuery.isInsideQuote(terminalExpect.getUnmatchedText())) {
List<Element> fieldElements = terminalExpect.getState().findMatchedElementsByLabel("criteriaField", true);
if (!fieldElements.isEmpty()) {
String fieldName = ProjectQuery.getValue(fieldElements.get(0).getMatchedText());
if (fieldName.equals(PullRequestConstants.FIELD_TITLE)
|| fieldName.equals(PullRequestConstants.FIELD_DESCRIPTION)
|| fieldName.equals(PullRequestConstants.FIELD_COMMENT)) {
hints.add("Use * for wildcard match");
hints.add("Use '\\' to escape quotes");
}
}
}
}
return hints;
}
}

View File

@ -256,7 +256,7 @@ public abstract class BuildListPanel extends Panel {
@Override
protected Map<String, String> load() {
Map<String, String> choices = new LinkedHashMap<>();
for (String fieldName: OneDev.getInstance(BuildParamManager.class).getBuildParamNames())
for (String fieldName: OneDev.getInstance(BuildParamManager.class).getBuildParamNames(null))
choices.put(fieldName, fieldName);
return choices;
}

View File

@ -49,8 +49,7 @@ public class BuildListPage extends LayoutPage {
@Override
protected String load() {
String query = getPageParameters().get(PARAM_QUERY).toOptionalString();
if (query != null && query.length() == 0) {
query = null;
if (query == null) {
List<String> queries = new ArrayList<>();
if (getLoginUser() != null) {
for (NamedBuildQuery namedQuery: getLoginUser().getBuildQuerySetting().getUserQueries())

View File

@ -45,8 +45,7 @@ public class IssueListPage extends LayoutPage {
@Override
protected String load() {
String query = getPageParameters().get(PARAM_QUERY).toOptionalString();
if (query != null && query.length() == 0) {
query = null;
if (query == null) {
List<String> queries = new ArrayList<>();
if (getLoginUser() != null) {
for (NamedIssueQuery namedQuery: getLoginUser().getIssueQuerySetting().getUserQueries())

View File

@ -13,8 +13,7 @@ public class BuildListTab implements MainTab {
@Override
public Component render(String componentId) {
return new ViewStateAwarePageLink<Void>(componentId, BuildListPage.class,
BuildListPage.paramsOf("", 0, 0)) {
return new ViewStateAwarePageLink<Void>(componentId, BuildListPage.class) {
private static final long serialVersionUID = 1L;

View File

@ -13,8 +13,7 @@ public class IssueListTab implements MainTab {
@Override
public Component render(String componentId) {
return new ViewStateAwarePageLink<Void>(componentId, IssueListPage.class,
IssueListPage.paramsOf("", 0)) {
return new ViewStateAwarePageLink<Void>(componentId, IssueListPage.class) {
private static final long serialVersionUID = 1L;

View File

@ -13,8 +13,7 @@ public class PullRequestListTab implements MainTab {
@Override
public Component render(String componentId) {
return new ViewStateAwarePageLink<Void>(componentId, PullRequestListPage.class,
PullRequestListPage.paramsOf("", 0)) {
return new ViewStateAwarePageLink<Void>(componentId, PullRequestListPage.class) {
private static final long serialVersionUID = 1L;

View File

@ -48,8 +48,7 @@ public class ProjectListPage extends LayoutPage {
@Override
protected String load() {
String query = getPageParameters().get(PARAM_QUERY).toOptionalString();
if (query != null && query.length() == 0) {
query = null;
if (query == null) {
List<String> queries = new ArrayList<>();
if (getLoginUser() != null) {
for (NamedProjectQuery namedQuery: getLoginUser().getProjectQuerySetting().getUserQueries())

View File

@ -6,7 +6,6 @@ import java.util.List;
import java.util.Map;
import org.apache.wicket.Component;
import org.apache.wicket.Page;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.behavior.AttributeAppender;
@ -14,7 +13,6 @@ import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Fragment;
@ -34,7 +32,6 @@ import io.onedev.server.util.SecurityUtils;
import io.onedev.server.web.component.floating.AlignPlacement;
import io.onedev.server.web.component.floating.FloatingPanel;
import io.onedev.server.web.component.link.DropdownLink;
import io.onedev.server.web.component.link.ViewStateAwarePageLink;
import io.onedev.server.web.component.project.avatar.ProjectAvatar;
import io.onedev.server.web.component.sidebar.SideBar;
import io.onedev.server.web.component.tabbable.Tab;
@ -195,14 +192,7 @@ public abstract class ProjectPage extends LayoutPage implements ProjectAware {
@Override
public Component render(String componentId) {
return new ProjectTabLink(componentId, this) {
@Override
protected Link<?> newLink(String linkId, Class<? extends Page> pageClass) {
return new ViewStateAwarePageLink<Void>(linkId, ProjectCommitsPage.class,
ProjectCommitsPage.paramsOf(getProject(), "", null));
}
};
return new ProjectTabLink(componentId, this);
}
});
@ -216,14 +206,7 @@ public abstract class ProjectPage extends LayoutPage implements ProjectAware {
@Override
public Component render(String componentId) {
return new ProjectTabLink(componentId, this) {
@Override
protected Link<?> newLink(String linkId, Class<? extends Page> pageClass) {
return new ViewStateAwarePageLink<Void>(linkId, ProjectPullRequestsPage.class,
ProjectPullRequestsPage.paramsOf(getProject(), "", 0));
}
};
return new ProjectTabLink(componentId, this);
}
});
@ -234,14 +217,7 @@ public abstract class ProjectPage extends LayoutPage implements ProjectAware {
@Override
public Component render(String componentId) {
return new ProjectTabLink(componentId, this) {
@Override
protected Link<?> newLink(String linkId, Class<? extends Page> pageClass) {
return new ViewStateAwarePageLink<Void>(linkId, ProjectIssueListPage.class,
ProjectIssueListPage.paramsOf(getProject(), "", 0));
}
};
return new ProjectTabLink(componentId, this);
}
});
@ -250,14 +226,7 @@ public abstract class ProjectPage extends LayoutPage implements ProjectAware {
@Override
public Component render(String componentId) {
return new ProjectTabLink(componentId, this) {
@Override
protected Link<?> newLink(String linkId, Class<? extends Page> pageClass) {
return new ViewStateAwarePageLink<Void>(linkId, ProjectBuildsPage.class,
ProjectBuildsPage.paramsOf(getProject(), "", 0));
}
};
return new ProjectTabLink(componentId, this);
}
});
@ -268,14 +237,7 @@ public abstract class ProjectPage extends LayoutPage implements ProjectAware {
@Override
public Component render(String componentId) {
return new ProjectTabLink(componentId, this) {
@Override
protected Link<?> newLink(String linkId, Class<? extends Page> pageClass) {
return new ViewStateAwarePageLink<Void>(linkId, ProjectCodeCommentsPage.class,
ProjectCodeCommentsPage.paramsOf(getProject(), ""));
}
};
return new ProjectTabLink(componentId, this);
}
});

View File

@ -1068,7 +1068,10 @@ public class ProjectBlobPage extends ProjectPage implements BlobRenderContext, S
@Override
public RevCommit getCommit() {
return getProject().getRevCommit(resolvedRevision, true);
if (resolvedRevision != null)
return getProject().getRevCommit(resolvedRevision, true);
else
return null;
}
@Override

View File

@ -88,6 +88,11 @@ public interface BlobRenderContext extends Serializable {
@Nullable
CodeComment getOpenComment();
/**
* @return
* null when there is no commit yet
*/
@Nullable
RevCommit getCommit();
/**

View File

@ -48,7 +48,7 @@ public class ProjectBuildsPage extends ProjectPage {
@Override
protected String load() {
String query = getPageParameters().get(PARAM_QUERY).toOptionalString();
if (query != null && query.length() == 0) {
if (query == null) {
List<String> queries = new ArrayList<>();
if (getProject().getBuildQuerySettingOfCurrentUser() != null) {
for (NamedBuildQuery namedQuery: getProject().getBuildQuerySettingOfCurrentUser().getUserQueries())

View File

@ -44,8 +44,7 @@ public class ProjectCodeCommentsPage extends ProjectPage {
@Override
protected String load() {
String query = getPageParameters().get(PARAM_QUERY).toString();
if (query != null && query.length() == 0) {
query = null;
if (query == null) {
List<String> queries = new ArrayList<>();
if (getProject().getCodeCommentQuerySettingOfCurrentUser() != null) {
for (NamedCodeCommentQuery namedQuery: getProject().getCodeCommentQuerySettingOfCurrentUser().getUserQueries())
@ -101,7 +100,8 @@ public class ProjectCodeCommentsPage extends ProjectPage {
@Override
protected Link<Void> newQueryLink(String componentId, NamedCodeCommentQuery namedQuery) {
return new BookmarkablePageLink<Void>(componentId, ProjectCodeCommentsPage.class, ProjectCodeCommentsPage.paramsOf(getProject(), namedQuery.getQuery()));
return new BookmarkablePageLink<Void>(componentId, ProjectCodeCommentsPage.class,
ProjectCodeCommentsPage.paramsOf(getProject(), namedQuery.getQuery()));
}
@Override

View File

@ -45,8 +45,7 @@ public class ProjectCommitsPage extends ProjectPage {
@Override
protected String load() {
String query = getPageParameters().get(PARAM_COMMIT_QUERY).toString();
if (query != null && query.length() == 0) {
query = null;
if (query == null) {
List<String> queries = new ArrayList<>();
if (getProject().getCommitQuerySettingOfCurrentUser() != null) {
for (NamedCommitQuery namedQuery: getProject().getCommitQuerySettingOfCurrentUser().getUserQueries())

View File

@ -17,7 +17,7 @@ public class ProjectDashboardPage extends ProjectPage {
if (SecurityUtils.canReadCode(getProject()))
throw new RestartResponseException(ProjectBlobPage.class, ProjectBlobPage.paramsOf(getProject()));
else
throw new RestartResponseException(ProjectIssueListPage.class, ProjectIssueListPage.paramsOf(getProject(), "", 0));
throw new RestartResponseException(ProjectIssueListPage.class, ProjectIssueListPage.paramsOf(getProject()));
}

View File

@ -61,14 +61,7 @@ public abstract class ProjectIssuesPage extends ProjectPage implements ScriptIde
@Override
public Component render(String componentId) {
return new PageTabLink(componentId, this) {
@Override
protected Link<?> newLink(String linkId, Class<? extends Page> pageClass) {
return new ViewStateAwarePageLink<Void>(linkId, ProjectIssueListPage.class,
ProjectIssueListPage.paramsOf(getProject(), "", 0));
}
};
return new PageTabLink(componentId, this);
}
});

View File

@ -29,7 +29,7 @@ import io.onedev.server.model.Issue;
import io.onedev.server.model.Project;
import io.onedev.server.search.entity.issue.IssueCriteria;
import io.onedev.server.search.entity.issue.IssueQuery;
import io.onedev.server.search.entity.issue.MilestoneCriteria;
import io.onedev.server.search.entity.issue.MilestoneIsEmptyCriteria;
import io.onedev.server.util.SecurityUtils;
import io.onedev.server.web.behavior.AbstractPostAjaxBehavior;
import io.onedev.server.web.component.modal.ModalLink;
@ -48,7 +48,7 @@ abstract class BacklogColumnPanel extends Panel {
List<IssueCriteria> criterias = new ArrayList<>();
if (backlogQuery.getCriteria() != null)
criterias.add(backlogQuery.getCriteria());
criterias.add(new MilestoneCriteria(null));
criterias.add(new MilestoneIsEmptyCriteria());
return new IssueQuery(IssueCriteria.of(criterias), backlogQuery.getSorts());
} else {
return null;
@ -112,7 +112,7 @@ abstract class BacklogColumnPanel extends Panel {
});
if (getQuery() != null) {
PageParameters params = ProjectIssueListPage.paramsOf(getProject(), getQuery().toString(), 1);
PageParameters params = ProjectIssueListPage.paramsOf(getProject(), getQuery().toString(), 0);
add(new BookmarkablePageLink<Void>("viewAsList", ProjectIssueListPage.class, params));
} else {
add(new WebMarkupContainer("viewAsList").setVisible(false));

View File

@ -264,7 +264,7 @@ abstract class BoardColumnPanel extends Panel implements EditContext {
}
if (getQuery() != null) {
PageParameters params = ProjectIssueListPage.paramsOf(getProject(), getQuery().toString(), 1);
PageParameters params = ProjectIssueListPage.paramsOf(getProject(), getQuery().toString(), 0);
head.add(new BookmarkablePageLink<Void>("viewAsList", ProjectIssueListPage.class, params));
} else {
head.add(new WebMarkupContainer("viewAsList").setVisible(false));

View File

@ -46,8 +46,7 @@ public class ProjectIssueListPage extends ProjectIssuesPage {
@Override
protected String load() {
String query = getPageParameters().get(PARAM_QUERY).toOptionalString();
if (query != null && query.length() == 0) {
query = null;
if (query == null) {
List<String> queries = new ArrayList<>();
if (getProject().getIssueQuerySettingOfCurrentUser() != null) {
for (NamedIssueQuery namedQuery: getProject().getIssueQuerySettingOfCurrentUser().getUserQueries())

View File

@ -50,8 +50,7 @@ public class ProjectPullRequestsPage extends ProjectPage {
@Override
protected String load() {
String query = getPageParameters().get(PARAM_QUERY).toOptionalString();
if (query != null && query.length() == 0) {
query = null;
if (query == null) {
List<String> queries = new ArrayList<>();
if (getProject().getPullRequestQuerySettingOfCurrentUser() != null) {
for (NamedPullRequestQuery namedQuery: getProject().getPullRequestQuerySettingOfCurrentUser().getUserQueries())

View File

@ -47,8 +47,7 @@ public class PullRequestListPage extends LayoutPage {
@Override
protected String load() {
String query = getPageParameters().get(PARAM_QUERY).toOptionalString();
if (query != null && query.length() == 0) {
query = null;
if (query == null) {
List<String> queries = new ArrayList<>();
if (getLoginUser() != null) {
for (NamedPullRequestQuery namedQuery: getLoginUser().getPullRequestQuerySetting().getUserQueries())

View File

@ -317,8 +317,10 @@ public class SuggestionUtils {
return suggestions;
}
public static List<InputSuggestion> suggestJobs(@Nullable Project project, String matchWith) {
Collection<String> jobNames = OneDev.getInstance(BuildManager.class).getJobNames(project);
public static List<InputSuggestion> suggestJobs(Project project, String matchWith) {
Collection<String> jobNames = OneDev.getInstance(BuildManager.class)
.getAccessibleJobNames(project, SecurityUtils.getUser())
.get(project);
List<InputSuggestion> suggestions = new ArrayList<>();
for (String jobName: jobNames) {
LinearRange match = LinearRange.match(jobName, matchWith);
@ -328,28 +330,25 @@ public class SuggestionUtils {
return suggestions;
}
public static List<InputSuggestion> suggestMilestones(@Nullable Project project, String matchWith) {
return suggest(project, matchWith, new ProjectScopedSuggester() {
@Override
public void fillSuggestions(List<InputSuggestion> suggestions, Project project, User user,
String matchWith, boolean prependProject) {
for (Milestone milestone: project.getMilestones()) {
LinearRange match = LinearRange.match(milestone.getName(), matchWith);
if (match != null) {
if (prependProject) {
int length = project.getName().length() + 1;
suggestions.add(new InputSuggestion(project.getName() + ":" + milestone.getName(),
null, new LinearRange(match.getFrom() + length, match.getTo() + length)));
} else {
suggestions.add(new InputSuggestion(milestone.getName(), null, match));
}
}
}
}
}, ":");
public static List<InputSuggestion> suggestBuildVersions(@Nullable Project project, String matchWith) {
Collection<String> buildVersions = OneDev.getInstance(BuildManager.class).getBuildVersions(project);
List<InputSuggestion> suggestions = new ArrayList<>();
for (String buildVersion: buildVersions) {
LinearRange match = LinearRange.match(buildVersion, matchWith);
if (match != null)
suggestions.add(new InputSuggestion(buildVersion, null, match));
}
return suggestions;
}
public static List<InputSuggestion> suggestMilestones(Project project, String matchWith) {
List<InputSuggestion> suggestions = new ArrayList<>();
for (Milestone milestone: project.getMilestones()) {
LinearRange match = LinearRange.match(milestone.getName(), matchWith);
if (match != null)
suggestions.add(new InputSuggestion(milestone.getName(), null, match));
}
return suggestions;
}
public static List<InputSuggestion> suggestArtifacts(Build build, String matchWith) {