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

Reimplement the UI for LabelAxis #213

Merged
merged 4 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>script-security</artifactId>
</dependency>
<dependency>
<groupId>io.jenkins.plugins</groupId>
<artifactId>ionicons-api</artifactId>
</dependency>

<dependency>
<groupId>org.jenkins-ci.main</groupId>
Expand Down
64 changes: 43 additions & 21 deletions src/main/java/hudson/matrix/LabelAxis.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@
package hudson.matrix;

import hudson.Extension;
import java.io.IOException;
import java.util.Set;
import jenkins.model.Jenkins;
import hudson.model.labels.LabelAtom;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;

import java.util.ArrayList;
import java.util.List;

import static hudson.Functions.htmlAttributeEscape;
import static hudson.Functions.jsStringEscape;

/**
* {@link Axis} that selects label expressions.
*
Expand All @@ -56,16 +56,9 @@ public String getValueString() {
return String.join("/", getValues());
}

public String getValueStringHtmlEscaped() {
final List<String> values = getValues();
StringBuilder str = new StringBuilder();
for (String value : values) {
if (str.length() > 0) {
str.append('/');
}
str.append(htmlAttributeEscape(value));
}
return str.toString();
@Restricted(NoExternalUse.class)
public boolean isChecked(String name) {
return getValues().contains(name);
}

@Extension
Expand All @@ -85,13 +78,42 @@ public boolean isInstantiable() {
return !j.getNodes().isEmpty() || !j.clouds.isEmpty();
}

public String buildLabelCheckBox(LabelAtom la) {
final String escapedName = jsStringEscape(htmlAttributeEscape(la.getName()));
final String escapedDescription = jsStringEscape(StringUtils.isEmpty(la.getDescription()) ? "" :
htmlAttributeEscape(la.getDescription()));
return "<input type='checkbox' name='values' json='" +
escapedName + "' " + "/><label class='attach-previous'>" + escapedName +
" (" + escapedDescription + ")</label>";
@Restricted(NoExternalUse.class)
public LabelLists getLabelLists() {
return new LabelLists();
}

@Restricted(NoExternalUse.class)
public String getSaveDescription(LabelAtom labelAtom) throws IOException {
// remove line breaks as html tooltip will replace linebreaks with </br>.
// This ensures that the description is displayed in the same way as on the label
return Jenkins.get().getMarkupFormatter().translate(labelAtom.getDescription()).
replaceAll("\r", "").replaceAll("\n", "");
}
}

@Restricted(NoExternalUse.class)
public static class LabelLists {
private List<LabelAtom> machines = new ArrayList<>();
private List<LabelAtom> labels = new ArrayList<>();

public LabelLists() {
Set<LabelAtom> labelsAtoms = Jenkins.get().getLabelAtoms();
labelsAtoms.forEach(atom -> {
if (atom.isSelfLabel()) {
machines.add(atom);
} else {
labels.add(atom);
}
});
}

public List<LabelAtom> getMachines() {
return machines;
}

public List<LabelAtom> getLabels() {
return labels;
}
}
}
50 changes: 39 additions & 11 deletions src/main/resources/hudson/matrix/LabelAxis/config.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,45 @@ THE SOFTWARE.
<f:textbox default="label" />
</f:entry>
<f:entry title="${%Node/Label}" field="labels">
<div class="yahooTree labelAxis-tree" style="border: 1px solid gray; height: 10em; overflow:auto;" values="${instance.valueStringHtmlEscaped}" />
<st:adjunct includes="hudson.matrix.LabelAxis.label-axis-resources"/>
<f:invisibleEntry>
<span class="label-axis-i18n" data-i18n-labels="${%Labels}" data-i18n-individual-nodes="${%Individual nodes}"/>
<div class="label-axis-data-container">
<j:forEach var="l" items="${app.labelAtoms}">
<span data-label-atom="${l}"
data-label-checkbox="${descriptor.buildLabelCheckBox(l)}"
data-label="${l.isSelfLabel()?'machines':'labels'}"/>
</j:forEach>
<div class="jenkins-input" >
<j:set var="labelLists" value="${descriptor.labelLists}"/>
<j:set var="machines" value="${labelLists.machines}"/>
<j:set var="labels" value="${labelLists.labels}"/>
<div class="mp-label-axis">
<j:if test="${labels.size() gt 0}">
<div class="mp-label-axis__container">
<div class="jenkins-form-label">${%Labels}
<button type="button" class="jenkins-button mp-label-axis__button" data-hidden="true">
<l:icon src="symbol-chevron-down-outline plugin-ionicons-api" class="icon-sm"/>
</button>
</div>
<div class="jenkins-!-margin-left-3 mp-label-axis__list jenkins-hidden">
<j:forEach var="l" items="${labels}">
<div data-html-tooltip="${descriptor.getSaveDescription(l)}" class="mp-label-axis__tooltip">
<f:checkbox name="values" title="${l.name}" checked="${instance.isChecked(l.name)}" json="${l.name}"/>
</div>
</j:forEach>
</div>
</div>
</j:if>
<j:if test="${machines.size() gt 0}">
<div class="mp-label-axis__container">
<div class="jenkins-form-label">${%Individual Agents}
<button type="button" class="jenkins-button mp-label-axis__button" data-hidden="true">
<l:icon src="symbol-chevron-down-outline plugin-ionicons-api" class="icon-sm"/>
</button>
</div>
<div class="jenkins-!-margin-left-3 mp-label-axis__list jenkins-hidden">
<j:forEach var="l" items="${machines}">
<div data-html-tooltip="${descriptor.getSaveDescription(l)}" class="mp-label-axis__tooltip">
<f:checkbox name="values" title="${l.name}" checked="${instance.isChecked(l.name)}" json="${l.name}"/>
</div>
</j:forEach>
</div>
</div>
</j:if>
</div>
</f:invisibleEntry>
</div>
</f:entry>
<st:adjunct includes="hudson.matrix.LabelAxis.label-axis"/>
</j:jelly>
27 changes: 27 additions & 0 deletions src/main/resources/hudson/matrix/LabelAxis/label-axis.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.mp-label-axis__button {
min-height: 20px;
height: 20px;
padding: 0.5rem 0.3rem;
}

.mp-label-axis__button[data-hidden=false] {
rotate: 180deg;
}

.mp-label-axis__container > .jenkins-form-label {
display:flex;
gap: 5px;
align-items: center;
}

.mp-label-axis {
display: flex;
gap: 10px;
flex-direction: column;
max-height: 300px;
overflow: auto;
}

.mp-label-axis__tooltip {
width: fit-content;
}
16 changes: 16 additions & 0 deletions src/main/resources/hudson/matrix/LabelAxis/label-axis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Behaviour.specify(".mp-label-axis__button", "mp-label-container", 0, function(btn) {
btn.addEventListener("click", function(evt) {
const container = btn.closest(".mp-label-axis__container");
if (container) {
const labelList = container.querySelector(".mp-label-axis__list");
if (labelList) {
labelList.classList.toggle("jenkins-hidden");
if (btn.dataset.hidden === "true") {
btn.dataset.hidden = "false";
} else {
btn.dataset.hidden = "true";
}
}
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,6 @@ THE SOFTWARE.
<p:config-trigger/>

<f:section title="${%Configuration Matrix}">
<!-- Needed for LabelAxis, but including these from LabelAxis/config.jelly is too late -->
<l:yui module="treeview" />
<link rel="stylesheet" type="text/css"
href="${rootURL}/scripts/yui/treeview/assets/skins/sam/treeview.css" />

<f:block>
<f:hetero-list name="axis" hasHeader="true"
descriptors="${descriptor.axisDescriptors}"
Expand Down