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-39300] View.getDisplayName() is ignored when rendering tabs #2603

Merged
merged 15 commits into from
Dec 12, 2016
Merged
Show file tree
Hide file tree
Changes from 7 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
13 changes: 10 additions & 3 deletions core/src/main/java/hudson/model/AllView.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ public boolean contains(TopLevelItem item) {
return true;
}

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

@RequirePOST
@Override
public Item doCreateItem(StaplerRequest req, StaplerResponse rsp)
Expand Down Expand Up @@ -92,10 +97,12 @@ protected void submit(StaplerRequest req) throws IOException, ServletException,
@Extension @Symbol("all")
public static final class DescriptorImpl extends ViewDescriptor {
@Override
public boolean isInstantiable() {
for (View v : Stapler.getCurrentRequest().findAncestorObject(ViewGroup.class).getViews())
if(v instanceof AllView)
public boolean isApplicableIn(ViewGroup owner) {
for (View v : owner.getViews()) {
if (v instanceof AllView) {
return false;
}
}
return true;
}

Expand Down
6 changes: 4 additions & 2 deletions core/src/main/java/hudson/model/ListView.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ private Object readResolve() {

protected void initColumns() {
if (columns == null)
columns = new DescribableList<ListViewColumn, Descriptor<ListViewColumn>>(this,ListViewColumn.createDefaultInitialColumnList());
columns = new DescribableList<ListViewColumn, Descriptor<ListViewColumn>>(this,
ListViewColumn.createDefaultInitialColumnList(getClass())
);
}

protected void initJobFilters() {
Expand Down Expand Up @@ -459,7 +461,7 @@ public FormValidation doCheckIncludeRegex( @QueryParameter String value ) throws
*/
@Deprecated
public static List<ListViewColumn> getDefaultColumns() {
return ListViewColumn.createDefaultInitialColumnList();
return ListViewColumn.createDefaultInitialColumnList(ListView.class);
}

@Restricted(NoExternalUse.class)
Expand Down
23 changes: 19 additions & 4 deletions core/src/main/java/hudson/model/View.java
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ public List<Widget> getWidgets() {
* If this view uses &lt;t:projectView> for rendering, this method returns columns to be displayed.
*/
public Iterable<? extends ListViewColumn> getColumns() {
return ListViewColumn.createDefaultInitialColumnList();
return ListViewColumn.createDefaultInitialColumnList(this);
}

/**
Expand Down Expand Up @@ -1219,11 +1219,20 @@ public static DescriptorExtensionList<View,ViewDescriptor> all() {
return Jenkins.getInstance().<View,ViewDescriptor>getDescriptorList(View.class);
}

/**
* Returns the {@link ViewDescriptor} instances that can be instantiated for the {@link ViewGroup} in the current
* {@link StaplerRequest}. <strong>NOTE: This method must be called from a {@link StaplerRequest}</strong>
* @return the list of instantiable {@link ViewDescriptor} instances for the current {@link StaplerRequest}
*/
Copy link
Member Author

Choose a reason for hiding this comment

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

Note: this is the javadoc that applied to the method before my changes as the method would have blown up with a NPE if invoked outside of the newView.jelly screen of a ViewGroup as soon as AllView.isInstantiable() was called

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps just check owner == null and return all()?

Copy link
Member

Choose a reason for hiding this comment

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

Need to also check whether the StaplerRequest is null.

public static List<ViewDescriptor> allInstantiable() {
List<ViewDescriptor> r = new ArrayList<ViewDescriptor>();
for (ViewDescriptor d : all())
if(d.isInstantiable())
ViewGroup owner = Stapler.getCurrentRequest().findAncestorObject(ViewGroup.class);
for (ViewDescriptor d : DescriptorVisibilityFilter.apply(owner, all())) {
if (d.isApplicableIn(owner) && d.isInstantiable()
&& owner.getACL().hasCreatePermission(Jenkins.getAuthentication(), owner, d)) {
r.add(d);
}
}
return r;
}

Expand Down Expand Up @@ -1268,6 +1277,9 @@ public static View create(StaplerRequest req, StaplerResponse rsp, ViewGroup own
if (mode==null || mode.length()==0) {
if(isXmlSubmission) {
View v = createViewFromXML(name, req.getInputStream());
if (!owner.getACL().hasCreatePermission(Jenkins.getAuthentication(), owner, v.getDescriptor())) {
throw new Failure(Messages.View_CreatePermissionMissing(v.getDescriptor().getDisplayName()));
Copy link
Member

Choose a reason for hiding this comment

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

AccessDeniedException, or better yet define a new ACL.checkCreatePermission overload for this.

}
v.owner = owner;
rsp.setStatus(HttpServletResponse.SC_OK);
return v;
Expand All @@ -1276,7 +1288,7 @@ public static View create(StaplerRequest req, StaplerResponse rsp, ViewGroup own
}

View v;
if (mode!=null && mode.equals("copy")) {
if ("copy".equals(mode)) {
v = copy(req, owner, name);
} else {
ViewDescriptor descriptor = all().findByName(mode);
Expand All @@ -1287,6 +1299,9 @@ public static View create(StaplerRequest req, StaplerResponse rsp, ViewGroup own
// create a view
v = descriptor.newInstance(req,req.getSubmittedForm());
}
if (!owner.getACL().hasCreatePermission(Jenkins.getAuthentication(), owner, v.getDescriptor())) {
throw new Failure(Messages.View_CreatePermissionMissing(v.getDescriptor().getDisplayName()));
Copy link
Member

Choose a reason for hiding this comment

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

ditto

}
v.owner = owner;

// redirect to the config screen
Expand Down
65 changes: 63 additions & 2 deletions core/src/main/java/hudson/model/ViewDescriptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,26 @@
*/
package hudson.model;

import hudson.util.FormValidation;
import hudson.views.ListViewColumn;
import hudson.views.ListViewColumnDescriptor;
import hudson.views.ViewJobFilter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.DirectlyModifiableTopLevelItemGroup;
import jenkins.model.Jenkins;
import org.apache.commons.lang.StringUtils;
import org.jvnet.tiger_types.Types;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;

/**
* {@link Descriptor} for {@link View}.
Expand Down Expand Up @@ -101,13 +110,65 @@ public AutoCompletionCandidates doAutoCompleteCopyNewItemFrom(@QueryParameter fi
* Possible {@link ListViewColumnDescriptor}s that can be used with this view.
*/
public List<Descriptor<ListViewColumn>> getColumnsDescriptors() {
return ListViewColumn.all();
StaplerRequest request = Stapler.getCurrentRequest();
View view = request.findAncestorObject(clazz);
Copy link
Member

Choose a reason for hiding this comment

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

request could be null.

return view == null ? DescriptorVisibilityFilter.applyType(clazz, ListViewColumn.all())
: DescriptorVisibilityFilter.apply(view, ListViewColumn.all());
}

/**
* Possible {@link ViewJobFilter} types that can be used with this view.
*/
public List<Descriptor<ViewJobFilter>> getJobFiltersDescriptors() {
return ViewJobFilter.all();
StaplerRequest request = Stapler.getCurrentRequest();
View view = request.findAncestorObject(clazz);
Copy link
Member

Choose a reason for hiding this comment

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

ditto

return view == null ? DescriptorVisibilityFilter.applyType(clazz, ViewJobFilter.all())
: DescriptorVisibilityFilter.apply(view, ViewJobFilter.all());
}

/**
* Validation of the display name field.
*
* @param view the view to check the new display name of.
* @param value the proposed new display name.
* @return the validation result.
* @since FIXME
*/
@SuppressWarnings("unused") // expose utility check method to subclasses
Copy link
Member

Choose a reason for hiding this comment

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

Is there a particular subclass you had in mind? Not in this PR; some other linked one?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes but it needs to use the Referer header hack to resolve the View so probably waiting on dedicated support in stapler for that

Copy link
Member

Choose a reason for hiding this comment

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

So where is that subclass?

protected FormValidation checkDisplayName(@Nonnull View view, @CheckForNull String value) {
if (StringUtils.isBlank(value)) {
// no custom name, no need to check
return FormValidation.ok();
}
for (View v: view.owner.getViews()) {
if (v.getViewName().equals(view.getViewName())) {
continue;
}
if (StringUtils.equals(v.getDisplayName(), value)) {
return FormValidation.warning(Messages.View_DisplayNameNotUniqueWarning(value));
}
}
return FormValidation.ok();
}

/**
* Returns true if this {@link View} type is applicable to the given {@link ViewGroup} type.
*
* @return true to indicate applicable, in which case the view will be instantiable within the type of owner.
Copy link
Member

Choose a reason for hiding this comment

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

Specify default implementation.

* @since FIXME
*/
public boolean isApplicable(Class<? extends ViewGroup> ownerType) {
return true;
}

/**
* Returns true if this {@link View} type is applicable in the specific {@link ViewGroup}.
*
* @return true to indicate applicable, in which case the view will be instantiable within the given owner.
Copy link
Member

Choose a reason for hiding this comment

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

Specify default implementation.

* @since FIXME
*/
public boolean isApplicableIn(ViewGroup owner) {
return isApplicable(owner.getClass());
}

}
19 changes: 19 additions & 0 deletions core/src/main/java/hudson/security/ACL.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
package hudson.security;

import hudson.model.User;
import hudson.model.ViewDescriptor;
import hudson.model.ViewGroup;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;

Expand Down Expand Up @@ -120,6 +122,23 @@ public boolean hasCreatePermission(@Nonnull Authentication a, @Nonnull ItemGroup
return true;
}

/**
* Checks if the given principal has the permission to create views within the specified view group.
* <p>
* Note that {@link #SYSTEM} can be passed in as the authentication parameter,
* in which case you should probably just assume it can create anything anywhere.
* @param a the principal.
* @param c the container of the view.
* @param d the descriptor of the view to be created.
* @return false
* if the user doesn't have the permission.
* @since FIXME
*/
public boolean hasCreatePermission(@Nonnull Authentication a, @Nonnull ViewGroup c,
@Nonnull ViewDescriptor d) {
return true;
}

//
// Sid constants
//
Expand Down
37 changes: 34 additions & 3 deletions core/src/main/java/hudson/views/ListViewColumn.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.Descriptor.FormException;
import hudson.model.DescriptorVisibilityFilter;
import jenkins.model.Jenkins;
import hudson.model.Item;
import hudson.model.ItemGroup;
Expand Down Expand Up @@ -121,20 +122,50 @@ public Descriptor<ListViewColumn> getDescriptor() {
/**
* Creates the list of {@link ListViewColumn}s to be used for newly created {@link ListView}s and their likes.
* @since 1.391
* @deprecated use {@link #createDefaultInitialColumnList(Class)}
*/
@Deprecated
public static List<ListViewColumn> createDefaultInitialColumnList() {
return createDefaultInitialColumnList(ListViewColumn.all());
}

/**
* Creates the list of {@link ListViewColumn}s to be used for newly created {@link ListView}s and their likes.
*
* @see ListView#initColumns()
* @since FIXME
*/
public static List<ListViewColumn> createDefaultInitialColumnList(Class<? extends View> context) {
return createDefaultInitialColumnList(DescriptorVisibilityFilter.applyType(context, ListViewColumn.all()));
}

/**
* Creates the list of {@link ListViewColumn}s to be used for a {@link ListView}s and their likes.
*
* @see View#getColumns()
* @since FIXME
*/
public static List<ListViewColumn> createDefaultInitialColumnList(View view) {
return createDefaultInitialColumnList(DescriptorVisibilityFilter.apply(view, ListViewColumn.all()));
}

private static List<ListViewColumn> createDefaultInitialColumnList(List<Descriptor<ListViewColumn>> descriptors) {
// OK, set up default list of columns:
// create all instances
ArrayList<ListViewColumn> r = new ArrayList<ListViewColumn>();
final JSONObject emptyJSON = new JSONObject();
for (Descriptor<ListViewColumn> d : ListViewColumn.all())
for (Descriptor<ListViewColumn> d : descriptors)
try {
if (d instanceof ListViewColumnDescriptor) {
ListViewColumnDescriptor ld = (ListViewColumnDescriptor) d;
if (!ld.shownByDefault()) continue; // skip this
if (!ld.shownByDefault()) {
continue; // skip this
}
}
ListViewColumn lvc = d.newInstance(null, emptyJSON);
if (!lvc.shownByDefault()) continue; // skip this
if (!lvc.shownByDefault()) {
continue; // skip this
}
Copy link
Member

Choose a reason for hiding this comment

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

gratuitous reformatting


r.add(lvc);
} catch (FormException e) {
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/jenkins/model/Jenkins.java
Original file line number Diff line number Diff line change
Expand Up @@ -3052,7 +3052,7 @@ public void run(Reactor session) throws Exception {
// initialize views by inserting the default view if necessary
// this is both for clean Jenkins and for backward compatibility.
if(views.size()==0 || primaryView==null) {
View v = new AllView(Messages.Hudson_ViewName());
View v = new AllView("all");
Copy link
Member

Choose a reason for hiding this comment

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

This looks like it fixes JENKINS-38606.

Copy link
Member

Choose a reason for hiding this comment

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

It is. But will it cause a fallout in upgrading Jenkins instances?

Copy link
Member Author

Choose a reason for hiding this comment

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

Will only affect new instances. Old instances can get the fix by recreating the All view (add new view, change default, delete all view, add back all view, change default, tidy up)

Copy link
Member Author

Choose a reason for hiding this comment

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

We could be more aggressive and rename the AllView to all but it is valid to give it whatever name you want... though perhaps we need some further tweaks

Copy link
Member

Choose a reason for hiding this comment

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

This looks like a reasonable default for new All views. No need to edit existing ones. Would just cause confusion and broken links.

setViewOwner(v);
views.add(0,v);
primaryView = v.getViewName();
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/resources/hudson/model/Messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ View.ConfigurePermission.Description=\
View.ReadPermission.Description=\
This permission allows users to see views (implied by generic read access).
View.MissingMode=No view type is specified
View.DisplayNameNotUniqueWarning=The display name, "{0}", is already in use by another view and \
could cause confusion and delay.
View.CreatePermissionMissing=You are not permitted to create a {0} view

UpdateCenter.Status.CheckingInternet=Checking internet connectivity
UpdateCenter.Status.CheckingJavaNet=Checking update center connectivity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ THE SOFTWARE.
<st:include page="sidepanel.jelly" it="${it.user}"/>

<l:main-panel>
<j:invokeStatic var="views" className="hudson.model.View" method="allInstantiable" />
<j:invokeStatic var="views" className="hudson.model.View" method="allInstantiable"/>
Copy link
Member

Choose a reason for hiding this comment

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

Gratuitous diff hunk.

<n:form nameTitle="${%View name}" action="createView"
descriptors="${views}" checkUrl="${rootURL}/${it.url}viewExistsCheck" xmlns:n="/lib/hudson/newFromList" />
</l:main-panel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ THE SOFTWARE.
<!-- view tab bar -->
<l:tabBar>
<j:forEach var="v" items="${views}">
<l:tab name="${v.viewName}" active="${v==currentView}" href="${rootURL}/${v.url}" />
<l:tab name="${v.displayName}" active="${v==currentView}" href="${rootURL}/${v.url}" />
</j:forEach>
<j:if test="${currentView.hasPermission(currentView.CREATE)}">
<l:tab name="+" href="${rootURL}/${currentView.owner.url}newView" active="false"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ THE SOFTWARE.
<!-- view tab bar -->
<l:tabBar>
<j:forEach var="v" items="${views}">
<l:tab name="${v.viewName}" active="${v==currentView}" href="${rootURL}/${v.url}" />
<l:tab name="${v.displayName}" active="${v==currentView}" href="${rootURL}/${v.url}" />
</j:forEach>
<j:if test="${currentView.hasPermission(currentView.CREATE)}">
<l:tab name="+" href="${rootURL}/${currentView.owner.url}newView" active="false"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ THE SOFTWARE.
<st:include page="sidepanel.jelly" />

<l:main-panel>
<j:invokeStatic var="views" className="hudson.model.View" method="allInstantiable" />
<j:invokeStatic var="views" className="hudson.model.View" method="allInstantiable"/>
Copy link
Member

Choose a reason for hiding this comment

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

ditto

<n:form nameTitle="${%View name}" action="createView" copyTitle="${%Copy Existing View}"
descriptors="${views}" checkUrl="checkViewName" xmlns:n="/lib/hudson/newFromList" />
</l:main-panel>
Expand Down
28 changes: 18 additions & 10 deletions core/src/main/resources/lib/layout/tab.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,32 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->

<!--
<%@ attribute name="name" required="true" type="java.lang.String" %>
<%@ attribute name="href" required="true" type="java.lang.String" %>
<%@ attribute name="active" required="true" type="java.lang.Boolean" %>
<%@ attribute name="title" required="false" type="java.lang.String" %>
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core">
<div class="tab${active?' active':''}">
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler">
<st:documentation>
<st:attribute name="name" use="required">
The name of the tab
</st:attribute>
<st:attribute name="href" use="required">
The url of the tab
</st:attribute>
<st:attribute name="active" type="java.lang.Boolean">
Whether the tab is active or not
</st:attribute>
<st:attribute name="title">
The title of the tab
Copy link
Member

Choose a reason for hiding this comment

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

Do we have an @since equivalent for Jelly tags?

Copy link
Member Author

@stephenc stephenc Oct 28, 2016

Choose a reason for hiding this comment

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

I am restoring behaviour that was broken in this earlier commit (appears to be removed by omission as the old style documentation of parameters was retained)

Copy link
Member Author

Choose a reason for hiding this comment

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

jenkinsci/stapler#84 created for you ;-)

</st:attribute>
</st:documentation>
<div class="tab${attrs.active?' active':''}">
<j:choose>
<j:when test="${active}">
<j:when test="${attrs.active}">
<input type="radio" id="tab-${tabBarId}-${tabIndex}" name="tab-group-${tabBarId}" checked="checked" />
</j:when>
<j:otherwise>
<input type="radio" id="tab-${tabBarId}-${tabIndex}" name="tab-group-${tabBarId}" />
</j:otherwise>
</j:choose>
<a href="${href}" class="${name.equals('+')?'addTab':''}">${name}</a>
<a href="${href}" class="${name.equals('+')?'addTab':''}" title="${attrs.title}">${attrs.name}</a>
</div>
<j:set scope="parent" var="tabIndex" value="${tabIndex+1}" />
</j:jelly>
Loading