-
-
Notifications
You must be signed in to change notification settings - Fork 9.3k
[JENKINS-26097] Adjust label expression auto-completion and validation #4774
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d7a89ae
9cbd50a
ab7fa7d
14d1efb
bb89a72
3fb2b66
5519088
0428cd8
9a65888
7a3d722
863eae5
f0621c2
dc286c7
d542a76
df6c373
0712143
1a0aea1
6cfba40
a94cb38
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -23,8 +23,24 @@ | |||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
| package hudson.model.labels; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| import antlr.ANTLRException; | ||||||||||||||||||||||||||
| import edu.umd.cs.findbugs.annotations.CheckForNull; | ||||||||||||||||||||||||||
| import edu.umd.cs.findbugs.annotations.NonNull; | ||||||||||||||||||||||||||
| import edu.umd.cs.findbugs.annotations.Nullable; | ||||||||||||||||||||||||||
| import hudson.Util; | ||||||||||||||||||||||||||
| import hudson.model.AbstractProject; | ||||||||||||||||||||||||||
| import hudson.model.AutoCompletionCandidates; | ||||||||||||||||||||||||||
| import hudson.model.Item; | ||||||||||||||||||||||||||
| import hudson.model.Label; | ||||||||||||||||||||||||||
| import hudson.model.Messages; | ||||||||||||||||||||||||||
| import hudson.util.FormValidation; | ||||||||||||||||||||||||||
| import hudson.util.VariableResolver; | ||||||||||||||||||||||||||
| import java.util.ArrayList; | ||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||
| import java.util.Set; | ||||||||||||||||||||||||||
| import jenkins.model.Jenkins; | ||||||||||||||||||||||||||
| import jenkins.model.labels.LabelAutoCompleteSeeder; | ||||||||||||||||||||||||||
| import jenkins.model.labels.LabelValidator; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||
| * Boolean expression of labels. | ||||||||||||||||||||||||||
|
|
@@ -210,4 +226,103 @@ public LabelOperatorPrecedence precedence() { | |||||||||||||||||||||||||
| return LabelOperatorPrecedence.IMPLIES; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| //region Auto-Completion and Validation | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||
| * Generates auto-completion candidates for a (partial) label. | ||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||
| * @param label The (partial) label for which auto-completion is being requested. | ||||||||||||||||||||||||||
| * @return A set of auto-completion candidates. | ||||||||||||||||||||||||||
| * @since TODO | ||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
| @NonNull | ||||||||||||||||||||||||||
| public static AutoCompletionCandidates autoComplete(@Nullable String label) { | ||||||||||||||||||||||||||
Zastai marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
| AutoCompletionCandidates c = new AutoCompletionCandidates(); | ||||||||||||||||||||||||||
| Set<Label> labels = Jenkins.get().getLabels(); | ||||||||||||||||||||||||||
| List<String> queries = new LabelAutoCompleteSeeder(Util.fixNull(label)).getSeeds(); | ||||||||||||||||||||||||||
| for (String term : queries) { | ||||||||||||||||||||||||||
| for (Label l : labels) { | ||||||||||||||||||||||||||
| if (l.getName().startsWith(term)) { | ||||||||||||||||||||||||||
| c.add(l.getName()); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| return c; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||
| * Validates a label expression. | ||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||
| * @param expression The expression to validate. | ||||||||||||||||||||||||||
| * @return The validation result. | ||||||||||||||||||||||||||
| * @since TODO | ||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
| @NonNull | ||||||||||||||||||||||||||
| public static FormValidation validate(@Nullable String expression) { | ||||||||||||||||||||||||||
| return LabelExpression.validate(expression, null); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||
| * Validates a label expression. | ||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||
| * @param expression The label expression to validate. | ||||||||||||||||||||||||||
| * @param item The context item (like a job or a folder), if applicable; used for potential additional | ||||||||||||||||||||||||||
| * restrictions via {@link LabelValidator} instances. | ||||||||||||||||||||||||||
| * @return The validation result. | ||||||||||||||||||||||||||
| * @since TODO | ||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
| // FIXME: Should the messages be moved, or kept where they are for backward compatibility? | ||||||||||||||||||||||||||
| @NonNull | ||||||||||||||||||||||||||
| public static FormValidation validate(@Nullable String expression, @CheckForNull Item item) { | ||||||||||||||||||||||||||
| if (Util.fixEmptyAndTrim(expression) == null) { | ||||||||||||||||||||||||||
| return FormValidation.ok(); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||
| Label.parseExpression(expression); | ||||||||||||||||||||||||||
| } catch (ANTLRException e) { | ||||||||||||||||||||||||||
| return FormValidation.error(e, Messages.LabelExpression_InvalidBooleanExpression(e.getMessage())); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| final Jenkins j = Jenkins.get(); | ||||||||||||||||||||||||||
| Label l = j.getLabel(expression); | ||||||||||||||||||||||||||
| if (l.isEmpty()) { | ||||||||||||||||||||||||||
| for (LabelAtom a : l.listAtoms()) { | ||||||||||||||||||||||||||
| if (a.isEmpty()) { | ||||||||||||||||||||||||||
| LabelAtom nearest = LabelAtom.findNearest(a.getName()); | ||||||||||||||||||||||||||
| return FormValidation.warning(Messages.LabelExpression_NoMatch_DidYouMean(a.getName(), nearest.getDisplayName())); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| return FormValidation.warning(Messages.LabelExpression_NoMatch()); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| if (item != null) { | ||||||||||||||||||||||||||
| final List<FormValidation> problems = new ArrayList<>(); | ||||||||||||||||||||||||||
| // Use the project-oriented validators too, so that validation from older plugins still gets applied. | ||||||||||||||||||||||||||
| for (AbstractProject.LabelValidator v : j.getExtensionList(AbstractProject.LabelValidator.class)) { | ||||||||||||||||||||||||||
| FormValidation result = v.checkItem(item, l); | ||||||||||||||||||||||||||
| if (FormValidation.Kind.OK.equals(result.kind)) { | ||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BTW I think if jenkins/core/src/main/java/hudson/util/FormValidation.java Lines 227 to 238 in 339f9b9
FormValidation.OK in its input, then the API and this impl could be simplified a bit while actually handling ok(String) from validators. Not necessary in this PR, just something I noticed while looking at aggregate.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes I looked there too. But I'm not sure it would help to get bulleted list entries for "Label is valid" (assuming a validator might put that in the OK text) among the errors/warnings.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right—
|
||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| problems.add(result); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| // And then use the new validators. | ||||||||||||||||||||||||||
| for (LabelValidator v : j.getExtensionList(LabelValidator.class)) { | ||||||||||||||||||||||||||
| FormValidation result = v.check(item, l); | ||||||||||||||||||||||||||
| if (FormValidation.Kind.OK.equals(result.kind)) { | ||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| problems.add(result); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| // If there were any problems, report them all. | ||||||||||||||||||||||||||
| if (!problems.isEmpty()) { | ||||||||||||||||||||||||||
| return FormValidation.aggregate(problems); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| // All done. Report the results. | ||||||||||||||||||||||||||
| return FormValidation.okWithMarkup(Messages.LabelExpression_LabelLink( | ||||||||||||||||||||||||||
| j.getRootUrl(), Util.escape(l.getName()), l.getUrl(), l.getNodes().size(), l.getClouds().size()) | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| //endregion | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| package jenkins.model.labels; | ||
|
|
||
| import edu.umd.cs.findbugs.annotations.NonNull; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import org.kohsuke.accmod.Restricted; | ||
| import org.kohsuke.accmod.restrictions.NoExternalUse; | ||
|
|
||
| /** | ||
| * Utility class for taking the current input value and computing a list of potential terms to match against the | ||
| * list of defined labels. | ||
| */ | ||
| @Restricted(NoExternalUse.class) | ||
| public class LabelAutoCompleteSeeder { | ||
|
|
||
| private final String source; | ||
|
|
||
| /** | ||
| * Creates a new auto-complete seeder for labels. | ||
| * | ||
| * @param source The (partial) label expression to use as the source.. | ||
| */ | ||
| public LabelAutoCompleteSeeder(@NonNull String source) { | ||
| this.source = source; | ||
| } | ||
|
|
||
| /** | ||
| * Gets a list of seeds for label auto-completion. | ||
| * | ||
| * @return A list of seeds for label auto-completion. | ||
| */ | ||
| @NonNull | ||
| public List<String> getSeeds() { | ||
| final ArrayList<String> terms = new ArrayList<>(); | ||
| boolean trailingQuote = source.endsWith("\""); | ||
| boolean leadingQuote = source.startsWith("\""); | ||
| boolean trailingSpace = source.endsWith(" "); | ||
| if (trailingQuote || (trailingSpace && !leadingQuote)) { | ||
| terms.add(""); | ||
| } else { | ||
| if (leadingQuote) { | ||
| int quote = source.lastIndexOf('"'); | ||
| if (quote == 0) { | ||
| terms.add(source.substring(1)); | ||
| } else { | ||
| terms.add(""); | ||
| } | ||
| } else { | ||
| int space = source.lastIndexOf(' '); | ||
| if (space > -1) { | ||
| terms.add(source.substring(space+1)); | ||
| } else { | ||
| terms.add(source); | ||
| } | ||
| } | ||
| } | ||
| return terms; | ||
| } | ||
|
|
||
| } |
Uh oh!
There was an error while loading. Please reload this page.