Skip to content

Commit

Permalink
[JENKINS-69869] Categorize the user properties
Browse files Browse the repository at this point in the history
  • Loading branch information
Wadeck committed Oct 16, 2022
1 parent 56108f1 commit 1ffbccb
Show file tree
Hide file tree
Showing 55 changed files with 1,913 additions and 2 deletions.
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
34 changes: 34 additions & 0 deletions core/src/main/java/hudson/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@
import hudson.init.Initializer;
import hudson.model.Descriptor.FormException;
import hudson.model.listeners.SaveableListener;
import hudson.model.userproperty.UserPreferencesProperty;
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.HttpResponses;
import hudson.util.RunList;
import hudson.util.XStream2;
import java.io.File;
Expand Down Expand Up @@ -336,6 +338,29 @@ public synchronized void addProperty(@NonNull UserProperty p) throws IOException
properties = ps;
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,6 +884,15 @@ public Api getApi() {
*/
@POST
public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
// FIXME DRAFT system property
if (true) {
LOGGER.warning(() ->
"Attempt to use the legacy /configSubmit endpoint of user login=" + this.id +
". The global configuration page has been deprecated in favor of categorized pages. For more details, see JENKINS-69869."
);
throw HttpResponses.error(400, "The user global configuration page was split into categorized individual pages. See JENKINS-69869.");
}

checkPermission(Jenkins.ADMINISTER);

JSONObject json = req.getSubmittedForm();
Expand Down
29 changes: 29 additions & 0 deletions core/src/main/java/hudson/model/UserProperty.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,21 @@

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 jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.export.ExportedBean;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
* Extensible property of {@link User}.
*
Expand All @@ -58,6 +65,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 +86,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
54 changes: 54 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,13 @@

package hudson.model;

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

import java.util.Optional;

/**
* {@link Descriptor} for {@link UserProperty}.
*
Expand Down Expand Up @@ -73,4 +80,51 @@ protected UserPropertyDescriptor() {
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) {
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();
}
}
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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* 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 hudson.model.userproperty;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.ExtensionPoint;
import hudson.model.Messages;
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
import jenkins.model.Jenkins;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.export.ExportedBean;

import java.util.Map;

//TODO DRAFT experimental to split
/**
* Property of {@link User} responsible to store their preferences.
*
* TODO rest of the javadoc
*
* @since TODO
*/
@ExportedBean
public class UserPreferencesProperty extends UserProperty {

private Map<UserPreferenceKey, UserPreferenceValue> preferences;



// descriptor must be of the UserPropertyDescriptor type
@Override
public UserPropertyDescriptor getDescriptor() {
return (UserPropertyDescriptor) Jenkins.get().getDescriptorOrDie(getClass());
}

public static class UserPreference implements ExtensionPoint {
public UserPreferenceKey getKey(){
return null;
}
public UserPreferenceValue getValue(){
return null;
}
}

public static class UserPreferenceKey {

}

public static class UserPreferenceValue {

}

// @Extension
@Symbol("preferences")
public static class DescriptorImpl extends UserPropertyDescriptor {

@NonNull
@Override
public String getDisplayName() {
return Messages.UserPreferencesProperty_DisplayName();
}

@Override
public UserProperty newInstance(User user) {
return new UserPreferencesProperty();
}
}
}
Loading

0 comments on commit 1ffbccb

Please sign in to comment.