Skip to content
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

[JENKINS-69869] Categorize the user properties #7268

Merged
merged 18 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/src/main/java/hudson/model/MyViewsProperty.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import hudson.Extension;
import hudson.Util;
import hudson.model.Descriptor.FormException;
import hudson.model.userproperty.UserPropertyCategory;
import hudson.security.ACL;
import hudson.util.FormValidation;
import hudson.views.MyViewsTabBar;
Expand Down Expand Up @@ -246,6 +247,11 @@ public String getDisplayName() {
public UserProperty newInstance(User user) {
return new MyViewsProperty();
}

@Override
public @NonNull UserPropertyCategory getUserPropertyCategory() {
return UserPropertyCategory.get(UserPropertyCategory.Preferences.class);
}
}

@Override
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/java/hudson/model/PaneStatusProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import static java.lang.String.format;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.userproperty.UserPropertyCategory;
import hudson.util.PersistedList;
import java.io.IOException;
import javax.servlet.http.HttpSession;
Expand Down Expand Up @@ -56,6 +58,10 @@ public boolean isEnabled() {
return false;
}

@Override
public @NonNull UserPropertyCategory getUserPropertyCategory() {
return UserPropertyCategory.get(UserPropertyCategory.Invisible.class);
}
}

private static class PaneStatusPropertiesSessionFallback extends PaneStatusProperties {
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/java/hudson/model/TimeZoneProperty.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.Util;
import hudson.model.userproperty.UserPropertyCategory;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import hudson.util.ListBoxModel.Option;
Expand Down Expand Up @@ -106,6 +107,10 @@ public FormValidation doCheckTimeZoneName(@QueryParameter String timeZoneName) {
}
}

@Override
public @NonNull UserPropertyCategory getUserPropertyCategory() {
return UserPropertyCategory.get(UserPropertyCategory.Account.class);
}
}

@CheckForNull
Expand Down
69 changes: 23 additions & 46 deletions core/src/main/java/hudson/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,11 @@
import hudson.XmlFile;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
import hudson.model.Descriptor.FormException;
import hudson.model.listeners.SaveableListener;
import hudson.security.ACL;
import hudson.security.AccessControlled;
import hudson.security.SecurityRealm;
import hudson.security.UserMayOrMayNotExistException2;
import hudson.util.FormApply;
import hudson.util.FormValidation;
import hudson.util.RunList;
import hudson.util.XStream2;
Expand Down Expand Up @@ -77,7 +75,6 @@
import jenkins.security.LastGrantedAuthoritiesProperty;
import jenkins.security.UserDetailsCache;
import jenkins.util.SystemProperties;
import net.sf.json.JSONObject;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
Expand All @@ -87,7 +84,6 @@
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.interceptor.RequirePOST;
import org.kohsuke.stapler.verb.POST;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
Expand Down Expand Up @@ -342,6 +338,29 @@ public synchronized void addProperty(@NonNull UserProperty p) throws IOException
save();
}

/**
* Expand {@link #addProperty(UserProperty)} for multiple properties to be done at once.
* Expected to be used by the categorized configuration pages to update part of the properties.
* The properties not included in the list will be let untouched.
* It will call the {@link UserProperty#setUser(User)} method and at the end, {@link #save()} once.
*
* @since TODO
*/
public synchronized void addProperties(@NonNull List<UserProperty> multipleProperties) throws IOException {
List<UserProperty> newProperties = new ArrayList<>(this.properties);
for (UserProperty property : multipleProperties) {
UserProperty oldProp = getProperty(property.getClass());
if (oldProp != null) {
newProperties.remove(oldProp);
}
newProperties.add(property);
property.setUser(this);
}

this.properties = newProperties;
this.save();
}

/**
* List of all {@link UserProperty}s exposed primarily for the remoting API.
*/
Expand Down Expand Up @@ -859,48 +878,6 @@ public Api getApi() {
return new Api(this);
}

/**
* Accepts submission from the configuration page.
*/
@POST
public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
checkPermission(Jenkins.ADMINISTER);

JSONObject json = req.getSubmittedForm();
String oldFullName = this.fullName;
fullName = json.getString("fullName");
description = json.getString("description");

List<UserProperty> props = new ArrayList<>();
int i = 0;
for (UserPropertyDescriptor d : UserProperty.all()) {
UserProperty p = getProperty(d.clazz);

JSONObject o = json.optJSONObject("userProperty" + i++);
if (o != null) {
if (p != null) {
p = p.reconfigure(req, o);
} else {
p = d.newInstance(req, o);
}
}

if (p != null) {
p.setUser(this);
props.add(p);
}
}
this.properties = props;

save();

if (oldFullName != null && !oldFullName.equals(this.fullName)) {
UserDetailsCache.get().invalidate(oldFullName);
}

FormApply.success(".").generateResponse(req, rsp, this);
}

/**
* Deletes this user from Hudson.
*/
Expand Down
26 changes: 26 additions & 0 deletions core/src/main/java/hudson/model/UserProperty.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@

package hudson.model;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.DescriptorExtensionList;
import hudson.ExtensionPoint;
import hudson.model.Descriptor.FormException;
import hudson.model.userproperty.UserPropertyCategory;
import java.util.ArrayList;
import java.util.List;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
Expand Down Expand Up @@ -58,6 +62,10 @@ public abstract class UserProperty implements ReconfigurableDescribable<UserProp
*/
protected transient User user;

/**
* This method is used to inform the property about its owner.
* It could be called multiple times, even without change, thus it should be idempotent.
*/
protected void setUser(User u) {
this.user = u;
}
Expand All @@ -75,6 +83,24 @@ public static DescriptorExtensionList<UserProperty, UserPropertyDescriptor> all(
return Jenkins.get().getDescriptorList(UserProperty.class);
}

/**
* Returns all the registered {@link UserPropertyCategory} descriptors for a given category.
*
* @since TODO
*/
public static List<UserPropertyDescriptor> allByCategoryClass(@NonNull Class<? extends UserPropertyCategory> categoryClass) {
DescriptorExtensionList<UserProperty, UserPropertyDescriptor> all = all();

List<UserPropertyDescriptor> onlyForTheCategory = new ArrayList<>(all.size());
for (UserPropertyDescriptor descriptor : all) {
if (descriptor.getUserPropertyCategory().getClass().equals(categoryClass)) {
onlyForTheCategory.add(descriptor);
}
}

return onlyForTheCategory;
}

@Override
public UserProperty reconfigure(StaplerRequest req, JSONObject form) throws FormException {
return form == null ? null : getDescriptor().newInstance(req, form);
Expand Down
53 changes: 53 additions & 0 deletions core/src/main/java/hudson/model/UserPropertyDescriptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@

package hudson.model;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.model.userproperty.UserPropertyCategory;
import java.util.Optional;
import org.jenkinsci.Symbol;

/**
* {@link Descriptor} for {@link UserProperty}.
*
Expand Down Expand Up @@ -73,4 +79,51 @@
public boolean isEnabled() {
return true;
}

/**
* Define the category for this user property descriptor.
*
* @return never null, always the same value for a given instance of {@link Descriptor}.
*
* @since TODO
*/
public @NonNull UserPropertyCategory getUserPropertyCategory() {
// As this method is expected to be overloaded by subclasses
// the logic here is just done to support plugins with older core version
String categoryAsString = this.getUserPropertyCategoryAsString();
if (categoryAsString != null) {

Check warning on line 94 in core/src/main/java/hudson/model/UserPropertyDescriptor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 94 is only partially covered, one branch is missing
Optional<UserPropertyCategory> firstIfFound = UserPropertyCategory.all().stream()
.filter(cat -> {
Symbol symbolAnnotation = cat.getClass().getAnnotation(Symbol.class);
if (symbolAnnotation != null) {
for (String symbolValue : symbolAnnotation.value()) {
if (symbolValue.equalsIgnoreCase(categoryAsString)) {
return true;
}
}
}
return false;
})
.findFirst();
if (firstIfFound.isPresent()) {
return firstIfFound.get();

Check warning on line 109 in core/src/main/java/hudson/model/UserPropertyDescriptor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 95-109 are not covered by tests
}
}
return UserPropertyCategory.get(UserPropertyCategory.Unclassified.class);
}

/**
* Method proposed to prevent plugins to rely on too recent core version
* while keeping the possibility to use the categories.
*
* @deprecated This should only be used when the core requirement is below the version this method was added
*
* @return String name corresponding to the symbol of {@link #getUserPropertyCategory()}
*
* @since TODO
*/
@Deprecated
protected @CheckForNull String getUserPropertyCategoryAsString() {
return null;
}
}
Loading
Loading