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-18884] Remove 'People' view #9060

Merged
merged 5 commits into from Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
266 changes: 0 additions & 266 deletions core/src/main/java/hudson/model/View.java
@@ -1,3 +1,3 @@
/*
* The MIT License
*
Expand Down Expand Up @@ -41,7 +41,6 @@
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;
Expand Down Expand Up @@ -81,7 +80,6 @@
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 +96,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 @@ -703,264 +695,6 @@
}
}

/**
* 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
20 changes: 0 additions & 20 deletions core/src/main/java/jenkins/model/Jenkins.java
Expand Up @@ -1455,26 +1455,6 @@ public void setNoUsageStatistics(Boolean noUsageStatistics) throws IOException {
save();
}

public View.People getPeople() {
return new View.People(this);
}

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

/**
* 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 View.People.isApplicable(items.values());
}

public Api getApi() {
/* Do not show "REST API" link in footer when on 404 error page */
final StaplerRequest req = Stapler.getCurrentRequest();
Expand Down
1 change: 0 additions & 1 deletion core/src/main/resources/hudson/model/User/sidepanel.jelly
Expand Up @@ -31,7 +31,6 @@ THE SOFTWARE.
<l:header />
<l:side-panel>
<l:tasks>
<l:task contextMenu="false" href="${rootURL}/asynchPeople/" icon="symbol-people" title="${%People}"/>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an awkward case as the people view basically represents the list of users, of which an individual user is an entry (which is why this is the first link in the sidepanel). Unless using the built-in Jenkins' own user database, admins no longer have a users list with this change. We should probably add a ManagementLink before long to iterate over (actual) users.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless using the built-in Jenkins' own user database, admins no longer have a users list with this change.

They do not have such a list currently. AsynchPeople lists SCM users which are not in the security realm; and does not list users which are in the security realm but have no user record on disk. It is simply useless for this purpose.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but have no user record on disk

Right, but anyone who's logged in once has such a record, which makes it a viable (if crappy and slow) entrypoint to getting to someone that's not directly entering the URL.

<l:task contextMenu="false" href="${rootURL}/${it.url}/" icon="symbol-person" title="${%Status}"/>
<l:task href="${rootURL}/${it.url}/builds" icon="symbol-build-history" title="${%Builds}"/>
<l:task href="${rootURL}/${it.url}/configure" icon="symbol-settings" permission="${app.ADMINISTER}" title="${%Configure}"/>
Expand Down