Skip to content

Commit

Permalink
add a separator for the link menu (#158)
Browse files Browse the repository at this point in the history
can be a text or when empty just a line
  • Loading branch information
mawinter69 authored Oct 29, 2024
1 parent 8e771d0 commit dd3dcbc
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.jenkins.plugins.customizable_header;

import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.model.AbstractDescribableImpl;
import jenkins.model.Jenkins;

public abstract class AbstractLink extends AbstractDescribableImpl<AbstractLink> implements ExtensionPoint {

public static ExtensionList<AbstractLink> all() {
return Jenkins.get().getExtensionList(AbstractLink.class);
}

public abstract String getType();

@Override
public LinkDescriptor getDescriptor() {
return (LinkDescriptor) super.getDescriptor();

Check warning on line 18 in src/main/java/io/jenkins/plugins/customizable_header/AbstractLink.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 8-18 are not covered by tests
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import io.jenkins.plugins.customizable_header.logo.ImageLogo;
import io.jenkins.plugins.customizable_header.logo.Logo;
Expand All @@ -13,7 +12,6 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.jenkins.ui.symbol.SymbolRequest;
import org.kohsuke.stapler.DataBoundConstructor;
Expand All @@ -24,7 +22,7 @@
import org.kohsuke.stapler.export.ExportedBean;

@ExportedBean
public class AppNavLink extends AbstractDescribableImpl<AppNavLink> implements Comparable<AppNavLink> {
public class AppNavLink extends AbstractLink {

private String url;
private String label;
Expand Down Expand Up @@ -86,6 +84,12 @@ public void setLogo(Logo logo) {
}


@Exported
@Override
public String getType() {
return "link";

Check warning on line 90 in src/main/java/io/jenkins/plugins/customizable_header/AppNavLink.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 90 is not covered by tests
}

@Exported
public String getLinkUrl() {
try {
Expand Down Expand Up @@ -151,34 +155,12 @@ private static String extractPluginNameFromIconSrc(String iconSrc) {
return "";
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AppNavLink that = (AppNavLink) o;
return Objects.equals(url, that.url) && Objects.equals(label, that.label);
}

@Override
public int hashCode() {
return Objects.hash(url, label);
}

@Override
public int compareTo(AppNavLink other) {
int labelCompare = label.compareToIgnoreCase(other.label);
if (labelCompare != 0) {
return labelCompare;
}
return url.compareTo(other.url);
}

@Extension
public static class DescriptorImpl extends Descriptor<AppNavLink> {
public static class DescriptorImpl extends LinkDescriptor {
@Override
@NonNull
public String getDisplayName() {
return "";
return "Link";
}

public List<Descriptor<Logo>> getLogoDescriptors() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
Expand Down Expand Up @@ -60,7 +61,7 @@ public class CustomHeaderConfiguration extends GlobalConfiguration {

private final List<SystemMessage> systemMessages = new ArrayList<>();

private List<AppNavLink> links = new ArrayList<>();
private List<AbstractLink> links = new ArrayList<>();

private static final transient Symbol star = new Symbol("symbol-star plugin-ionicons-api");

Expand Down Expand Up @@ -108,7 +109,16 @@ private static List<AppNavLink> getFavorites(User user) {
fav.setColor("jenkins-!-color-yellow");
favorites.add(fav);
});
Collections.sort(favorites);
favorites.sort(new Comparator<AppNavLink>() {
@Override
public int compare(AppNavLink o1, AppNavLink o2) {
int labelCompare = o1.getLabel().compareToIgnoreCase(o2.getLabel());
if (labelCompare != 0) {
return labelCompare;
}
return o1.getUrl().compareTo(o2.getUrl());
}
});
return favorites;
}

Expand Down Expand Up @@ -179,12 +189,12 @@ public void deleteSystemMessage(String id) {
save();
}

public List<AppNavLink> getLinks() {
public List<AbstractLink> getLinks() {
return links;
}

@DataBoundSetter
public void setLinks(List<AppNavLink> links) {
public void setLinks(List<AbstractLink> links) {
this.links = links;
save();
}
Expand Down Expand Up @@ -220,9 +230,9 @@ public boolean hasLinks() {
return hasFavorites() || hasAppLinks() || hasUserLinks();
}

public List<AppNavLink> getUserLinks() {
public List<AbstractLink> getUserLinks() {
User user = User.current();
List<AppNavLink> links = null;
List<AbstractLink> links = null;

Check warning on line 235 in src/main/java/io/jenkins/plugins/customizable_header/CustomHeaderConfiguration.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 112-235 are not covered by tests
if (user != null) {
UserHeader userHeader = user.getProperty(UserHeader.class);
links = userHeader.getLinks();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,18 +112,23 @@ public void doDismissMessage(@QueryParameter String uid) throws IOException {
@ExportedBean
static class Links implements HttpResponse {
@Exported(inline = true)
public List<AppNavLink> getLinks() {
List<AppNavLink> links = new ArrayList<>();
links.addAll(CustomHeaderConfiguration.get().getLinks());
links.addAll(CustomHeaderConfiguration.get().getUserLinks());
public List<AbstractLink> getLinks() {
List<AbstractLink> links = new ArrayList<>(CustomHeaderConfiguration.get().getLinks());
List<AbstractLink> userLinks = CustomHeaderConfiguration.get().getUserLinks();
if (!userLinks.isEmpty()) {
if (!(userLinks.get(0) instanceof LinkSeparator)) {
links.add(new LinkSeparator());
}
links.addAll(userLinks);
}
List<AppNavLink> favorites = CustomHeaderConfiguration.get().getFavorites();
if (!favorites.isEmpty()) {
links.add(new LinkSeparator());
links.addAll(favorites);
}
return links;

Check warning on line 129 in src/main/java/io/jenkins/plugins/customizable_header/HeaderRootAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 116-129 are not covered by tests
}

@Exported(inline = true)
public List<AppNavLink> getFavorites() {
return CustomHeaderConfiguration.get().getFavorites();
}

@Override
public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object o)
throws IOException, ServletException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.jenkins.plugins.customizable_header;

import hudson.DescriptorExtensionList;
import hudson.model.Descriptor;
import jenkins.model.Jenkins;

public class LinkDescriptor extends Descriptor<AbstractLink> {
public static DescriptorExtensionList<AbstractLink, LinkDescriptor> all() {
return Jenkins.get().getDescriptorList(AbstractLink.class);

Check warning on line 9 in src/main/java/io/jenkins/plugins/customizable_header/LinkDescriptor.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 9 is not covered by tests
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.jenkins.plugins.customizable_header;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;

@ExportedBean
public class LinkSeparator extends AbstractLink {

private String title;

@DataBoundConstructor
public LinkSeparator() {
}

@DataBoundSetter
public void setTitle(String title) {
this.title = title;
}

@Exported
public String getTitle() {
return title;
}

@Exported
@Override
public String getType() {
return "separator";

Check warning on line 32 in src/main/java/io/jenkins/plugins/customizable_header/LinkSeparator.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 16-32 are not covered by tests
}

@Extension
public static class DescriptorImpl extends LinkDescriptor {
@Override
@NonNull
public String getDisplayName() {
return "Separator";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class UserHeader extends UserProperty {

private HeaderSelector headerSelector;

private List<AppNavLink> links = new ArrayList<>();
private List<AbstractLink> links = new ArrayList<>();

Check warning on line 30 in src/main/java/io/jenkins/plugins/customizable_header/UserHeader.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 30 is not covered by tests

private Set<String> dismissedMessages = new HashSet<>();

Expand Down Expand Up @@ -71,12 +71,12 @@ public boolean isOverwriteHeader() {
return overwriteHeader;
}

public List<AppNavLink> getLinks() {
public List<AbstractLink> getLinks() {
return links;
}

@DataBoundSetter
public void setLinks(List<AppNavLink> links) {
public void setLinks(List<AbstractLink> links) {
this.links = links;
}

Expand Down
75 changes: 41 additions & 34 deletions src/main/js/app-nav/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,49 +29,56 @@ function createElementFromHtml(html) {
};

function menuItem(options) {
const label = xmlEscape(options.label);
let linkIcon = "";
if (options.external) {
linkIcon = generateSVGIcon("external-link").outerHTML;
}
let color = "";
if (options.color) {
color = options.color;
let item = null;
if (options.type === "separator") {
if (options.title) {
item = createElementFromHtml(`
<div class="jenkins-dropdown__heading">${xmlEscape(options.title)}</div>
`);
} else {
item = separator();
}
} else {
const label = xmlEscape(options.label);
let linkIcon = "";
if (options.external) {
linkIcon = generateSVGIcon("external-link").outerHTML;
}
let color = "";
if (options.color) {
color = options.color;
}
item = createElementFromHtml(`
<a class="jenkins-dropdown__item" href="${options.linkUrl}" ${options.external? `target="_blank"` : ``}>
${
options.iconUrl
? `<div class="jenkins-dropdown__item__icon ${color}">${
options.iconXml
? options.iconXml
: `<img src="${options.iconUrl}" class="custom-header__link-image"/>`
}</div>`
: ``
}
${label} ${linkIcon}
</a>
`);
}
const item = createElementFromHtml(`
<a class="jenkins-dropdown__item" href="${options.linkUrl}" ${options.external? `target="_blank"` : ``}>
${
options.iconUrl
? `<div class="jenkins-dropdown__item__icon ${color}">${
options.iconXml
? options.iconXml
: `<img src="${options.iconUrl}" class="custom-header__link-image"/>`
}</div>`
: ``
}
${label} ${linkIcon}
</a>
`)
return item;
};

function generateItems(links, favorites) {
function separator() {
return createElementFromHtml(
`<div class="jenkins-dropdown__separator"></div>`,
);
}

function generateItems(links) {
const menuItems = document.createElement("div");
menuItems.classList.add("jenkins-dropdown");
links.map((item) => {
return menuItem(item);
})
.forEach((item) => menuItems.appendChild(item));
if (favorites.length != 0 && links.length != 0) {
const separator = createElementFromHtml(
`<div class="jenkins-dropdown__separator"></div>`,
);
menuItems.appendChild(separator);
}
favorites.map((item) => {
return menuItem(item);
})
.forEach((item) => menuItems.appendChild(item));

return menuItems;
}
Expand All @@ -80,7 +87,7 @@ function callback(element, instance) {
const href = element.dataset.href;

fetch(href).then((response) => response.json()).then(json => {
instance.setContent(generateItems(json.links, json.favorites));
instance.setContent(generateItems(json.links));
})
.catch((error) => console.log(`AppNav request failed: ${error}`))
.finally(() => (instance.loaded = true));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@
<f:advanced title="${%Links}">
<f:entry title="${%Application Links}" field="links">
<f:helpArea/>
<f:repeatableProperty field="links" add="${%Add Link}" header="${%Link}">
<f:repeatableDeleteButton/>
</f:repeatableProperty>
<f:repeatableHeteroProperty field="links" addCaption="${%Add Link}" hasHeader="true"/>
</f:entry>
</f:advanced>
</f:optionalBlock>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:entry field="title" title="${%Title}">
<f:textbox/>
</f:entry>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
When left empty a line will be used to separate the links.
Loading

0 comments on commit dd3dcbc

Please sign in to comment.