feat: Use natural language for various queries (OD-2600)

This commit is contained in:
Robin Shen 2025-11-15 22:36:02 +08:00
parent 706b2dbdba
commit c6a74dc008
16 changed files with 939 additions and 304 deletions

View File

@ -1,6 +1,7 @@
package io.onedev.server.ai; package io.onedev.server.ai;
import static io.onedev.server.ai.QueryDescriptions.getBuildQueryDescription; import static io.onedev.server.ai.QueryDescriptions.getBuildQueryDescription;
import static io.onedev.server.ai.QueryDescriptions.getPackQueryDescription;
import static javax.ws.rs.core.Response.Status.NOT_ACCEPTABLE; import static javax.ws.rs.core.Response.Status.NOT_ACCEPTABLE;
import static org.unbescape.html.HtmlEscape.escapeHtml5; import static org.unbescape.html.HtmlEscape.escapeHtml5;
@ -66,6 +67,7 @@ import io.onedev.server.model.IssueSchedule;
import io.onedev.server.model.IssueWork; import io.onedev.server.model.IssueWork;
import io.onedev.server.model.Iteration; import io.onedev.server.model.Iteration;
import io.onedev.server.model.LabelSpec; import io.onedev.server.model.LabelSpec;
import io.onedev.server.model.Pack;
import io.onedev.server.model.Project; import io.onedev.server.model.Project;
import io.onedev.server.model.PullRequest; import io.onedev.server.model.PullRequest;
import io.onedev.server.model.PullRequestAssignment; import io.onedev.server.model.PullRequestAssignment;
@ -95,6 +97,7 @@ import io.onedev.server.search.entity.EntityQuery;
import io.onedev.server.search.entity.build.BuildQuery; import io.onedev.server.search.entity.build.BuildQuery;
import io.onedev.server.search.entity.issue.IssueQuery; import io.onedev.server.search.entity.issue.IssueQuery;
import io.onedev.server.search.entity.issue.IssueQueryParseOption; import io.onedev.server.search.entity.issue.IssueQueryParseOption;
import io.onedev.server.search.entity.pack.PackQuery;
import io.onedev.server.search.entity.pullrequest.PullRequestQuery; import io.onedev.server.search.entity.pullrequest.PullRequestQuery;
import io.onedev.server.security.SecurityUtils; import io.onedev.server.security.SecurityUtils;
import io.onedev.server.service.BuildParamService; import io.onedev.server.service.BuildParamService;
@ -107,6 +110,7 @@ import io.onedev.server.service.IssueWorkService;
import io.onedev.server.service.IterationService; import io.onedev.server.service.IterationService;
import io.onedev.server.service.LabelSpecService; import io.onedev.server.service.LabelSpecService;
import io.onedev.server.service.LinkSpecService; import io.onedev.server.service.LinkSpecService;
import io.onedev.server.service.PackService;
import io.onedev.server.service.ProjectService; import io.onedev.server.service.ProjectService;
import io.onedev.server.service.PullRequestAssignmentService; import io.onedev.server.service.PullRequestAssignmentService;
import io.onedev.server.service.PullRequestChangeService; import io.onedev.server.service.PullRequestChangeService;
@ -166,6 +170,9 @@ public class McpHelperResource {
private final BuildService buildService; private final BuildService buildService;
@Inject
private PackService packService;
private final JobService jobService; private final JobService jobService;
private final GitService gitService; private final GitService gitService;
@ -623,6 +630,28 @@ public class McpHelperResource {
inputSchemas.put("editPullRequest", editPullRequestInputSchema); inputSchemas.put("editPullRequest", editPullRequestInputSchema);
var queryPacksInputSchema = new HashMap<String, Object>();
var queryPacksProperties = new HashMap<String, Object>();
queryPacksProperties.put("project", Map.of(
"type", "string",
"description", "Project to query packages in. Leave empty to query in current project"));
queryPacksProperties.put("query", Map.of(
"type", "string",
"description", escapeHtml5(getPackQueryDescription())));
queryPacksProperties.put("offset", Map.of(
"type", "integer",
"description", "start position for the query (optional, defaults to 0)"));
queryPacksProperties.put("count", Map.of(
"type", "integer",
"description", "number of packages to return (optional, defaults to 25, max 100)"));
queryPacksInputSchema.put("Type", "object");
queryPacksInputSchema.put("Properties", queryPacksProperties);
queryPacksInputSchema.put("Required", new ArrayList<>());
inputSchemas.put("queryPacks", queryPacksInputSchema);
return inputSchemas; return inputSchemas;
} }
@ -1946,6 +1975,53 @@ public class McpHelperResource {
} }
} }
@Path("/query-packs")
@GET
public List<Map<String, Object>> queryPacks(
@QueryParam("currentProject") @NotNull String currentProjectPath,
@QueryParam("project") String projectPath,
@QueryParam("query") String query,
@QueryParam("offset") int offset,
@QueryParam("count") int count) {
var subject = SecurityUtils.getSubject();
if (SecurityUtils.getUser(subject) == null)
throw new UnauthenticatedException();
var projectInfo = getProjectInfo(projectPath, currentProjectPath);
if (count > RestConstants.MAX_PAGE_SIZE)
throw new NotAcceptableException("Count should not be greater than " + RestConstants.MAX_PAGE_SIZE);
EntityQuery<Pack> parsedQuery;
if (query != null) {
parsedQuery = PackQuery.parse(projectInfo.project, query, true);
} else {
parsedQuery = new PackQuery();
}
var packs = new ArrayList<Map<String, Object>>();
for (var pack : packService.query(subject, projectInfo.project, parsedQuery, false, offset, count)) {
var packMap = getPackMap(projectInfo.currentProject, pack);
packMap.put("link", urlService.urlFor(pack, true));
packs.add(packMap);
}
return packs;
}
private Map<String, Object> getPackMap(Project currentProject, Pack pack) {
var typeReference = new TypeReference<LinkedHashMap<String, Object>>() {};
var packMap = objectMapper.convertValue(pack, typeReference);
packMap.remove("id");
packMap.remove("userId");
packMap.put("user", pack.getUser().getName());
packMap.remove("buildId");
if (pack.getBuild() != null)
packMap.put("build", pack.getBuild().getReference().toString(currentProject));
packMap.put("project", pack.getProject().getPath());
return packMap;
}
private void normalizePullRequestData(Map<String, Serializable> data) { private void normalizePullRequestData(Map<String, Serializable> data) {
for (var entry : data.entrySet()) { for (var entry : data.entrySet()) {
if (entry.getValue() instanceof String) if (entry.getValue() instanceof String)

View File

@ -1,29 +1,63 @@
package io.onedev.server.ai; package io.onedev.server.ai;
import static java.util.stream.Collectors.joining;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.stream.Collectors; import java.util.List;
import java.util.stream.IntStream;
import io.onedev.server.OneDev; import io.onedev.server.OneDev;
import io.onedev.server.model.Agent;
import io.onedev.server.model.Build; import io.onedev.server.model.Build;
import io.onedev.server.model.Issue; import io.onedev.server.model.Issue;
import io.onedev.server.model.LabelSpec; import io.onedev.server.model.LabelSpec;
import io.onedev.server.model.Pack;
import io.onedev.server.model.Project;
import io.onedev.server.model.PullRequest; import io.onedev.server.model.PullRequest;
import io.onedev.server.model.support.issue.field.spec.BooleanField; import io.onedev.server.model.support.issue.field.spec.BooleanField;
import io.onedev.server.model.support.issue.field.spec.BuildChoiceField;
import io.onedev.server.model.support.issue.field.spec.CommitField;
import io.onedev.server.model.support.issue.field.spec.DateField; import io.onedev.server.model.support.issue.field.spec.DateField;
import io.onedev.server.model.support.issue.field.spec.DateTimeField; import io.onedev.server.model.support.issue.field.spec.DateTimeField;
import io.onedev.server.model.support.issue.field.spec.FloatField;
import io.onedev.server.model.support.issue.field.spec.GroupChoiceField; import io.onedev.server.model.support.issue.field.spec.GroupChoiceField;
import io.onedev.server.model.support.issue.field.spec.IntegerField; import io.onedev.server.model.support.issue.field.spec.IntegerField;
import io.onedev.server.model.support.issue.field.spec.IssueChoiceField;
import io.onedev.server.model.support.issue.field.spec.IterationChoiceField;
import io.onedev.server.model.support.issue.field.spec.PullRequestChoiceField;
import io.onedev.server.model.support.issue.field.spec.TextField;
import io.onedev.server.model.support.issue.field.spec.choicefield.ChoiceField; import io.onedev.server.model.support.issue.field.spec.choicefield.ChoiceField;
import io.onedev.server.model.support.issue.field.spec.userchoicefield.UserChoiceField; import io.onedev.server.model.support.issue.field.spec.userchoicefield.UserChoiceField;
import io.onedev.server.model.support.pullrequest.MergeStrategy; import io.onedev.server.model.support.pullrequest.MergeStrategy;
import io.onedev.server.service.BuildParamService; import io.onedev.server.pack.PackSupport;
import io.onedev.server.service.BuildService; import io.onedev.server.service.AgentAttributeService;
import io.onedev.server.service.AgentService;
import io.onedev.server.service.LabelSpecService; import io.onedev.server.service.LabelSpecService;
import io.onedev.server.service.LinkSpecService; import io.onedev.server.service.LinkSpecService;
import io.onedev.server.service.SettingService; import io.onedev.server.service.SettingService;
public class QueryDescriptions { public class QueryDescriptions {
private static String REACTION_CRITERIAS = """
| '"Reaction: Thumbs Up Count"' 'is' ('not')? '"'Number'"'
| '"Reaction: Thumbs Up Count"' 'is' ('greater'|'less') 'than' '"'Number'"'
| '"Reaction: Thumbs Down Count"' 'is' ('not')? '"'Number'"'
| '"Reaction: Thumbs Down Count"' 'is' ('greater'|'less') 'than' '"'Number'"'
| '"Reaction: Smile Count"' 'is' ('not')? '"'Number'"'
| '"Reaction: Smile Count"' 'is' ('greater'|'less') 'than' '"'Number'"'
| '"Reaction: Tada Count"' 'is' ('not')? '"'Number'"'
| '"Reaction: Tada Count"' 'is' ('greater'|'less') 'than' '"'Number'"'
| '"Reaction: Confused Count"' 'is' ('not')? '"'Number'"'
| '"Reaction: Confused Count"' 'is' ('greater'|'less') 'than' '"'Number'"'
| '"Reaction: Heart Count"' 'is' ('not')? '"'Number'"'
| '"Reaction: Heart Count"' 'is' ('greater'|'less') 'than' '"'Number'"'
| '"Reaction: Rocket Count"' 'is' ('not')? '"'Number'"'
| '"Reaction: Rocket Count"' 'is' ('greater'|'less') 'than' '"'Number'"'
| '"Reaction: Eyes Count"' 'is' ('not')? '"'Number'"'
| '"Reaction: Eyes Count"' 'is' ('greater'|'less') 'than' '"'Number'"'
""".trim();
private static SettingService getSettingService() { private static SettingService getSettingService() {
return OneDev.getInstance(SettingService.class); return OneDev.getInstance(SettingService.class);
} }
@ -31,270 +65,575 @@ public class QueryDescriptions {
private static LinkSpecService getLinkSpecService() { private static LinkSpecService getLinkSpecService() {
return OneDev.getInstance(LinkSpecService.class); return OneDev.getInstance(LinkSpecService.class);
} }
private static LabelSpecService getLabelSpecService() {
return OneDev.getInstance(LabelSpecService.class);
}
private static BuildService getBuildService() {
return OneDev.getInstance(BuildService.class);
}
private static BuildParamService getBuildParamService() {
return OneDev.getInstance(BuildParamService.class);
}
public static String getIssueQueryDescription() { public static String getIssueQueryDescription() {
var settingService = getSettingService(); var settingService = getSettingService();
var linkSpecService = getLinkSpecService(); var linkSpecService = getLinkSpecService();
var stateNames = new StringBuilder(); var fieldCriterias = new ArrayList<String>();
for (var state: settingService.getIssueSetting().getStateSpecs()) { var choiceFieldValueRules = new ArrayList<String>();
stateNames.append(" - "); int choiceFieldValueRuleIndex = 0;
stateNames.append(state.getName());
if (state.getDescription() != null) {
stateNames.append(": ").append(state.getDescription().replace("\n", " "));
}
stateNames.append("\n");
}
var fieldCriterias = new StringBuilder();
for (var field: settingService.getIssueSetting().getFieldSpecs()) { for (var field: settingService.getIssueSetting().getFieldSpecs()) {
if (field instanceof ChoiceField) { if (field instanceof ChoiceField) {
var choiceField = (ChoiceField) field; var choiceField = (ChoiceField) field;
fieldCriterias.append("- " + field.getName().toLowerCase() + " criteria in form of: \"" var fieldValueRuleName = "ChoiceFieldValue" + (choiceFieldValueRuleIndex++);
+ field.getName() + "\" is \"<" + field.getName().toLowerCase() fieldCriterias.add("'\"" + field.getName() + "\"' 'is' ('not')? '\"'" + fieldValueRuleName + "'\"'");
+ " value>\" (quotes are required), where <" + field.getName().toLowerCase() choiceFieldValueRules.add(choiceField.getPossibleValues().stream().map(it->"'" + it.replace("'", "\\'") + "'").collect(joining("\n | ")));
+ " value> is one of below:\n");
for (var choice : choiceField.getPossibleValues())
fieldCriterias.append(" - " + choice).append("\n");
} else if (field instanceof UserChoiceField) { } else if (field instanceof UserChoiceField) {
fieldCriterias.append("- " + field.getName().toLowerCase() + " criteria in form of: \"" fieldCriterias.add("'\"" + field.getName() + "\"' 'is' ('not')? '\"'LoginNameOfUser'\"'");
+ field.getName() + "\" is \"<login name of a user>\" (quotes are required)\n"); fieldCriterias.add("'\"" + field.getName() + "\"' 'is' ('not')? 'me'");
fieldCriterias.append(
"- " + field.getName().toLowerCase() + " criteria for current user in form of: \""
+ field.getName() + "\" is me (quotes are required)\n");
} else if (field instanceof GroupChoiceField) { } else if (field instanceof GroupChoiceField) {
fieldCriterias.append("- " + field.getName().toLowerCase() + " criteria in form of: \"" fieldCriterias.add("'\"" + field.getName() + "\"' 'is' ('not')? '\"'GroupName'\"'");
+ field.getName() + "\" is \"<group name>\" (quotes are required)\n");
} else if (field instanceof BooleanField) { } else if (field instanceof BooleanField) {
fieldCriterias.append("- " + field.getName().toLowerCase() + " is true criteria in form of: \"" fieldCriterias.add("'\"" + field.getName() + "\"' 'is' ('not')? '\"'('true'|'false')'\"'");
+ field.getName() + "\" is \"true\" (quotes are required)\n");
fieldCriterias.append("- " + field.getName().toLowerCase() + " is false criteria in form of: \""
+ field.getName() + "\" is \"false\" (quotes are required)\n");
} else if (field instanceof DateField) { } else if (field instanceof DateField) {
fieldCriterias.append("- " + field.getName().toLowerCase() fieldCriterias.add("'\"" + field.getName() + "\"' 'is' ('before'|'after') '\"'DateDescription'\"'");
+ " is before certain date criteria in form of: \"" + field.getName()
+ "\" is before \"<date>\" (quotes are required), where <date> is of format YYYY-MM-DD\n");
fieldCriterias.append("- " + field.getName().toLowerCase()
+ " is after certain date criteria in form of: \"" + field.getName()
+ "\" is after \"<date>\" (quotes are required), where <date> is of format YYYY-MM-DD\n");
} else if (field instanceof DateTimeField) { } else if (field instanceof DateTimeField) {
fieldCriterias.append("- " + field.getName().toLowerCase() fieldCriterias.add("'\"" + field.getName() + "\"' 'is' ('before'|'after') '\"'DateTimeDescription'\"'");
+ " is before certain date time criteria in form of: \"" + field.getName()
+ "\" is before \"<date time>\" (quotes are required), where <date time> is of format YYYY-MM-DD HH:mm\n");
fieldCriterias.append("- " + field.getName().toLowerCase()
+ " is after certain date time criteria in form of: \"" + field.getName()
+ "\" is after \"<date time>\" (quotes are required), where <date time> is of format YYYY-MM-DD HH:mm\n");
} else if (field instanceof IntegerField) { } else if (field instanceof IntegerField) {
fieldCriterias.append("- " + field.getName().toLowerCase() fieldCriterias.add("'\"" + field.getName() + "\"' 'is' ('not')? '\"'Integer'\"'");
+ " is equal to certain integer criteria in form of: \"" + field.getName() fieldCriterias.add("'\"" + field.getName() + "\"' 'is' ('greater'|'less') 'than' '\"'Integer'\"'");
+ "\" is \"<integer>\" (quotes are required), where <integer> is an integer\n"); } else if (field instanceof FloatField) {
fieldCriterias.append("- " + field.getName().toLowerCase() fieldCriterias.add("'\"" + field.getName() + "\"' 'is' ('greater'|'less') 'than' '\"'Float'\"'");
+ " is greater than certain integer criteria in form of: \"" + field.getName() } else if (field instanceof TextField) {
+ "\" is greater than \"<integer>\" (quotes are required), where <integer> is an integer\n"); fieldCriterias.add("'\"" + field.getName() + "\"' 'is' ('not')? '\"'Text'\"'");
fieldCriterias.append("- " + field.getName().toLowerCase() fieldCriterias.add("'\"" + field.getName() + "\"' 'contains' '\"'Text'\"'");
+ " is less than certain integer criteria in form of: \"" + field.getName() } else if (field instanceof PullRequestChoiceField || field instanceof BuildChoiceField || field instanceof IssueChoiceField) {
+ "\" is less than \"<integer>\" (quotes are required), where <integer> is an integer\n"); fieldCriterias.add("'\"" + field.getName() + "\"' 'is' ('not')? '\"'EntityReference'\"'");
} else if (field instanceof CommitField) {
fieldCriterias.add("'\"" + field.getName() + "\"' 'is' ('not')? '\"'CommitReference'\"'");
} else if (field instanceof IterationChoiceField) {
fieldCriterias.add("'\"" + field.getName() + "\"' 'is' ('not')? '\"'IterationNameOrPattern'\"'");
} }
fieldCriterias.append("- " + field.getName().toLowerCase() + " is not set criteria in form of: \"" fieldCriterias.add("'\"" + field.getName() + "\"' 'is' ('not')? 'empty'");
+ field.getName() + "\" is empty (quotes are required)\n");
}
var linkCriterias = new StringBuilder();
for (var linkSpec: linkSpecService.query()) {
linkCriterias.append("- criteria to list issues with any " + linkSpec.getName().toLowerCase()
+ " matching certain criteria in form of: any \"" + linkSpec.getName()
+ "\" matching(another criteria) (quotes are required)\n");
linkCriterias.append("- criteria to list issues with all " + linkSpec.getName().toLowerCase()
+ " matching certain criteria in form of: all \"" + linkSpec.getName()
+ "\" matching(another criteria) (quotes are required)\n");
linkCriterias.append("- criteria to list issues with some " + linkSpec.getName().toLowerCase()
+ " in form of: has any \"" + linkSpec.getName() + "\" (quotes are required)\n");
if (linkSpec.getOpposite() != null) {
linkCriterias.append("- criteria to list issues with any "
+ linkSpec.getOpposite().getName().toLowerCase()
+ " matching certain criteria in form of: any \"" + linkSpec.getOpposite().getName()
+ "\" matching(another criteria) (quotes are required)\n");
linkCriterias.append("- criteria to list issues with all "
+ linkSpec.getOpposite().getName().toLowerCase()
+ " matching certain criteria in form of: all \"" + linkSpec.getOpposite().getName()
+ "\" matching(another criteria) (quotes are required)\n");
linkCriterias.append("- criteria to list issues with some " + linkSpec.getOpposite().getName().toLowerCase()
+ " in form of: has any \"" + linkSpec.getOpposite().getName() + "\" (quotes are required)\n");
}
}
var orderFields = new StringBuilder();
for (var field: Issue.SORT_FIELDS.keySet()) {
orderFields.append("- ").append(field).append("\n");
} }
var description = var linkCriterias = new ArrayList<String>();
"A query string is one of below criteria:\n\n" + for (var linkSpec: linkSpecService.query()) {
"- issue with specified number in form of: \"Number\" is \"#<issue number>\", or in form of: \"Number\" is \"<project key>-<issue number>\" (quotes are required)\n" + linkCriterias.add("'any' '\"" + linkSpec.getName() + "\"' 'matching' '('criteria')'");
"- state criteria in form of: \"State\" is \"<state name>\" (quotes are required), where <state name> is one of below:\n" + linkCriterias.add("'all' '\"" + linkSpec.getName() + "\"' 'matching' '('criteria')'");
stateNames + linkCriterias.add("'has any' '\"" + linkSpec.getName() + "\"'");
fieldCriterias + if (linkSpec.getOpposite() != null) {
linkCriterias + linkCriterias.add("'any' '\"" + linkSpec.getOpposite().getName() + "\"' 'matching' '('criteria')'");
"- submitted by specified user criteria in form of: submitted by \"<login name of a user>\" (quotes are required)\n" + linkCriterias.add("'all' '\"" + linkSpec.getOpposite().getName() + "\"' 'matching' '('criteria')'");
"- submitted by current user criteria in form of: submitted by me (quotes are required)\n" + linkCriterias.add("'has any' '\"" + linkSpec.getOpposite().getName() + "\"'");
"- submitted before certain date criteria in form of: \"Submit Date\" is until \"<date>\" (quotes are required), where <date> is of format YYYY-MM-DD HH:mm\n" + }
"- submitted after certain date criteria in form of: \"Submit Date\" is since \"<date>\" (quotes are required), where <date> is of format YYYY-MM-DD HH:mm\n" + }
"- updated before certain date criteria in form of: \"Last Activity Date\" is until \"<date>\" (quotes are required), where <date> is of format YYYY-MM-DD HH:mm\n" +
"- updated after certain date criteria in form of: \"Last Activity Date\" is since \"<date>\" (quotes are required), where <date> is of format YYYY-MM-DD HH:mm\n" + var description = String.format("""
"- confidential criteria in form of: confidential\n" + A structured query should conform to below ANTLR grammar:
"- iteration criteria in form of: \"Iteration\" is \"<iteration name>\" (quotes are required)\n" +
"- title contains specified text criteria in form of: \"Title\" contains \"<containing text>\" (quotes are required)\n" + issueQuery
"- description contains specified text criteria in form of: \"Description\" contains \"<containing text>\" (quotes are required)\n" + : criteria ('order by' '"'OrderField'"' ('asc'|'desc') (',' '"'OrderField'"' ('asc'|'desc'))*)?
"- comment contains specified text criteria in form of: \"Comment\" contains \"<containing text>\" (quotes are required)\n" + ;
"- project criteria in form of: \"Project\" is \"<project path pattern>\" (quotes are required)\n" +
"- and criteria in form of <criteria1> and <criteria2>\n" + criteria
"- or criteria in form of <criteria1> or <criteria2>\n" + : '"Number"' 'is' ('not')? '"'EntityReference'"'
"- operator 'and' takes precedence over 'or' when used together, unless parentheses are used to group 'or' criterias\n" + | '"Number"' 'is' ('greater'|'less') 'than' '"'EntityReference'"'
"- not criteria in form of not(<criteria>)\n" + | '"State"' 'is' ('not')? '"'StateName'"'
"\n" + | '"State"' 'is' ('after'|'before') '"'StateName'"'
"And can optionally add order clause at end of query string in form of: order by \"<field1>\" <asc|desc>,\"<field2>\" <asc|desc>,... (quotes are required), where <field> is one of below:\n\n" + %s
orderFields + "\n" + %s
"Issue, build or pull request can be referenced by their number in form of: #<number>, <project path>#<number> or <project key>-<number>\n" + | 'submitted by' '"'LoginNameOfUser'"'
"\n" + | 'submitted by me'
"Leave empty to list all accessible issues"; | 'watched by' '"'LoginNameOfUser'"'
| 'watched by me'
| 'ignored by' '"'LoginNameOfUser'"'
| 'ignored by me'
| 'commented by' '"'LoginNameOfUser'"'
| 'commented by me'
| 'mentioned' '"'LoginNameOfUser'"'
| 'mentioned me'
| 'fixed in commit' '"'CommitReference'"'
| 'fixed in current commit'
| 'fixed in build' '"'EntityReference'"'
| 'fixed in current build'
| 'fixed in pull request' '"'EntityReference'"'
| 'fixed in current pull request'
| 'fixed between' revision 'and' revision
| '"Submit Date"' 'is' ('until'|'since') '"'DateDescription'"'
| '"Last Activity Date"' 'is' ('until'|'since') '"'DateDescription'"'
| 'confidential'
| '"Spent Time"' 'is' ('greater'|'less') 'than' '"'TimePeriodDescription'"'
| '"Spent Time"' 'is' ('not')? '"'TimePeriodDescription'"'
| '"Estimated Time"' 'is' ('greater'|'less') 'than' '"'TimePeriodDescription'"'
| '"Estimated Time"' 'is' ('not')? '"'TimePeriodDescription'"'
| '"Progress"' 'is' ('greater'|'less') 'than' '"'Float'"'
| '"Iteration"' 'is' ('not')? '"'IterationNameOrPattern'"'
| '"Iteration"' 'is' ('not')? 'empty'
| '"Title"' 'contains' '"'Text'"'
| '"Description"' 'contains' '"'Text'"'
| '"Comment"' 'contains' '"'Text'"'
| '"Comment Count"' 'is' ('not')? '"'Number'"'
| '"Comment Count"' 'is' ('greater'|'less') 'than' '"'Number'"'
| '"Vote Count"' 'is' ('not')? '"'Number'"'
| '"Vote Count"' 'is' ('greater'|'less') 'than' '"'Number'"'
%s
| '"Project"' 'is current'
| '"Project"' 'is' ('not')? '"'ProjectPathOrPattern'"'
| criteria 'and' criteria
| criteria 'or' criteria
| 'not('criteria')'
| '('criteria')'
;
StateName
: %s
;
%s
revision
: 'commit' '"'CommitReference'"'
| 'build' '"'EntityReference'"'
| 'branch' '"'BranchReference'"'
| 'tag' '"'TagReference'"'
;
EntityReference
: '#'Number
| ProjectPath'#'Number
| ProjectKey'-'Number
;
CommitReference
: (ProjectPath':')?CommitHash
;
BranchReference
: (ProjectPath':')?BranchName
;
TagReference
: (ProjectPath':')?TagName
;
OrderField
: %s
;
WS
: [ ]+ -> skip
;
Please note:
1. "LoginNameOfUser" should be retrieved via tool 'getLoginName' if available, with parameter set to user name
2. Use an empty query to list all accessible issues""",
fieldCriterias.stream().map(it->" | " + it + "\n").collect(joining("")).trim(),
linkCriterias.stream().map(it->" | " + it + "\n").collect(joining("")).trim(),
REACTION_CRITERIAS,
settingService.getIssueSetting().getStateSpecs().stream().map(it->"'" + it.getName() + "'").collect(joining("\n | ")).trim(),
IntStream.range(0, choiceFieldValueRules.size()).mapToObj(i -> "ChoiceFieldValue" + i + "\n : " + choiceFieldValueRules.get(i) + "\n ;\n\n").collect(joining("")).trim(),
Issue.SORT_FIELDS.keySet().stream().map(it->"'" + it + "'").collect(joining("\n | ")).trim());
return description; return description;
} }
public static String getPullRequestQueryDescription() { public static String getPullRequestQueryDescription() {
var labelSpecService = getLabelSpecService(); var description = String.format("""
A structured query should conform to below ANTLR grammar:
var orderFields = new StringBuilder();
for (var field : PullRequest.SORT_FIELDS.keySet()) {
orderFields.append("- ").append(field).append("\n");
}
var labelNames = labelSpecService.query().stream().map(LabelSpec::getName).collect(Collectors.joining(", ")); pullRequestQuery
var mergeStrategyNames = Arrays.stream(MergeStrategy.values()).map(MergeStrategy::name).collect(Collectors.joining(", ")); : criteria ('order by' '"'OrderField'"' ('asc'|'desc') (',' '"'OrderField'"' ('asc'|'desc'))*)?
;
var description = criteria
"A query string is one of below criteria:\n\n" + : '"Number"' 'is' ('not')? '"'EntityReference'"'
"- pull request with specified number in form of: \"Number\" is \"#<pull request number>\", or in form of: \"Number\" is \"<project key>-<pull request number>\" (quotes are required)\n" + | '"Number"' 'is' ('greater'|'less') 'than' '"'EntityReference'"'
"- open criteria in form of: open\n" + | 'open'
"- merged criteria in form of: merged\n" + | 'merged'
"- discarded criteria in form of: discarded\n" + | 'discarded'
"- source branch criteria in form of: \"Source Branch\" is \"<branch name>\" (quotes are required)\n" + | '"Source Branch"' 'is' ('not')? '"'BranchNameOrPattern'"'
"- target branch criteria in form of: \"Target Branch\" is \"<branch name>\" (quotes are required)\n" + | '"Souce Project"' 'is' ('not')? '"'ProjectPathOrPattern'"'
"- merge strategy criteria in form of: \"Merge Strategy\" is \"<merge strategy>\" (quotes are required), where <merge strategy> is one of: " + mergeStrategyNames + "\n" + | '"Target Branch"' 'is' ('not')? '"'BranchNameOrPattern'"'
"- label criteria in form of: \"Label\" is \"<label name>\" (quotes are required), where <label name> is one of: " + labelNames + "\n" + | '"Merge Strategy"' 'is' ('not')? '"'MergeStrategy'"'
"- ready to merge criteria in form of: ready to merge\n" + | '"Label"' 'is' ('not')? '"'LabelName'"'
"- waiting for someone to review criteria in form of: has pending reviews\n" + | 'ready to merge'
"- some builds are unsuccessful criteria in form of: has unsuccessful builds\n" + | 'has pending reviews'
"- some builds are not finished criteria in form of: has unfinished builds\n" + | 'has unsuccessful builds'
"- has merge conflicts criteria in form of: has merge conflicts\n" + | 'has unfinished builds'
"- assigned to specified user criteria in form of: assigned to \"<login name of a user>\" (quotes are required)\n" + | 'has merge conflicts'
"- approved by specified user criteria in form of: approved by \"<login name of a user>\" (quotes are required)\n" + | 'assigned to' '"'LoginNameOfUser'"'
"- to be reviewed by specified user criteria in form of: to be reviewed by \"<login name of a user>\" (quotes are required)\n" + | 'approved by' '"'LoginNameOfUser'"'
"- to be changed by specified user criteria in form of: to be changed by \"<login name of a user>\" (quotes are required)\n" + | 'to be reviewed by' '"'LoginNameOfUser'"'
"- to be merged by specified user criteria in form of: to be merged by \"<login name of a user>\" (quotes are required)\n" + | 'to be changed by' '"'LoginNameOfUser'"'
"- requested for changes by specified user in form of: requested for changes by \"<login name of a user>\" (quotes are required)\n" + | 'to be merged by' '"'LoginNameOfUser'"'
"- need action of specified user criteria in form of: need action by \"<login name of a user>\" (quotes are required)\n" + | 'requested for changes by' '"'LoginNameOfUser'"'
"- assigned to current user criteria in form of: assigned to me\n" + | 'need action of' '"'LoginNameOfUser'"'
"- approved by current user criteria in form of: approved by me\n" + | 'assigned to me'
"- to be reviewed by current user criteria in form of: to be reviewed by me\n" + | 'approved by me'
"- to be changed by current user criteria in form of: to be changed by me\n" + | 'to be reviewed by me'
"- to be merged by current user criteria in form of: to be merged by me\n" + | 'to be changed by me'
"- requested for changes by current user in form of: requested for changes by me\n" + | 'to be merged by me'
"- requested for changes by any user criteria in form of: someone requested for changes\n" + | 'requested for changes by me'
"- need action of current user criteria in form of: need my action\n" + | 'someone requested for changes'
"- submitted by specified user criteria in form of: submitted by \"<login name of a user>\" (quotes are required)\n" + | 'mentioned' '"'LoginNameOfUser'"'
"- submitted by current user criteria in form of: submitted by me (quotes are required)\n" + | 'mentioned me'
"- submitted before certain date criteria in form of: \"Submit Date\" is until \"<date>\" (quotes are required), where <date> is of format YYYY-MM-DD HH:mm\n" + | 'need action of' '"'LoginNameOfUser'"'
"- submitted after certain date criteria in form of: \"Submit Date\" is since \"<date>\" (quotes are required), where <date> is of format YYYY-MM-DD HH:mm\n" + | 'need my action'
"- updated before certain date criteria in form of: \"Last Activity Date\" is until \"<date>\" (quotes are required), where <date> is of format YYYY-MM-DD HH:mm\n" + | 'submitted by' '"'LoginNameOfUser'"'
"- updated after certain date criteria in form of: \"Last Activity Date\" is since \"<date>\" (quotes are required), where <date> is of format YYYY-MM-DD HH:mm\n" + | 'submitted by me'
"- closed (merged or discarded) before certain date criteria in form of: \"Close Date\" is until \"<date>\" (quotes are required), where <date> is of format YYYY-MM-DD HH:mm\n" + | '"Submit Date"' 'is' ('until'|'since') '"'DateDescription'"'
"- closed (merged or discarded) after certain date criteria in form of: \"Close Date\" is since \"<date>\" (quotes are required), where <date> is of format YYYY-MM-DD HH:mm\n" + | '"Last Activity Date"' 'is' ('until'|'since') '"'DateDescription'"'
"- includes specified issue criteria in form of: includes issue \"<issue reference>\" (quotes are required)\n" + | '"Close Date"' 'is' ('until'|'since') '"'DateDescription'"'
"- includes specified commit criteria in form of: includes commit \"<commit hash>\" (quotes are required)\n" + | 'includes issue' '"'EntityReference'"'
"- title contains specified text criteria in form of: \"Title\" contains \"<containing text>\" (quotes are required)\n" + | 'includes commit' '"'CommitReference'"'
"- description contains specified text criteria in form of: \"Description\" contains \"<containing text>\" (quotes are required)\n" + | '"Title"' 'contains' '"'Text'"'
"- comment contains specified text criteria in form of: \"Comment\" contains \"<containing text>\" (quotes are required)\n" + | '"Description"' 'contains' '"'Text'"'
"- project criteria in form of: \"Project\" is \"<project path pattern>\" (quotes are required)\n" + | '"Comment"' 'contains' '"'Text'"'
"- and criteria in form of <criteria1> and <criteria2>\n" + | '"Comment Count"' 'is' ('not')? '"'Number'"'
"- or criteria in form of <criteria1> or <criteria2>\n" + | '"Comment Count"' 'is' ('greater'|'less') 'than' '"'Number'"'
"- operator 'and' takes precedence over 'or' when used together, unless parentheses are used to group 'or' criterias\n" + %s
"- not criteria in form of not(<criteria>)\n" + | '"Project"' 'is' ('not')? '"'ProjectPathOrPattern'"'
"\n" + | 'watched by' '"'LoginNameOfUser'"'
"And can optionally add order clause at end of query string in form of: order by \"<field1>\" <asc|desc>,\"<field2>\" <asc|desc>,... (quotes are required), where <field> is one of below:\n\n" + | 'watched by me'
orderFields + "\n" + | 'ignored by' '"'LoginNameOfUser'"'
"Issue, build or pull request can be referenced by their number in form of: #<number>, <project path>#<number> or <project key>-<number>\n" + | 'ignored by me'
"\n" + | 'commented by' '"'LoginNameOfUser'"'
"Leave empty to list all accessible pull requests"; | 'commented by me'
| criteria 'and' criteria
| criteria 'or' criteria
| 'not('criteria')'
| '('criteria')'
;
EntityReference
: '#'Number
| ProjectPath'#'Number
| ProjectKey'-'Number
;
CommitReference
: (ProjectPath':')?CommitHash
;
BranchReference
: (ProjectPath':')?BranchName
;
TagReference
: (ProjectPath':')?TagName
;
MergeStrategy
: %s
;
LabelName
: %s
;
OrderField
: %s
;
WS
: [ ]+ -> skip
;
Please note:
1. "LoginNameOfUser" should be retrieved via tool 'getLoginName' if available, with parameter set to user name
2. Use an empty query to list all accessible pull requests""",
REACTION_CRITERIAS,
Arrays.stream(MergeStrategy.values()).map(it->"'" + it.name() + "'").collect(joining("\n | ")).trim(),
getLabelSpecs().stream().map(it->"'" + it.getName() + "'").collect(joining("\n | ")).trim(),
PullRequest.SORT_FIELDS.keySet().stream().map(it->"'" + it + "'").collect(joining("\n | ")).trim());
return description; return description;
} }
public static String getBuildQueryDescription() { public static String getBuildQueryDescription() {
var buildService = getBuildService(); var description = String.format("""
var buildParamService = getBuildParamService(); A structured query should conform to below ANTLR grammar:
var labelSpecService = getLabelSpecService();
var orderFields = new StringBuilder();
for (var field : Build.SORT_FIELDS.keySet()) {
orderFields.append("- ").append(field).append("\n");
}
var jobNames = buildService.getJobNames(null).stream().collect(Collectors.joining(", ")); buildQuery
var paramNames = buildParamService.getParamNames(null).stream().collect(Collectors.joining(", ")); : criteria ('order by' '"'OrderField'"' ('asc'|'desc') (',' '"'OrderField'"' ('asc'|'desc'))*)?
var labelNames = labelSpecService.query().stream().map(LabelSpec::getName).collect(Collectors.joining(", ")); ;
var description = criteria
"A query string is one of below criteria:\n\n" + : '"Number"' 'is' ('not')? '"'EntityReference'"'
"- build with specified number in form of: \"Number\" is \"#<build number>\", or in form of: \"Number\" is \"<project key>-<build number>\" (quotes are required)\n" + | '"Number"' 'is' ('greater'|'less') 'than' '"'EntityReference'"'
"- criteria to check if version/job contains specified text in form of: ~<containing text>~\n" + | 'sucessful'
"- sucessful criteria in form of: sucessful\n" + | 'failed'
"- failed criteria in form of: failed\n" + | 'cancelled'
"- cancelled criteria in form of: cancelled\n" + | 'timed out'
"- timed out criteria in form of: timed out\n" + | 'finished'
"- finished criteria in form of: finished\n" + | 'running'
"- running criteria in form of: running\n" + | 'waiting'
"- waiting criteria in form of: waiting\n" + | 'pending'
"- pending criteria in form of: pending\n" + | 'submitted by' '"'LoginNameOfUser'"'
"- submitted by specified user criteria in form of: submitted by \"<login name of a user>\" (quotes are required)\n" + | 'submitted by me'
"- submitted by current user criteria in form of: submitted by me (quotes are required)\n" + | 'cancelled by' '"'LoginNameOfUser'"'
"- cancelled by specified user criteria in form of: cancelled by \"<login name of a user>\" (quotes are required)\n" + | 'cancelled by me'
"- cancelled by current user criteria in form of: cancelled by me (quotes are required)\n" + | 'depends on' '"'EntityReference'"'
"- depends on specified build criteria in form of: depends on \"<build reference>\" (quotes are required)\n" + | 'dependencies of' '"'EntityReference'"'
"- dependencies of specified build criteria in form of: dependencies of \"<build reference>\" (quotes are required)\n" + | 'ran on' '"'AgentName'"'
"- fixed specified issue criteria in form of: fixed issue \"<issue reference>\" (quotes are required)\n" + | 'fixed issue' '"'EntityReference'"'
"- job criteria in form of: \"Job\" is \"<job name>\" (quotes are required), where <job name> is one of: " + jobNames + "\n" + | '"Project"' 'is' ('not')? '"'ProjectPathOrPattern'"'
"- version criteria in form of: \"Version\" is \"<version>\" (quotes are required)\n" + | '"Job"' 'is' ('not')? '"'JobNameOrPattern'"'
"- branch criteria in form of: \"Branch\" is \"<branch name>\" (quotes are required)\n" + | '"Version"' 'is' ('not')? '"'VersionNameOrPattern'"'
"- tag criteria in form of: \"Tag\" is \"<tag name>\" (quotes are required)\n" + | '"Branch"' 'is' ('not')? '"'BranchNameOrPattern'"'
"- param criteria in form of: \"<param name>\" is \"<param value>\" (quotes are required), where <param name> is one of: " + paramNames + "\n" + | '"Tag"' 'is' ('not')? '"'TagNameOrPattern'"'
"- label criteria in form of: \"Label\" is \"<label name>\" (quotes are required), where <label name> is one of: " + labelNames + "\n" + | '"Param"' 'is' ('not')? '"'ParamName'"'
"- pull request criteria in form of: \"Pull Request\" is \"<pull request reference>\" (quotes are required)\n" + | '"Label"' 'is' ('not')? '"'LabelName'"'
"- commit criteria in form of: \"Commit\" is \"<commit hash>\" (quotes are required)\n" + | '"Pull Request"' 'is' ('not')? '"'EntityReference'"'
"- before certain date criteria in form of: \"Submit Date\" is until \"<date>\" (quotes are required), where <date> is of format YYYY-MM-DD HH:mm\n" + | '"Commit"' 'is' ('not')? '"'CommitReference'"'
"- after certain date criteria in form of: \"Submit Date\" is since \"<date>\" (quotes are required), where <date> is of format YYYY-MM-DD HH:mm\n" + | '"Submit Date"' 'is' ('until'|'since') '"'DateDescription'"'
"- project criteria in form of: \"Project\" is \"<project path pattern>\" (quotes are required)\n" + | '"Pending Date"' 'is' ('until'|'since') '"'DateDescription'"'
"- and criteria in form of <criteria1> and <criteria2>\n" + | '"Running Date"' 'is' ('until'|'since') '"'DateDescription'"'
"- or criteria in form of <criteria1> or <criteria2>\n" + | '"Finish Date"' 'is' ('until'|'since') '"'DateDescription'"'
"- operator 'and' takes precedence over 'or' when used together, unless parentheses are used to group 'or' criterias\n" + | criteria 'and' criteria
"- not criteria in form of not(<criteria>)\n" + | criteria 'or' criteria
"\n" + | 'not('criteria')'
"And can optionally add order clause at end of query string in form of: order by \"<field1>\" <asc|desc>,\"<field2>\" <asc|desc>,... (quotes are required), where <field> is one of below:\n\n" + | '('criteria')'
orderFields + "\n" + ;
"Issue, build or pull request can be referenced by their number in form of: #<number>, <project path>#<number> or <project key>-<number>\n" +
"\n" + EntityReference
"Leave empty to list all accessible builds"; : '#'Number
| ProjectPath'#'Number
| ProjectKey'-'Number
;
CommitReference
: (ProjectPath':')?CommitHash
;
LabelName
: %s
;
OrderField
: %s
;
WS
: [ ]+ -> skip
;
Please note:
1. "LoginNameOfUser" should be retrieved via tool 'getLoginName' if available, with parameter set to user name
2. Use an empty query to list all accessible builds""",
getLabelSpecs().stream().map(it->"'" + it.getName() + "'").collect(joining("\n | ")).trim(),
Build.SORT_FIELDS.keySet().stream().map(it->"'" + it + "'").collect(joining("\n | ")).trim());
return description; return description;
} }
private static List<LabelSpec> getLabelSpecs() {
return OneDev.getInstance(LabelSpecService.class).query();
}
public static String getPackQueryDescription() {
var packSupports = new ArrayList<>(OneDev.getExtensions(PackSupport.class));
var description = String.format("""
A structured query should conform to below ANTLR grammar:
packQuery
: criteria ('order by' '"'OrderField'"' ('asc'|'desc') (',' '"'OrderField'"' ('asc'|'desc'))*)?
;
criteria
: 'published by me'
| 'published by user' '"'LoginNameOfUser'"'
| 'published by build' '"'EntityReference'"'
| 'published by project' '"'ProjectPathOrPattern'"'
| '"Project"' 'is' ('not')? '"'ProjectPathOrPattern'"'
| '"Type"' 'is' ('not')? '"'PackType'"'
| '"Name"' 'is' ('not')? '"'PackName'"'
| '"Version"' 'is' ('not')? '"'PackVersion'"'
| '"Label"' 'is' ('not')? '"'LabelName'"'
| '"Publish Date"' 'is' ('until'|'since') '"'DateDescription'"'
| criteria 'and' criteria
| criteria 'or' criteria
| 'not('criteria')'
| '('criteria')'
;
EntityReference
: '#'Number
| ProjectPath'#'Number
| ProjectKey'-'Number
;
PackType
: %s
;
LabelName
: %s
;
OrderField
: %s
;
WS
: [ ]+ -> skip
;
Please note:
1. "LoginNameOfUser" should be retrieved via tool 'getLoginName' if available, with parameter set to user name
2. Use an empty query to list all accessible packages""",
packSupports.stream().map(it->"'" + it.getPackType() + "'").collect(joining("\n | ")).trim(),
getLabelSpecs().stream().map(it->"'" + it.getName() + "'").collect(joining("\n | ")).trim(),
Pack.SORT_FIELDS.keySet().stream().map(it->"'" + it + "'").collect(joining("\n | ")).trim());
return description;
}
public static String getCommitQueryDescription() {
var description = """
A structured query should conform to below ANTLR grammar:
commitQuery
: criteria+
;
criteria
: ('before('|'after(') DateDescription ')'
| 'committer(' CommitterNameAndEmail ')' // committer is specified user
| 'author(' AuthorNameAndEmail ')' // author is specified user
| 'message(' Text ')' // commit message contains specified text
| 'path(' FilePath ')' // commit touches specified file
| 'authored-by-me'
| 'committed-by-me'
| ('until')? revision // until specified revision
| 'since' revision // since specified revision
;
revision
: 'commit(' CommitHash ')'
| 'build(' '#'Number ')'
| 'branch(' BranchName ')'
| 'tag(' TagName ')'
| 'default-branch'
;
WS
: [ ]+ -> skip
;
""";
return description;
}
public static String getProjectQueryDescription() {
var description = String.format("""
A structured query should conform to below ANTLR grammar:
projectQuery
: criteria ('order by' '"'OrderField'"' ('asc'|'desc') (',' '"'OrderField'"' ('asc'|'desc'))*)?
;
criteria
: 'owned by' '"'LoginNameOfUser'"'
| 'owned by me'
| 'owned by none'
| 'has outdated replicas'
| 'without enough replicas'
| 'missing storage'
| 'children of' '"'ProjectPathOrPattern'"'
| 'forks of' '"'ProjectPathOrPattern'"'
| 'roots'
| 'leafs'
| 'fork roots'
| '"Name"' 'is' ('not')? '"'ProjectNameOrPattern'"'
| '"Key"' 'is' ('not')? '"'ProjectKeyOrPattern'"'
| '"Path"' 'is' ('not')? '"'ProjectPathOrPattern'"'
| '"Label"' 'is' ('not')? '"'LabelName'"'
| '"Description"' 'contains' '"'Text'"'
| '"Id"' 'is' ('not')? '"'Number'"'
| '"Id"' 'is' ('greater'|'less') 'than' '"'Number'"'
| '"Service Desk Email Address"' 'is' ('not')? '"'EmailAddressOrPattern'"'
| '"Last Activity Date"' 'is' ('until'|'since') '"'DateDescription'"'
| criteria 'and' criteria
| criteria 'or' criteria
| 'not('criteria')'
| '('criteria')'
;
LabelName
: %s
;
OrderField
: %s
;
WS
: [ ]+ -> skip
;
Please note:
1. "LoginNameOfUser" should be retrieved via tool 'getLoginName' if available, with parameter set to user name
2. Use an empty query to list all accessible projects""",
getLabelSpecs().stream().map(it->"'" + it.getName() + "'").collect(joining("\n | ")).trim(),
Project.SORT_FIELDS.keySet().stream().map(it->"'" + it + "'").collect(joining("\n | ")).trim());
return description;
}
public static String getAgentQueryDescription() {
var agentService = OneDev.getInstance(AgentService.class);
var attributeService = OneDev.getInstance(AgentAttributeService.class);
var description = String.format("""
A structured query should conform to below ANTLR grammar:
agentQuery
: criteria ('order by' '"'OrderField'"' ('asc'|'desc') (',' '"'OrderField'"' ('asc'|'desc'))*)?
;
criteria
: 'online'
| 'offline'
| 'paused'
| 'has running builds'
| 'has attribute' '"'AttributeName'"'
| 'not used since' '"'DateDescription'"'
| 'ever used since' '"'DateDescription'"'
| 'ran build' '"'EntityReference'"'
| '"Name"' 'is' ('not')? '"'AgentNameOrPattern'"'
| '"Ip Address"' 'is' ('not')? '"'IPAddressOrPattern'"'
| '"Os"' 'is' ('not')? '"'(OsName|OsNamePattern)'"'
| '"Os Arch"' 'is' ('not')? '"'(OsArch|OsArchPattern)'"'
| '"Os Version"' 'is' ('not')? '"'OsVersionOrPattern'"'
| '"'AttributeName'"' 'is' ('not')? '"'AttributeValue'"'
| criteria 'and' criteria
| criteria 'or' criteria
| 'not('criteria')'
| '('criteria')'
;
OsName
: %s
;
OsArch
: %s
;
AttributeName
: %s
;
OrderField
: %s
;
WS
: [ ]+ -> skip
;
Please note:
1. "LoginNameOfUser" should be retrieved via tool 'getLoginName' if available, with parameter set to user name
2. Use an empty query to list all accessible agents""",
agentService.getOsNames().stream().map(it->"'" + it + "'").collect(joining("\n | ")).trim(),
agentService.getOsArchs().stream().map(it->"'" + it + "'").collect(joining("\n | ")).trim(),
attributeService.getAttributeNames().stream().map(it->"'" + it + "'").collect(joining("\n | ")).trim(),
Agent.SORT_FIELDS.keySet().stream().map(it->"'" + it + "'").collect(joining("\n | ")).trim());
return description;
}
} }

View File

@ -87,7 +87,15 @@ public abstract class EntityReference implements Serializable {
else else
throw new ValidationException("Reference project not found with key: " + projectKey); throw new ValidationException("Reference project not found with key: " + projectKey);
} }
throw new ValidationException("Invalid entity reference: " + referenceString); try {
var number = Long.valueOf(referenceString);
if (currentProject != null)
return EntityReference.of(type, currentProject, number);
else
throw new ValidationException("Reference project not specified: " + referenceString);
} catch (NumberFormatException e) {
throw new ValidationException("Invalid entity reference: " + referenceString);
}
} }
@Override @Override

View File

@ -7,17 +7,31 @@ import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.jspecify.annotations.Nullable;
import javax.persistence.criteria.Path; import javax.persistence.criteria.Path;
import javax.validation.ValidationException; import javax.validation.ValidationException;
import org.jspecify.annotations.Nullable;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import io.onedev.commons.codeassist.FenceAware; import io.onedev.commons.codeassist.FenceAware;
import io.onedev.commons.utils.ExplicitException; import io.onedev.commons.utils.ExplicitException;
import io.onedev.commons.utils.StringUtils; import io.onedev.commons.utils.StringUtils;
import io.onedev.server.OneDev; import io.onedev.server.OneDev;
import io.onedev.server.entityreference.BuildReference;
import io.onedev.server.entityreference.IssueReference;
import io.onedev.server.entityreference.PullRequestReference;
import io.onedev.server.model.AbstractEntity;
import io.onedev.server.model.Build;
import io.onedev.server.model.Group;
import io.onedev.server.model.Issue;
import io.onedev.server.model.Iteration;
import io.onedev.server.model.LabelSpec;
import io.onedev.server.model.Project;
import io.onedev.server.model.PullRequest;
import io.onedev.server.model.User;
import io.onedev.server.service.BuildService; import io.onedev.server.service.BuildService;
import io.onedev.server.service.GroupService;
import io.onedev.server.service.IssueService; import io.onedev.server.service.IssueService;
import io.onedev.server.service.IterationService; import io.onedev.server.service.IterationService;
import io.onedev.server.service.LabelSpecService; import io.onedev.server.service.LabelSpecService;
@ -25,17 +39,6 @@ import io.onedev.server.service.ProjectService;
import io.onedev.server.service.PullRequestService; import io.onedev.server.service.PullRequestService;
import io.onedev.server.service.SettingService; import io.onedev.server.service.SettingService;
import io.onedev.server.service.UserService; import io.onedev.server.service.UserService;
import io.onedev.server.entityreference.BuildReference;
import io.onedev.server.entityreference.IssueReference;
import io.onedev.server.entityreference.PullRequestReference;
import io.onedev.server.model.AbstractEntity;
import io.onedev.server.model.Build;
import io.onedev.server.model.Issue;
import io.onedev.server.model.Iteration;
import io.onedev.server.model.LabelSpec;
import io.onedev.server.model.Project;
import io.onedev.server.model.PullRequest;
import io.onedev.server.model.User;
import io.onedev.server.util.DateUtils; import io.onedev.server.util.DateUtils;
import io.onedev.server.util.ProjectScopedCommit; import io.onedev.server.util.ProjectScopedCommit;
import io.onedev.server.util.ProjectScopedRevision; import io.onedev.server.util.ProjectScopedRevision;
@ -139,6 +142,13 @@ public abstract class EntityQuery<T extends AbstractEntity> implements Serializa
throw new ExplicitException("Unable to find user with login: " + loginName); throw new ExplicitException("Unable to find user with login: " + loginName);
return user; return user;
} }
public static Group getGroup(String groupName) {
Group group = OneDev.getInstance(GroupService.class).find(groupName);
if (group == null)
throw new ExplicitException("Unable to find group: " + groupName);
return group;
}
public static Project getProject(String projectPath) { public static Project getProject(String projectPath) {
Project project = OneDev.getInstance(ProjectService.class).findByPath(projectPath); Project project = OneDev.getInstance(ProjectService.class).findByPath(projectPath);

View File

@ -0,0 +1,54 @@
package io.onedev.server.search.entity.issue;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueField;
public class FloatFieldCriteria extends FieldCriteria {
private static final long serialVersionUID = 1L;
private final float value;
private final int operator;
public FloatFieldCriteria(String name, float value, int operator) {
super(name);
this.value = value;
this.operator = operator;
}
@Override
protected Predicate getValuePredicate(From<Issue, Issue> issueFrom, From<IssueField, IssueField> fieldFrom, CriteriaBuilder builder) {
Path<Float> attribute = fieldFrom.get(IssueField.PROP_ORDINAL);
if (operator == IssueQueryLexer.IsGreaterThan)
return builder.greaterThan(attribute, value);
else
return builder.lessThan(attribute, value);
}
@Override
public boolean matches(Issue issue) {
Float fieldValue = (Float) issue.getFieldValue(getFieldName());
if (operator == IssueQueryLexer.IsGreaterThan)
return fieldValue != null && fieldValue > value;
else
return fieldValue != null && fieldValue < value;
}
@Override
public String toStringWithoutParens() {
return quote(getFieldName()) + " "
+ IssueQuery.getRuleName(operator) + " "
+ quote(String.valueOf(value));
}
@Override
public void fill(Issue issue) {
}
}

View File

@ -10,8 +10,7 @@ import javax.persistence.criteria.Predicate;
import io.onedev.server.model.Issue; import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueField; import io.onedev.server.model.IssueField;
public class IntegerFieldCriteria extends FieldCriteria {
public class NumericFieldCriteria extends FieldCriteria {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -19,7 +18,7 @@ public class NumericFieldCriteria extends FieldCriteria {
private final int operator; private final int operator;
public NumericFieldCriteria(String name, int value, int operator) { public IntegerFieldCriteria(String name, int value, int operator) {
super(name); super(name);
this.value = value; this.value = value;
this.operator = operator; this.operator = operator;

View File

@ -1,5 +1,6 @@
package io.onedev.server.search.entity.issue; package io.onedev.server.search.entity.issue;
import static io.onedev.server.model.AbstractEntity.NAME_NUMBER;
import static io.onedev.server.model.Issue.NAME_COMMENT; import static io.onedev.server.model.Issue.NAME_COMMENT;
import static io.onedev.server.model.Issue.NAME_COMMENT_COUNT; import static io.onedev.server.model.Issue.NAME_COMMENT_COUNT;
import static io.onedev.server.model.Issue.NAME_CONFUSED_COUNT; import static io.onedev.server.model.Issue.NAME_CONFUSED_COUNT;
@ -56,8 +57,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.jspecify.annotations.Nullable;
import org.antlr.v4.runtime.BailErrorStrategy; import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStream;
@ -65,11 +64,11 @@ import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer; import org.antlr.v4.runtime.Recognizer;
import org.jspecify.annotations.Nullable;
import io.onedev.commons.codeassist.AntlrUtils; import io.onedev.commons.codeassist.AntlrUtils;
import io.onedev.commons.utils.ExplicitException; import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.OneDev; import io.onedev.server.OneDev;
import io.onedev.server.service.SettingService;
import io.onedev.server.model.Issue; import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueSchedule; import io.onedev.server.model.IssueSchedule;
import io.onedev.server.model.Project; import io.onedev.server.model.Project;
@ -80,6 +79,7 @@ import io.onedev.server.model.support.issue.field.spec.CommitField;
import io.onedev.server.model.support.issue.field.spec.DateField; import io.onedev.server.model.support.issue.field.spec.DateField;
import io.onedev.server.model.support.issue.field.spec.DateTimeField; import io.onedev.server.model.support.issue.field.spec.DateTimeField;
import io.onedev.server.model.support.issue.field.spec.FieldSpec; import io.onedev.server.model.support.issue.field.spec.FieldSpec;
import io.onedev.server.model.support.issue.field.spec.FloatField;
import io.onedev.server.model.support.issue.field.spec.GroupChoiceField; import io.onedev.server.model.support.issue.field.spec.GroupChoiceField;
import io.onedev.server.model.support.issue.field.spec.IntegerField; import io.onedev.server.model.support.issue.field.spec.IntegerField;
import io.onedev.server.model.support.issue.field.spec.IssueChoiceField; import io.onedev.server.model.support.issue.field.spec.IssueChoiceField;
@ -106,6 +106,7 @@ import io.onedev.server.search.entity.issue.IssueQueryParser.ParensCriteriaConte
import io.onedev.server.search.entity.issue.IssueQueryParser.QueryContext; import io.onedev.server.search.entity.issue.IssueQueryParser.QueryContext;
import io.onedev.server.search.entity.issue.IssueQueryParser.ReferenceCriteriaContext; import io.onedev.server.search.entity.issue.IssueQueryParser.ReferenceCriteriaContext;
import io.onedev.server.search.entity.issue.IssueQueryParser.RevisionCriteriaContext; import io.onedev.server.search.entity.issue.IssueQueryParser.RevisionCriteriaContext;
import io.onedev.server.service.SettingService;
import io.onedev.server.util.criteria.AndCriteria; import io.onedev.server.util.criteria.AndCriteria;
import io.onedev.server.util.criteria.Criteria; import io.onedev.server.util.criteria.Criteria;
import io.onedev.server.util.criteria.NotCriteria; import io.onedev.server.util.criteria.NotCriteria;
@ -363,7 +364,7 @@ public class IssueQuery extends EntityQuery<Issue> {
criterias.add(new RocketCountCriteria(getIntValue(value), operator)); criterias.add(new RocketCountCriteria(getIntValue(value), operator));
} else if (fieldName.equals(NAME_EYES_COUNT)) { } else if (fieldName.equals(NAME_EYES_COUNT)) {
criterias.add(new EyesCountCriteria(getIntValue(value), operator)); criterias.add(new EyesCountCriteria(getIntValue(value), operator));
} else if (fieldName.equals(Issue.NAME_NUMBER)) { } else if (fieldName.equals(NAME_NUMBER)) {
criterias.add(new ReferenceCriteria(project, value, operator)); criterias.add(new ReferenceCriteria(project, value, operator));
} else if (fieldName.equals(NAME_ESTIMATED_TIME)) { } else if (fieldName.equals(NAME_ESTIMATED_TIME)) {
int intValue = timeTrackingSetting.parseWorkingPeriod(value); int intValue = timeTrackingSetting.parseWorkingPeriod(value);
@ -384,13 +385,16 @@ public class IssueQuery extends EntityQuery<Issue> {
} else if (field instanceof BooleanField) { } else if (field instanceof BooleanField) {
criterias.add(new BooleanFieldCriteria(fieldName, getBooleanValue(value), operator)); criterias.add(new BooleanFieldCriteria(fieldName, getBooleanValue(value), operator));
} else if (field instanceof IntegerField) { } else if (field instanceof IntegerField) {
criterias.add(new NumericFieldCriteria(fieldName, getIntValue(value), operator)); criterias.add(new IntegerFieldCriteria(fieldName, getIntValue(value), operator));
} else if (field instanceof ChoiceField) { } else if (field instanceof ChoiceField) {
long ordinal = getValueOrdinal((ChoiceField) field, value); long ordinal = getValueOrdinal((ChoiceField) field, value);
criterias.add(new ChoiceFieldCriteria(fieldName, value, ordinal, operator, field.isAllowMultiple())); criterias.add(new ChoiceFieldCriteria(fieldName, value, ordinal, operator, field.isAllowMultiple()));
} else if (field instanceof UserChoiceField } else if (field instanceof UserChoiceField) {
|| field instanceof GroupChoiceField) { // call getUser(value) to validate the user name
criterias.add(new ChoiceFieldCriteria(fieldName, value, -1, operator, field.isAllowMultiple())); criterias.add(new ChoiceFieldCriteria(fieldName, getUser(value).getName(), -1, operator, field.isAllowMultiple()));
} else if (field instanceof GroupChoiceField) {
// call getGroup(value) to validate the group name
criterias.add(new ChoiceFieldCriteria(fieldName, getGroup(value).getName(), -1, operator, field.isAllowMultiple()));
} else if (field instanceof IterationChoiceField) { } else if (field instanceof IterationChoiceField) {
criterias.add(new IterationFieldCriteria(fieldName, value, operator, field.isAllowMultiple())); criterias.add(new IterationFieldCriteria(fieldName, value, operator, field.isAllowMultiple()));
} else { } else {
@ -443,10 +447,15 @@ public class IssueQuery extends EntityQuery<Issue> {
var floatValue = getFloatValue(value); var floatValue = getFloatValue(value);
criterias.add(new ProgressCriteria(floatValue, operator)); criterias.add(new ProgressCriteria(floatValue, operator));
break; break;
case NAME_NUMBER:
criterias.add(new ReferenceCriteria(project, value, operator));
break;
default: default:
FieldSpec field = getGlobalIssueSetting().getFieldSpec(fieldName); FieldSpec field = getGlobalIssueSetting().getFieldSpec(fieldName);
if (field instanceof IntegerField) { if (field instanceof IntegerField) {
criterias.add(new NumericFieldCriteria(fieldName, getIntValue(value), operator)); criterias.add(new IntegerFieldCriteria(fieldName, getIntValue(value), operator));
} else if (field instanceof FloatField) {
criterias.add(new FloatFieldCriteria(fieldName, getFloatValue(value), operator));
} else { } else {
long ordinal; long ordinal;
if (validate) if (validate)
@ -600,7 +609,7 @@ public class IssueQuery extends EntityQuery<Issue> {
&& !fieldName.equals(Issue.NAME_HEART_COUNT) && !fieldName.equals(Issue.NAME_HEART_COUNT)
&& !fieldName.equals(Issue.NAME_ROCKET_COUNT) && !fieldName.equals(Issue.NAME_ROCKET_COUNT)
&& !fieldName.equals(Issue.NAME_EYES_COUNT) && !fieldName.equals(Issue.NAME_EYES_COUNT)
&& !fieldName.equals(Issue.NAME_NUMBER) && !fieldName.equals(NAME_NUMBER)
&& !fieldName.equals(IssueSchedule.NAME_ITERATION) && !fieldName.equals(IssueSchedule.NAME_ITERATION)
&& !(fieldSpec instanceof IssueChoiceField) && !(fieldSpec instanceof IssueChoiceField)
&& !(fieldSpec instanceof PullRequestChoiceField) && !(fieldSpec instanceof PullRequestChoiceField)
@ -636,8 +645,9 @@ public class IssueQuery extends EntityQuery<Issue> {
&& !fieldName.equals(Issue.NAME_HEART_COUNT) && !fieldName.equals(Issue.NAME_HEART_COUNT)
&& !fieldName.equals(Issue.NAME_ROCKET_COUNT) && !fieldName.equals(Issue.NAME_ROCKET_COUNT)
&& !fieldName.equals(Issue.NAME_EYES_COUNT) && !fieldName.equals(Issue.NAME_EYES_COUNT)
&& !fieldName.equals(Issue.NAME_NUMBER) && !fieldName.equals(NAME_NUMBER)
&& !(fieldSpec instanceof IntegerField) && !(fieldSpec instanceof IntegerField)
&& !(fieldSpec instanceof FloatField)
&& !(fieldSpec instanceof ChoiceField && !fieldSpec.isAllowMultiple())) && !(fieldSpec instanceof ChoiceField && !fieldSpec.isAllowMultiple()))
throw newOperatorException(fieldName, operator); throw newOperatorException(fieldName, operator);
break; break;

View File

@ -16,7 +16,6 @@ import io.onedev.server.model.Issue;
import io.onedev.server.util.ProjectScope; import io.onedev.server.util.ProjectScope;
import io.onedev.server.util.criteria.Criteria; import io.onedev.server.util.criteria.Criteria;
public class SpentTimeCriteria extends Criteria<Issue> { public class SpentTimeCriteria extends Criteria<Issue> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -8,7 +8,6 @@ import javax.persistence.criteria.Predicate;
import io.onedev.server.model.Issue; import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueField; import io.onedev.server.model.IssueField;
public class StringFieldCriteria extends FieldCriteria { public class StringFieldCriteria extends FieldCriteria {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -1,11 +1,13 @@
package io.onedev.server.search.entity.project; package io.onedev.server.search.entity.project;
import org.jspecify.annotations.Nullable;
import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.From; import javax.persistence.criteria.From;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Predicate;
import org.jspecify.annotations.Nullable;
import io.onedev.server.model.Project; import io.onedev.server.model.Project;
import io.onedev.server.util.ProjectScope; import io.onedev.server.util.ProjectScope;
import io.onedev.server.util.criteria.Criteria; import io.onedev.server.util.criteria.Criteria;
@ -25,18 +27,27 @@ public class IdCriteria extends Criteria<Project> {
@Override @Override
public Predicate getPredicate(@Nullable ProjectScope projectScope, CriteriaQuery<?> query, From<Project, Project> from, CriteriaBuilder builder) { public Predicate getPredicate(@Nullable ProjectScope projectScope, CriteriaQuery<?> query, From<Project, Project> from, CriteriaBuilder builder) {
Path<Long> attribute = from.get(Project.PROP_ID);
if (operator == ProjectQueryLexer.Is) if (operator == ProjectQueryLexer.Is)
return builder.equal(from.get(Project.PROP_ID), value); return builder.equal(attribute, value);
else if (operator == ProjectQueryLexer.IsNot)
return builder.not(builder.equal(attribute, value));
else if (operator == ProjectQueryLexer.IsGreaterThan)
return builder.greaterThan(attribute, value);
else else
return builder.not(builder.equal(from.get(Project.PROP_ID), value)); return builder.lessThan(attribute, value);
} }
@Override @Override
public boolean matches(Project project) { public boolean matches(Project project) {
if (operator == ProjectQueryLexer.Is) if (operator == ProjectQueryLexer.Is)
return project.getId().equals(value); return project.getId().equals(value);
else else if (operator == ProjectQueryLexer.IsNot)
return !project.getId().equals(value); return !project.getId().equals(value);
else if (operator == ProjectQueryLexer.IsGreaterThan)
return project.getId() > value;
else
return project.getId() < value;
} }
@Override @Override

View File

@ -9,7 +9,7 @@ query
criteria criteria
: operator=(Roots|Leafs|ForkRoots|OwnedByMe|OwnedByNone|HasOutdatedReplicas|WithoutEnoughReplicas|MissingStorage) #OperatorCriteria : operator=(Roots|Leafs|ForkRoots|OwnedByMe|OwnedByNone|HasOutdatedReplicas|WithoutEnoughReplicas|MissingStorage) #OperatorCriteria
| operator=(OwnedBy|ForksOf|ChildrenOf) WS+ criteriaValue=multipleQuoted #OperatorValueCriteria | operator=(OwnedBy|ForksOf|ChildrenOf) WS+ criteriaValue=multipleQuoted #OperatorValueCriteria
| criteriaField=Quoted WS+ operator=(Is|IsNot|Contains|IsUntil|IsSince) WS+ criteriaValue=multipleQuoted #FieldOperatorValueCriteria | criteriaField=Quoted WS+ operator=(Is|IsNot|IsGreaterThan|IsLessThan|Contains|IsUntil|IsSince) WS+ criteriaValue=multipleQuoted #FieldOperatorValueCriteria
| criteria WS+ And WS+ criteria #AndCriteria | criteria WS+ And WS+ criteria #AndCriteria
| criteria WS+ Or WS+ criteria #OrCriteria | criteria WS+ Or WS+ criteria #OrCriteria
| Not WS* LParens WS* criteria WS* RParens #NotCriteria | Not WS* LParens WS* criteria WS* RParens #NotCriteria
@ -37,6 +37,14 @@ IsNot
: 'is' WS+ 'not' : 'is' WS+ 'not'
; ;
IsGreaterThan
: 'is' WS+ 'greater' WS+ 'than'
;
IsLessThan
: 'is' WS+ 'less' WS+ 'than'
;
OwnedBy OwnedBy
: 'owned' WS+ 'by' : 'owned' WS+ 'by'
; ;

View File

@ -16,6 +16,8 @@ import static io.onedev.server.search.entity.project.ProjectQueryParser.Leafs;
import static io.onedev.server.search.entity.project.ProjectQueryParser.MissingStorage; import static io.onedev.server.search.entity.project.ProjectQueryParser.MissingStorage;
import static io.onedev.server.search.entity.project.ProjectQueryParser.OwnedByMe; import static io.onedev.server.search.entity.project.ProjectQueryParser.OwnedByMe;
import static io.onedev.server.search.entity.project.ProjectQueryParser.OwnedByNone; import static io.onedev.server.search.entity.project.ProjectQueryParser.OwnedByNone;
import static io.onedev.server.search.entity.project.ProjectQueryParser.IsGreaterThan;
import static io.onedev.server.search.entity.project.ProjectQueryParser.IsLessThan;
import static io.onedev.server.search.entity.project.ProjectQueryParser.Roots; import static io.onedev.server.search.entity.project.ProjectQueryParser.Roots;
import static io.onedev.server.search.entity.project.ProjectQueryParser.WithoutEnoughReplicas; import static io.onedev.server.search.entity.project.ProjectQueryParser.WithoutEnoughReplicas;
@ -177,6 +179,10 @@ public class ProjectQuery extends EntityQuery<Project> {
criterias.add(new LabelCriteria(getLabelSpec(value), operator)); criterias.add(new LabelCriteria(getLabelSpec(value), operator));
} }
break; break;
case IsGreaterThan:
case IsLessThan:
criterias.add(new IdCriteria(getLongValue(value), operator));
break;
case Contains: case Contains:
criterias.add(new DescriptionCriteria(value)); criterias.add(new DescriptionCriteria(value));
break; break;
@ -266,6 +272,12 @@ public class ProjectQuery extends EntityQuery<Project> {
throw newOperatorException(fieldName, operator); throw newOperatorException(fieldName, operator);
} }
break; break;
case IsGreaterThan:
case IsLessThan:
if (!fieldName.equals(Project.NAME_ID)) {
throw newOperatorException(fieldName, operator);
}
break;
case IsUntil: case IsUntil:
case IsSince: case IsSince:
if (!fieldName.equals(Project.NAME_LAST_ACTIVITY_DATE) if (!fieldName.equals(Project.NAME_LAST_ACTIVITY_DATE)

View File

@ -1,7 +1,16 @@
package io.onedev.server.web.behavior; package io.onedev.server.web.behavior;
import static io.onedev.server.web.translation.Translation._T;
import static java.util.Collections.sort;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.onedev.commons.codeassist.FenceAware; import io.onedev.commons.codeassist.FenceAware;
import io.onedev.commons.codeassist.InputCompletion; import io.onedev.commons.codeassist.InputCompletion;
import io.onedev.commons.codeassist.InputSuggestion; import io.onedev.commons.codeassist.InputSuggestion;
@ -11,23 +20,19 @@ import io.onedev.commons.codeassist.parser.ParseExpect;
import io.onedev.commons.codeassist.parser.TerminalExpect; import io.onedev.commons.codeassist.parser.TerminalExpect;
import io.onedev.commons.utils.ExplicitException; import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.OneDev; import io.onedev.server.OneDev;
import io.onedev.server.service.AgentAttributeService; import io.onedev.server.ai.QueryDescriptions;
import io.onedev.server.service.AgentService;
import io.onedev.server.model.Agent; import io.onedev.server.model.Agent;
import io.onedev.server.search.entity.agent.AgentQuery; import io.onedev.server.search.entity.agent.AgentQuery;
import io.onedev.server.search.entity.agent.AgentQueryLexer; import io.onedev.server.search.entity.agent.AgentQueryLexer;
import io.onedev.server.search.entity.agent.AgentQueryParser; import io.onedev.server.search.entity.agent.AgentQueryParser;
import io.onedev.server.service.AgentAttributeService;
import io.onedev.server.service.AgentService;
import io.onedev.server.service.SettingService;
import io.onedev.server.util.DateUtils; import io.onedev.server.util.DateUtils;
import io.onedev.server.web.behavior.inputassist.ANTLRAssistBehavior; import io.onedev.server.web.behavior.inputassist.ANTLRAssistBehavior;
import io.onedev.server.web.behavior.inputassist.InputAssistBehavior; import io.onedev.server.web.behavior.inputassist.InputAssistBehavior;
import io.onedev.server.web.behavior.inputassist.NaturalLanguageTranslator;
import io.onedev.server.web.util.SuggestionUtils; import io.onedev.server.web.util.SuggestionUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import static io.onedev.server.web.translation.Translation._T;
import static java.util.Collections.sort;
public class AgentQueryBehavior extends ANTLRAssistBehavior { public class AgentQueryBehavior extends ANTLRAssistBehavior {
@ -48,11 +53,11 @@ public class AgentQueryBehavior extends ANTLRAssistBehavior {
@Override @Override
protected List<InputSuggestion> match(String matchWith) { protected List<InputSuggestion> match(String matchWith) {
AgentService agentService = OneDev.getInstance(AgentService.class); AgentService agentService = OneDev.getInstance(AgentService.class);
AgentAttributeService attributeManager = OneDev.getInstance(AgentAttributeService.class); AgentAttributeService attributeService = OneDev.getInstance(AgentAttributeService.class);
ParseExpect criteriaValueExpect; ParseExpect criteriaValueExpect;
if ("criteriaField".equals(spec.getLabel())) { if ("criteriaField".equals(spec.getLabel())) {
var fields = new ArrayList<>(Agent.QUERY_FIELDS); var fields = new ArrayList<>(Agent.QUERY_FIELDS);
var attributeNames = new ArrayList<>(attributeManager.getAttributeNames()); var attributeNames = new ArrayList<>(attributeService.getAttributeNames());
sort(attributeNames); sort(attributeNames);
fields.addAll(attributeNames); fields.addAll(attributeNames);
return SuggestionUtils.suggest(fields, matchWith); return SuggestionUtils.suggest(fields, matchWith);
@ -69,7 +74,7 @@ public class AgentQueryBehavior extends ANTLRAssistBehavior {
if (operator == AgentQueryLexer.EverUsedSince || operator == AgentQueryLexer.NotUsedSince) { if (operator == AgentQueryLexer.EverUsedSince || operator == AgentQueryLexer.NotUsedSince) {
return SuggestionUtils.suggest(DateUtils.RELAX_DATE_EXAMPLES, matchWith); return SuggestionUtils.suggest(DateUtils.RELAX_DATE_EXAMPLES, matchWith);
} else if (operator == AgentQueryLexer.HasAttribute) { } else if (operator == AgentQueryLexer.HasAttribute) {
var attributeNames = new ArrayList<>(attributeManager.getAttributeNames()); var attributeNames = new ArrayList<>(attributeService.getAttributeNames());
sort(attributeNames); sort(attributeNames);
return SuggestionUtils.suggest(attributeNames, matchWith); return SuggestionUtils.suggest(attributeNames, matchWith);
} else if (operator == AgentQueryLexer.RanBuild) { } else if (operator == AgentQueryLexer.RanBuild) {
@ -184,6 +189,27 @@ public class AgentQueryBehavior extends ANTLRAssistBehavior {
return hints; return hints;
} }
@Override
protected NaturalLanguageTranslator getNaturalLanguageTranslator() {
var liteModel = getSettingService().getAISetting().getLiteModel();
if (liteModel != null) {
return new NaturalLanguageTranslator(liteModel) {
@Override
public String getQueryDescription() {
return QueryDescriptions.getAgentQueryDescription();
}
};
} else {
return null;
}
}
private SettingService getSettingService() {
return OneDev.getInstance(SettingService.class);
}
@Override @Override
protected boolean isFuzzySuggestion(InputCompletion suggestion) { protected boolean isFuzzySuggestion(InputCompletion suggestion) {
return suggestion.getLabel().startsWith("~") return suggestion.getLabel().startsWith("~")

View File

@ -25,12 +25,15 @@ import io.onedev.commons.utils.StringUtils;
import io.onedev.commons.utils.match.PatternApplied; import io.onedev.commons.utils.match.PatternApplied;
import io.onedev.commons.utils.match.WildcardUtils; import io.onedev.commons.utils.match.WildcardUtils;
import io.onedev.server.OneDev; import io.onedev.server.OneDev;
import io.onedev.server.ai.QueryDescriptions;
import io.onedev.server.model.Project; import io.onedev.server.model.Project;
import io.onedev.server.search.commit.CommitQueryParser; import io.onedev.server.search.commit.CommitQueryParser;
import io.onedev.server.service.SettingService;
import io.onedev.server.util.DateUtils; import io.onedev.server.util.DateUtils;
import io.onedev.server.util.NameAndEmail; import io.onedev.server.util.NameAndEmail;
import io.onedev.server.web.behavior.inputassist.ANTLRAssistBehavior; import io.onedev.server.web.behavior.inputassist.ANTLRAssistBehavior;
import io.onedev.server.web.behavior.inputassist.InputAssistBehavior; import io.onedev.server.web.behavior.inputassist.InputAssistBehavior;
import io.onedev.server.web.behavior.inputassist.NaturalLanguageTranslator;
import io.onedev.server.web.util.SuggestionUtils; import io.onedev.server.web.util.SuggestionUtils;
import io.onedev.server.xodus.CommitInfoService; import io.onedev.server.xodus.CommitInfoService;
@ -194,6 +197,27 @@ public class CommitQueryBehavior extends ANTLRAssistBehavior {
return Optional.fromNullable(description); return Optional.fromNullable(description);
} }
@Override
protected NaturalLanguageTranslator getNaturalLanguageTranslator() {
var liteModel = getSettingService().getAISetting().getLiteModel();
if (liteModel != null) {
return new NaturalLanguageTranslator(liteModel) {
@Override
public String getQueryDescription() {
return QueryDescriptions.getCommitQueryDescription();
}
};
} else {
return null;
}
}
private SettingService getSettingService() {
return OneDev.getInstance(SettingService.class);
}
@Override @Override
protected boolean isFuzzySuggestion(InputCompletion suggestion) { protected boolean isFuzzySuggestion(InputCompletion suggestion) {
return suggestion.getLabel().startsWith("~") return suggestion.getLabel().startsWith("~")

View File

@ -1,7 +1,24 @@
package io.onedev.server.web.behavior; package io.onedev.server.web.behavior;
import static io.onedev.server.model.Pack.NAME_LABEL;
import static io.onedev.server.model.Pack.NAME_NAME;
import static io.onedev.server.model.Pack.NAME_TYPE;
import static io.onedev.server.model.Pack.NAME_VERSION;
import static io.onedev.server.model.Pack.PROP_NAME;
import static io.onedev.server.model.Pack.PROP_VERSION;
import static io.onedev.server.web.translation.Translation._T;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.wicket.Component;
import org.apache.wicket.model.IModel;
import org.jspecify.annotations.Nullable;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.onedev.commons.codeassist.AntlrUtils; import io.onedev.commons.codeassist.AntlrUtils;
import io.onedev.commons.codeassist.FenceAware; import io.onedev.commons.codeassist.FenceAware;
import io.onedev.commons.codeassist.InputCompletion; import io.onedev.commons.codeassist.InputCompletion;
@ -11,25 +28,19 @@ import io.onedev.commons.codeassist.parser.Element;
import io.onedev.commons.codeassist.parser.ParseExpect; import io.onedev.commons.codeassist.parser.ParseExpect;
import io.onedev.commons.codeassist.parser.TerminalExpect; import io.onedev.commons.codeassist.parser.TerminalExpect;
import io.onedev.commons.utils.ExplicitException; import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.OneDev;
import io.onedev.server.ai.QueryDescriptions;
import io.onedev.server.model.Pack; import io.onedev.server.model.Pack;
import io.onedev.server.model.Project; import io.onedev.server.model.Project;
import io.onedev.server.search.entity.pack.PackQuery; import io.onedev.server.search.entity.pack.PackQuery;
import io.onedev.server.search.entity.pack.PackQueryLexer; import io.onedev.server.search.entity.pack.PackQueryLexer;
import io.onedev.server.search.entity.pack.PackQueryParser; import io.onedev.server.search.entity.pack.PackQueryParser;
import io.onedev.server.service.SettingService;
import io.onedev.server.util.DateUtils; import io.onedev.server.util.DateUtils;
import io.onedev.server.web.behavior.inputassist.ANTLRAssistBehavior; import io.onedev.server.web.behavior.inputassist.ANTLRAssistBehavior;
import io.onedev.server.web.behavior.inputassist.InputAssistBehavior; import io.onedev.server.web.behavior.inputassist.InputAssistBehavior;
import io.onedev.server.web.behavior.inputassist.NaturalLanguageTranslator;
import io.onedev.server.web.util.SuggestionUtils; import io.onedev.server.web.util.SuggestionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.wicket.Component;
import org.apache.wicket.model.IModel;
import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import static io.onedev.server.model.Pack.*;
import static io.onedev.server.web.translation.Translation._T;
public class PackQueryBehavior extends ANTLRAssistBehavior { public class PackQueryBehavior extends ANTLRAssistBehavior {
@ -218,6 +229,27 @@ public class PackQueryBehavior extends ANTLRAssistBehavior {
return hints; return hints;
} }
@Override
protected NaturalLanguageTranslator getNaturalLanguageTranslator() {
var liteModel = getSettingService().getAISetting().getLiteModel();
if (liteModel != null) {
return new NaturalLanguageTranslator(liteModel) {
@Override
public String getQueryDescription() {
return QueryDescriptions.getPackQueryDescription();
}
};
} else {
return null;
}
}
private SettingService getSettingService() {
return OneDev.getInstance(SettingService.class);
}
@Override @Override
protected boolean isFuzzySuggestion(InputCompletion suggestion) { protected boolean isFuzzySuggestion(InputCompletion suggestion) {
return suggestion.getLabel().startsWith("~") return suggestion.getLabel().startsWith("~")

View File

@ -1,7 +1,22 @@
package io.onedev.server.web.behavior; package io.onedev.server.web.behavior;
import static io.onedev.server.search.entity.project.ProjectQuery.getRuleName;
import static io.onedev.server.search.entity.project.ProjectQueryParser.ChildrenOf;
import static io.onedev.server.search.entity.project.ProjectQueryParser.HasOutdatedReplicas;
import static io.onedev.server.search.entity.project.ProjectQueryParser.Roots;
import static io.onedev.server.search.entity.project.ProjectQueryParser.WithoutEnoughReplicas;
import static io.onedev.server.web.translation.Translation._T;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.onedev.commons.codeassist.FenceAware; import io.onedev.commons.codeassist.FenceAware;
import io.onedev.commons.codeassist.InputCompletion; import io.onedev.commons.codeassist.InputCompletion;
import io.onedev.commons.codeassist.InputSuggestion; import io.onedev.commons.codeassist.InputSuggestion;
@ -11,29 +26,21 @@ import io.onedev.commons.codeassist.parser.ParseExpect;
import io.onedev.commons.codeassist.parser.TerminalExpect; import io.onedev.commons.codeassist.parser.TerminalExpect;
import io.onedev.commons.utils.ExplicitException; import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.OneDev; import io.onedev.server.OneDev;
import io.onedev.server.ai.QueryDescriptions;
import io.onedev.server.cluster.ClusterService; import io.onedev.server.cluster.ClusterService;
import io.onedev.server.service.ProjectService;
import io.onedev.server.service.SettingService;
import io.onedev.server.model.Project; import io.onedev.server.model.Project;
import io.onedev.server.search.entity.project.ProjectQuery; import io.onedev.server.search.entity.project.ProjectQuery;
import io.onedev.server.search.entity.project.ProjectQueryLexer; import io.onedev.server.search.entity.project.ProjectQueryLexer;
import io.onedev.server.search.entity.project.ProjectQueryParser; import io.onedev.server.search.entity.project.ProjectQueryParser;
import io.onedev.server.security.SecurityUtils; import io.onedev.server.security.SecurityUtils;
import io.onedev.server.security.permission.AccessProject; import io.onedev.server.security.permission.AccessProject;
import io.onedev.server.service.ProjectService;
import io.onedev.server.service.SettingService;
import io.onedev.server.util.DateUtils; import io.onedev.server.util.DateUtils;
import io.onedev.server.util.facade.ProjectCache; import io.onedev.server.util.facade.ProjectCache;
import io.onedev.server.web.behavior.inputassist.ANTLRAssistBehavior; import io.onedev.server.web.behavior.inputassist.ANTLRAssistBehavior;
import io.onedev.server.web.behavior.inputassist.NaturalLanguageTranslator;
import io.onedev.server.web.util.SuggestionUtils; import io.onedev.server.web.util.SuggestionUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import static io.onedev.server.search.entity.project.ProjectQuery.getRuleName;
import static io.onedev.server.search.entity.project.ProjectQueryParser.*;
import static io.onedev.server.web.translation.Translation._T;
public class ProjectQueryBehavior extends ANTLRAssistBehavior { public class ProjectQueryBehavior extends ANTLRAssistBehavior {
@ -221,6 +228,27 @@ public class ProjectQueryBehavior extends ANTLRAssistBehavior {
return hints; return hints;
} }
@Override
protected NaturalLanguageTranslator getNaturalLanguageTranslator() {
var liteModel = getSettingService().getAISetting().getLiteModel();
if (liteModel != null) {
return new NaturalLanguageTranslator(liteModel) {
@Override
public String getQueryDescription() {
return QueryDescriptions.getProjectQueryDescription();
}
};
} else {
return null;
}
}
private SettingService getSettingService() {
return OneDev.getInstance(SettingService.class);
}
@Override @Override
protected boolean isFuzzySuggestion(InputCompletion suggestion) { protected boolean isFuzzySuggestion(InputCompletion suggestion) {
return suggestion.getLabel().startsWith("~") return suggestion.getLabel().startsWith("~")