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 4 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
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