forked from jenkinsci/jenkins
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[JENKINS-69853] User experimental flags (jenkinsci#7299)
* [JENKINS-69853] User experimental flags * Applying feedbacks from Tim * Improve default wording + padding + title * Remove the "UI" * Correct Spotbugs --------- Co-authored-by: Alexander Brandes <[email protected]> Co-authored-by: Tim Jacomb <[email protected]>
- Loading branch information
1 parent
6c076b9
commit af09d67
Showing
14 changed files
with
963 additions
and
0 deletions.
There are no files selected for viewing
60 changes: 60 additions & 0 deletions
60
core/src/main/java/jenkins/model/experimentalflags/BooleanUserExperimentalFlag.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
* The MIT License | ||
* | ||
* Copyright (c) 2022, CloudBees, Inc. | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* of this software and associated documentation files (the "Software"), to deal | ||
* in the Software without restriction, including without limitation the rights | ||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
* copies of the Software, and to permit persons to whom the Software is | ||
* furnished to do so, subject to the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be included in | ||
* all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
* THE SOFTWARE. | ||
*/ | ||
|
||
package jenkins.model.experimentalflags; | ||
|
||
import edu.umd.cs.findbugs.annotations.NonNull; | ||
|
||
/** | ||
* @since TODO | ||
*/ | ||
public abstract class BooleanUserExperimentalFlag extends UserExperimentalFlag<Boolean> { | ||
protected BooleanUserExperimentalFlag(@NonNull String flagKey) { | ||
super(flagKey); | ||
} | ||
|
||
@Override | ||
public @NonNull Boolean getDefaultValue() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public Object serializeValue(Boolean rawValue) { | ||
if (rawValue == null) { | ||
return null; | ||
} | ||
return rawValue ? "true" : "false"; | ||
} | ||
|
||
@Override | ||
protected Boolean deserializeValue(Object serializedValue) { | ||
if (serializedValue.equals("true")) { | ||
return Boolean.TRUE; | ||
} | ||
if (serializedValue.equals("false")) { | ||
return Boolean.FALSE; | ||
} | ||
return null; | ||
} | ||
} |
149 changes: 149 additions & 0 deletions
149
core/src/main/java/jenkins/model/experimentalflags/UserExperimentalFlag.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/* | ||
* The MIT License | ||
* | ||
* Copyright (c) 2022, CloudBees, Inc. | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* of this software and associated documentation files (the "Software"), to deal | ||
* in the Software without restriction, including without limitation the rights | ||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
* copies of the Software, and to permit persons to whom the Software is | ||
* furnished to do so, subject to the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be included in | ||
* all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
* THE SOFTWARE. | ||
*/ | ||
|
||
package jenkins.model.experimentalflags; | ||
|
||
import edu.umd.cs.findbugs.annotations.CheckForNull; | ||
import edu.umd.cs.findbugs.annotations.NonNull; | ||
import edu.umd.cs.findbugs.annotations.Nullable; | ||
import hudson.ExtensionList; | ||
import hudson.ExtensionPoint; | ||
import hudson.model.User; | ||
|
||
/** | ||
* User specific experimental flag to enable or disable specific behavior. | ||
* As it's user specific, usually this kind of feature flag is only used for UI. | ||
* | ||
* @since TODO | ||
*/ | ||
public abstract class UserExperimentalFlag<T> implements ExtensionPoint { | ||
private final String flagKey; | ||
|
||
protected UserExperimentalFlag(@NonNull String flagKey) { | ||
this.flagKey = flagKey; | ||
} | ||
|
||
public abstract @NonNull T getDefaultValue(); | ||
|
||
/** | ||
* Convert the usable value into a serializable form that can be stored in the user property. | ||
* If no changes are necessary, simply returning the {@code rawValue} is fine. | ||
*/ | ||
public abstract @Nullable Object serializeValue(T rawValue); | ||
|
||
/** | ||
* Convert the serialized value into the usable instance. | ||
* If the instance is invalid (like after migration), | ||
* returning {@code null} will force to return the {@link #getDefaultValue()} | ||
*/ | ||
protected abstract @Nullable T deserializeValue(Object serializedValue); | ||
|
||
/** | ||
* The name that will be used in the configuration page for that flag | ||
* It must be user readable | ||
*/ | ||
public abstract String getDisplayName(); | ||
|
||
/** | ||
* Describe what the flag is changing depending on its value. | ||
* This method is called in description.jelly, which could be overloaded by children. | ||
* It could return HTML content. | ||
*/ | ||
public abstract @Nullable String getShortDescription(); | ||
|
||
/** | ||
* The ID used by the machine to link the flag with its value within the user properties | ||
*/ | ||
public @NonNull String getFlagKey() { | ||
return flagKey; | ||
} | ||
|
||
public @NonNull T getFlagValue() { | ||
User currentUser = User.current(); | ||
if (currentUser == null) { | ||
// the anonymous user is not expected to use flags | ||
return this.getDefaultValue(); | ||
} | ||
return this.getFlagValue(currentUser); | ||
} | ||
|
||
public @NonNull T getFlagValue(User user) { | ||
UserExperimentalFlagsProperty property = user.getProperty(UserExperimentalFlagsProperty.class); | ||
if (property == null) { | ||
// if for whatever reason there is no such property | ||
return this.getDefaultValue(); | ||
} | ||
|
||
Object value = property.getFlagValue(this.flagKey); | ||
if (value == null) { | ||
return this.getDefaultValue(); | ||
} | ||
|
||
T convertedValue = this.deserializeValue(value); | ||
if (convertedValue == null) { | ||
return this.getDefaultValue(); | ||
} | ||
return convertedValue; | ||
} | ||
|
||
public String getFlagDescriptionPage() { | ||
return "flagDescription.jelly"; | ||
} | ||
|
||
public String getFlagConfigPage() { | ||
return "flagConfig.jelly"; | ||
} | ||
|
||
@NonNull | ||
@SuppressWarnings("rawtypes") | ||
public static ExtensionList<UserExperimentalFlag> all() { | ||
return ExtensionList.lookup(UserExperimentalFlag.class); | ||
} | ||
|
||
/** | ||
* From the flag class, return the value of the flag for the current user | ||
* If the returned value is {@code null}, | ||
* it means that either the class was not found or the current user is anonymous | ||
*/ | ||
@SuppressWarnings("unchecked") | ||
public static @CheckForNull <T> T getFlagValueForCurrentUser(String flagClassCanonicalName) { | ||
Class<? extends UserExperimentalFlag<T>> flagClass; | ||
try { | ||
Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(flagClassCanonicalName); | ||
if (!UserExperimentalFlag.class.isAssignableFrom(clazz)) { | ||
return null; | ||
} | ||
flagClass = (Class<? extends UserExperimentalFlag<T>>) clazz; | ||
} catch (Exception e) { | ||
return null; | ||
} | ||
|
||
UserExperimentalFlag<T> userExperimentalFlag = all().get(flagClass); | ||
if (userExperimentalFlag == null) { | ||
return null; | ||
} | ||
|
||
return userExperimentalFlag.getFlagValue(); | ||
} | ||
} |
88 changes: 88 additions & 0 deletions
88
core/src/main/java/jenkins/model/experimentalflags/UserExperimentalFlagsProperty.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/* | ||
* The MIT License | ||
* | ||
* Copyright (c) 2022, CloudBees, Inc. | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* of this software and associated documentation files (the "Software"), to deal | ||
* in the Software without restriction, including without limitation the rights | ||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
* copies of the Software, and to permit persons to whom the Software is | ||
* furnished to do so, subject to the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be included in | ||
* all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
* THE SOFTWARE. | ||
*/ | ||
|
||
package jenkins.model.experimentalflags; | ||
|
||
import edu.umd.cs.findbugs.annotations.CheckForNull; | ||
import edu.umd.cs.findbugs.annotations.NonNull; | ||
import edu.umd.cs.findbugs.annotations.Nullable; | ||
import hudson.Extension; | ||
import hudson.model.User; | ||
import hudson.model.UserProperty; | ||
import hudson.model.UserPropertyDescriptor; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import net.sf.json.JSONObject; | ||
import org.jenkinsci.Symbol; | ||
import org.kohsuke.stapler.DataBoundConstructor; | ||
import org.kohsuke.stapler.StaplerRequest; | ||
|
||
|
||
/** | ||
* Per user experimental flags to enable features that still not completely ready to be active by default. | ||
* | ||
* @since TODO | ||
*/ | ||
public class UserExperimentalFlagsProperty extends UserProperty { | ||
private Map<String, String> flags = new HashMap<>(); | ||
|
||
@DataBoundConstructor | ||
public UserExperimentalFlagsProperty() { | ||
} | ||
|
||
public UserExperimentalFlagsProperty(Map<String, String> flags) { | ||
this.flags = new HashMap<>(flags); | ||
} | ||
|
||
public @CheckForNull Object getFlagValue(String flagKey) { | ||
return this.flags.get(flagKey); | ||
} | ||
|
||
@Extension(ordinal = -500) | ||
@Symbol("experimentalFlags") | ||
public static final class DescriptorImpl extends UserPropertyDescriptor { | ||
@Override | ||
public @NonNull String getDisplayName() { | ||
return Messages.UserExperimentalFlagsProperty_DisplayName(); | ||
} | ||
|
||
@Override | ||
public @NonNull UserProperty newInstance(User user) { | ||
return new UserExperimentalFlagsProperty(); | ||
} | ||
|
||
@Override | ||
public UserProperty newInstance(@Nullable StaplerRequest req, @NonNull JSONObject formData) throws FormException { | ||
JSONObject flagsObj = formData.getJSONObject("flags"); | ||
Map<String, String> flags = new HashMap<>(); | ||
for (Object key : flagsObj.keySet()) { | ||
String value = (String) flagsObj.get((String) key); | ||
if (!value.isEmpty()) { | ||
flags.put((String) key, value); | ||
} | ||
} | ||
return new UserExperimentalFlagsProperty(flags); | ||
} | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
...in/resources/jenkins/model/experimentalflags/BooleanUserExperimentalFlag/flagConfig.jelly
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<!-- | ||
The MIT License | ||
Copyright (c) 2022, CloudBees, Inc. | ||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. | ||
--> | ||
|
||
<?jelly escape-by-default='true'?> | ||
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form"> | ||
<select class="jenkins-select__input" name="[${it.flagKey}]"> | ||
<f:option selected="${flagValue == null}" value=""> | ||
<j:if test="${it.getDefaultValue() == true}">${%Default_True}</j:if> | ||
<j:if test="${it.getDefaultValue() == false}">${%Default_False}</j:if> | ||
</f:option> | ||
<f:option selected="${flagValue == true}" value="true">${%True}</f:option> | ||
<f:option selected="${flagValue == false}" value="false">${%False}</f:option> | ||
</select> | ||
</j:jelly> |
25 changes: 25 additions & 0 deletions
25
...sources/jenkins/model/experimentalflags/BooleanUserExperimentalFlag/flagConfig.properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# The MIT License | ||
# | ||
# Copyright (c) 2022, CloudBees, Inc. | ||
# | ||
# Permission is hereby granted, free of charge, to any person obtaining a copy | ||
# of this software and associated documentation files (the "Software"), to deal | ||
# in the Software without restriction, including without limitation the rights | ||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
# copies of the Software, and to permit persons to whom the Software is | ||
# furnished to do so, subject to the following conditions: | ||
# | ||
# The above copyright notice and this permission notice shall be included in | ||
# all copies or substantial portions of the Software. | ||
# | ||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
# THE SOFTWARE. | ||
Default_True=Default (enabled) | ||
Default_False=Default (disabled) | ||
True=Enabled | ||
False=Disabled |
23 changes: 23 additions & 0 deletions
23
core/src/main/resources/jenkins/model/experimentalflags/Messages.properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# The MIT License | ||
# | ||
# Copyright (c) 2022, CloudBees, Inc. | ||
# | ||
# Permission is hereby granted, free of charge, to any person obtaining a copy | ||
# of this software and associated documentation files (the "Software"), to deal | ||
# in the Software without restriction, including without limitation the rights | ||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
# copies of the Software, and to permit persons to whom the Software is | ||
# furnished to do so, subject to the following conditions: | ||
# | ||
# The above copyright notice and this permission notice shall be included in | ||
# all copies or substantial portions of the Software. | ||
# | ||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
# THE SOFTWARE. | ||
|
||
UserExperimentalFlagsProperty.DisplayName=Experiments |
Oops, something went wrong.