Skip to content

Commit

Permalink
[JENKINS-18884] Remove 'People' view (#9060)
Browse files Browse the repository at this point in the history
* [JENKINS-18884] Remove 'People' view

* Add placeholder pages when someone tries to access removed URLs

* Remove obsolete UserInfo class, inapplicable tests

* Restore test with a different core-contributed object

It's not entirely clear what this test signifies, given the
existence of `testTopLevelItemIsLegal`, but whatever.

* Remove probably unnecessary code

---------

Co-authored-by: Daniel Beck <daniel-beck@users.noreply.github.com>
  • Loading branch information
daniel-beck and daniel-beck committed Mar 28, 2024
1 parent b60c3aa commit 28ed39d
Show file tree
Hide file tree
Showing 69 changed files with 93 additions and 1,543 deletions.
337 changes: 0 additions & 337 deletions core/src/main/java/hudson/model/View.java
Expand Up @@ -41,15 +41,13 @@
import hudson.init.Initializer;
import hudson.model.Descriptor.FormException;
import hudson.model.listeners.ItemListener;
import hudson.scm.ChangeLogSet;
import hudson.search.CollectionSearchIndex;
import hudson.search.SearchIndexBuilder;
import hudson.security.ACL;
import hudson.security.AccessControlled;
import hudson.security.Permission;
import hudson.security.PermissionGroup;
import hudson.security.PermissionScope;
import hudson.tasks.UserAvatarResolver;
import hudson.util.AlternativeUiTextProvider;
import hudson.util.AlternativeUiTextProvider.Message;
import hudson.util.DescribableList;
Expand All @@ -70,18 +68,15 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
Expand All @@ -98,19 +93,13 @@
import jenkins.model.item_category.Categories;
import jenkins.model.item_category.Category;
import jenkins.model.item_category.ItemCategory;
import jenkins.scm.RunWithSCM;
import jenkins.security.stapler.StaplerAccessibleType;
import jenkins.util.ProgressiveRendering;
import jenkins.util.xml.XMLUtils;
import jenkins.widgets.HasWidgets;
import net.sf.json.JSON;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.jenkins.ui.icon.Icon;
import org.jenkins.ui.icon.IconSet;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
Expand Down Expand Up @@ -635,332 +624,6 @@ public ACL getACL() {
@Deprecated
public void onJobRenamed(Item item, String oldName, String newName) {}

@ExportedBean(defaultVisibility = 2)
public static final class UserInfo implements Comparable<UserInfo> {
private final User user;
/**
* When did this user made a last commit on any of our projects? Can be null.
*/
private Calendar lastChange;
/**
* Which project did this user commit? Can be null.
*/
private Job<?, ?> project;

/** @see UserAvatarResolver */
String avatar;

UserInfo(User user, Job<?, ?> p, Calendar lastChange) {
this.user = user;
this.project = p;
this.lastChange = lastChange;
}

@Exported
public User getUser() {
return user;
}

@Exported
public Calendar getLastChange() {
return lastChange;
}

@Deprecated
public AbstractProject getProject() {
return project instanceof AbstractProject ? (AbstractProject) project : null;
}

@Exported(name = "project")
public Job<?, ?> getJob() {
return project;
}

/**
* Returns a human-readable string representation of when this user was last active.
*/
public String getLastChangeTimeString() {
if (lastChange == null) return "N/A";
long duration = new GregorianCalendar().getTimeInMillis() - ordinal();
return Util.getTimeSpanString(duration);
}

public String getTimeSortKey() {
if (lastChange == null) return "-";
return Util.XS_DATETIME_FORMATTER.format(lastChange.getTime());
}

@Override
public int compareTo(UserInfo that) {
long rhs = that.ordinal();
long lhs = this.ordinal();
return Long.compare(rhs, lhs);
}

private long ordinal() {
if (lastChange == null) return 0;
return lastChange.getTimeInMillis();
}
}

/**
* Does this {@link View} has any associated user information recorded?
* @deprecated Potentially very expensive call; do not use from Jelly views.
*/
@Deprecated
public boolean hasPeople() {
return People.isApplicable(getItems());
}

/**
* Gets the users that show up in the changelog of this job collection.
*/
public People getPeople() {
return new People(this);
}

/**
* @since 1.484
*/
public AsynchPeople getAsynchPeople() {
return new AsynchPeople(this);
}

@ExportedBean
@StaplerAccessibleType
public static final class People {
@Exported
public final List<UserInfo> users;

public final ModelObject parent;

public People(Jenkins parent) {
this.parent = parent;
// for Hudson, really load all users
Map<User, UserInfo> users = getUserInfo(parent.getItems());
User unknown = User.getUnknown();
for (User u : User.getAll()) {
if (u == unknown) continue; // skip the special 'unknown' user
if (!users.containsKey(u))
users.put(u, new UserInfo(u, null, null));
}
this.users = toList(users);
}

public People(View parent) {
this.parent = parent;
this.users = toList(getUserInfo(parent.getItems()));
}

private Map<User, UserInfo> getUserInfo(Collection<? extends Item> items) {
Map<User, UserInfo> users = new HashMap<>();
for (Item item : items) {
for (Job<?, ?> job : item.getAllJobs()) {
RunList<? extends Run<?, ?>> runs = job.getBuilds();
for (Run<?, ?> r : runs) {
if (r instanceof RunWithSCM) {
RunWithSCM<?, ?> runWithSCM = (RunWithSCM<?, ?>) r;

for (ChangeLogSet<? extends ChangeLogSet.Entry> c : runWithSCM.getChangeSets()) {
for (ChangeLogSet.Entry entry : c) {
User user = entry.getAuthor();

UserInfo info = users.get(user);
if (info == null)
users.put(user, new UserInfo(user, job, r.getTimestamp()));
else if (info.getLastChange().before(r.getTimestamp())) {
info.project = job;
info.lastChange = r.getTimestamp();
}
}
}
}
}
}
}
return users;
}

private List<UserInfo> toList(Map<User, UserInfo> users) {
ArrayList<UserInfo> list = new ArrayList<>(users.values());
Collections.sort(list);
return Collections.unmodifiableList(list);
}

public Api getApi() {
return new Api(this);
}

/**
* @deprecated Potentially very expensive call; do not use from Jelly views.
*/
@Deprecated
public static boolean isApplicable(Collection<? extends Item> items) {
for (Item item : items) {
for (Job job : item.getAllJobs()) {
RunList<? extends Run<?, ?>> runs = job.getBuilds();

for (Run<?, ?> r : runs) {
if (r instanceof RunWithSCM) {
RunWithSCM<?, ?> runWithSCM = (RunWithSCM<?, ?>) r;
for (ChangeLogSet<? extends ChangeLogSet.Entry> c : runWithSCM.getChangeSets()) {
for (ChangeLogSet.Entry entry : c) {
User user = entry.getAuthor();
if (user != null)
return true;
}
}
}
}
}
}
return false;
}
}

/**
* Variant of {@link People} which can be displayed progressively, since it may be slow.
* @since 1.484
*/
public static final class AsynchPeople extends ProgressiveRendering { // JENKINS-15206

private final Collection<TopLevelItem> items;
private final User unknown;
private final Map<User, UserInfo> users = new HashMap<>();
private final Set<User> modified = new HashSet<>();
private final String iconSize;
public final ModelObject parent;

/** @see Jenkins#getAsynchPeople */
public AsynchPeople(Jenkins parent) {
this.parent = parent;
items = parent.getItems();
unknown = User.getUnknown();
}

/** @see View#getAsynchPeople */
public AsynchPeople(View parent) {
this.parent = parent;
items = parent.getItems();
unknown = null;
}

{
StaplerRequest req = Stapler.getCurrentRequest();
iconSize = req != null ? Functions.validateIconSize(Functions.getCookie(req, "iconSize", "32x32")) : "32x32";
}

@Override protected void compute() throws Exception {
int itemCount = 0;
for (Item item : items) {
for (Job<?, ?> job : item.getAllJobs()) {
RunList<? extends Run<?, ?>> builds = job.getBuilds();
int buildCount = 0;
for (Run<?, ?> r : builds) {
if (canceled()) {
return;
}
if (!(r instanceof RunWithSCM)) {
continue;
}

RunWithSCM<?, ?> runWithSCM = (RunWithSCM<?, ?>) r;
for (ChangeLogSet<? extends ChangeLogSet.Entry> c : runWithSCM.getChangeSets()) {
for (ChangeLogSet.Entry entry : c) {
User user = entry.getAuthor();
UserInfo info = users.get(user);
if (info == null) {
UserInfo userInfo = new UserInfo(user, job, r.getTimestamp());
userInfo.avatar = UserAvatarResolver.resolveOrNull(user, iconSize);
synchronized (this) {
users.put(user, userInfo);
modified.add(user);
}
} else if (info.getLastChange().before(r.getTimestamp())) {
synchronized (this) {
info.project = job;
info.lastChange = r.getTimestamp();
modified.add(user);
}
}
}
}
// TODO consider also adding the user of the UserCause when applicable
buildCount++;
// TODO this defeats lazy-loading. Should rather do a breadth-first search, as in hudson.plugins.view.dashboard.builds.LatestBuilds
// (though currently there is no quick implementation of RunMap.size() ~ idOnDisk.size(), which would be needed for proper progress)
progress((itemCount + 1.0 * buildCount / builds.size()) / (items.size() + 1));
}
}
itemCount++;
progress(1.0 * itemCount / (items.size() + /* handling User.getAll */1));
}
if (unknown != null) {
if (canceled()) {
return;
}
for (User u : User.getAll()) { // TODO nice to have a method to iterate these lazily
if (canceled()) {
return;
}
if (u == unknown) {
continue;
}
if (!users.containsKey(u)) {
UserInfo userInfo = new UserInfo(u, null, null);
userInfo.avatar = UserAvatarResolver.resolveOrNull(u, iconSize);
synchronized (this) {
users.put(u, userInfo);
modified.add(u);
}
}
}
}
}

@NonNull
@Override protected synchronized JSON data() {
JSONArray r = new JSONArray();
for (User u : modified) {
UserInfo i = users.get(u);
JSONObject entry = new JSONObject().
accumulate("id", u.getId()).
accumulate("fullName", u.getFullName()).
accumulate("url", u.getUrl() + "/").
accumulate("avatar", i.avatar != null ? i.avatar : Stapler.getCurrentRequest().getContextPath() + Functions.getResourcePath() + "/images/svgs/person.svg").
accumulate("timeSortKey", i.getTimeSortKey()).
accumulate("lastChangeTimeString", i.getLastChangeTimeString());
Job<?, ?> p = i.getJob();
if (p != null) {
entry.accumulate("projectUrl", p.getUrl()).accumulate("projectFullDisplayName", p.getFullDisplayName());
}
r.add(entry);
}
modified.clear();
return r;
}

public Api getApi() {
return new Api(new People());
}

/** JENKINS-16397 workaround */
@Restricted(NoExternalUse.class)
@ExportedBean
public final class People {

private View.People people;

@Exported public synchronized List<UserInfo> getUsers() {
if (people == null) {
people = parent instanceof Jenkins ? new View.People((Jenkins) parent) : new View.People((View) parent);
}
return people.users;
}
}

}

void addDisplayNamesToSearchIndex(SearchIndexBuilder sib, Collection<TopLevelItem> items) {
for (TopLevelItem item : items) {

Expand Down

0 comments on commit 28ed39d

Please sign in to comment.