From 93e44fbb3264ef2d693c3a5b2a04b2dfe0b9573e Mon Sep 17 00:00:00 2001 From: frankie Date: Tue, 12 Jul 2022 10:06:31 +0800 Subject: [PATCH 01/20] [JENKINS-68985] Context menu shadow looks terrible on redesigned "About Jenkins" page --- war/src/main/less/abstracts/theme.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/war/src/main/less/abstracts/theme.less b/war/src/main/less/abstracts/theme.less index 52fbc76e43df..6ecb956a539d 100644 --- a/war/src/main/less/abstracts/theme.less +++ b/war/src/main/less/abstracts/theme.less @@ -280,7 +280,7 @@ --menu-text-color: black; --menu-bg-color: var(--white); --menu-selected-color: #b3d4ff; - --menu-box-shadow: 0 3px 10px #bbb; + --menu-box-shadow: 0 3px 10px #3e3e3e; // Manage component --manage-option-bg-color--hover: var(--very-light-grey); From 11345540139711dddd3d1f8140c0bef312392c8b Mon Sep 17 00:00:00 2001 From: frankie Date: Tue, 12 Jul 2022 21:41:12 +0800 Subject: [PATCH 02/20] [JENKINS-68913] Bottom bar for update center "Updates" view --- war/src/main/less/abstracts/theme.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/war/src/main/less/abstracts/theme.less b/war/src/main/less/abstracts/theme.less index 6ecb956a539d..33fe61774d64 100644 --- a/war/src/main/less/abstracts/theme.less +++ b/war/src/main/less/abstracts/theme.less @@ -280,7 +280,7 @@ --menu-text-color: black; --menu-bg-color: var(--white); --menu-selected-color: #b3d4ff; - --menu-box-shadow: 0 3px 10px #3e3e3e; + --menu-box-shadow: 0 3px 10px rgba(0, 0, 0, 0.3); // Manage component --manage-option-bg-color--hover: var(--very-light-grey); From 8bb8a6ec4fcd3496a770cdd164460026fffc563a Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Wed, 13 Jul 2022 10:05:17 -0700 Subject: [PATCH 03/20] Remove redundant JUnit Jupiter dependencies --- cli/pom.xml | 12 ------------ core/pom.xml | 12 ------------ war/pom.xml | 12 ------------ 3 files changed, 36 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 2bfaf2dd6a86..1338f2dfae5f 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -106,18 +106,6 @@ ${junit.jupiter.version} test - - org.junit.jupiter - junit-jupiter-api - ${junit.jupiter.version} - test - - - org.junit.jupiter - junit-jupiter-engine - ${junit.jupiter.version} - test - diff --git a/core/pom.xml b/core/pom.xml index fe1415c82182..cda15a406ff3 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -521,18 +521,6 @@ THE SOFTWARE. ${junit.jupiter.version} test - - org.junit.jupiter - junit-jupiter-api - ${junit.jupiter.version} - test - - - org.junit.jupiter - junit-jupiter-engine - ${junit.jupiter.version} - test - org.junit.vintage junit-vintage-engine diff --git a/war/pom.xml b/war/pom.xml index 1db05dc7b875..199b532a87f7 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -146,18 +146,6 @@ THE SOFTWARE. ${junit.jupiter.version} test - - org.junit.jupiter - junit-jupiter-api - ${junit.jupiter.version} - test - - - org.junit.jupiter - junit-jupiter-engine - ${junit.jupiter.version} - test - From d977ee8741ed6362dac282e69a317a070ee9d813 Mon Sep 17 00:00:00 2001 From: Kevin Guerroudj <91883215+Kevin-CB@users.noreply.github.com> Date: Sat, 16 Jul 2022 00:41:39 +0200 Subject: [PATCH 04/20] [JENKINS-68805] Demonstrate that `IconSet` doesn't reset attributes with ampersand (#6685) --- .../test/java/org/jenkins/ui/icon/IconSetTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/src/test/java/org/jenkins/ui/icon/IconSetTest.java b/core/src/test/java/org/jenkins/ui/icon/IconSetTest.java index 7c462457fdd8..3a48f66ac97c 100644 --- a/core/src/test/java/org/jenkins/ui/icon/IconSetTest.java +++ b/core/src/test/java/org/jenkins/ui/icon/IconSetTest.java @@ -6,6 +6,7 @@ import static org.hamcrest.Matchers.not; import java.util.Map; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; public class IconSetTest { @@ -66,4 +67,16 @@ void getSymbol_notSettingTooltipDoesntAddTooltipAttribute() { assertThat(symbol, not(containsString("tooltip"))); } + + /** + * Culprit: https://github.com/jenkinsci/jenkins/blob/ab0bb8495819bd807a9211ac0df3f08e420226f1/core/src/main/java/org/jenkins/ui/icon/IconSet.java#L97= + * If the tooltip contains an ampersand symbol (&), it won't be removed. + */ + @Disabled("TODO see JENKINS-68805") + @Test + void getSymbol_notSettingTooltipDoesntAddTooltipAttribute_evenWithAmpersand() { + String symbol = IconSet.getSymbol("download", "Title", "With&Ampersand", "class1 class2", "", "id"); + + assertThat(symbol, not(containsString("tooltip"))); + } } From 91f972de9133f1d9d74abd74cf980d1adf154d65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 17 Jul 2022 09:49:22 -0700 Subject: [PATCH 05/20] Bump matrix-project from 772.v494f19991984 to 785.v06b_7f47b_c631 (#6850) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- test/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pom.xml b/test/pom.xml index 491a8d20cd10..85f24fcdc679 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -180,7 +180,7 @@ THE SOFTWARE. org.jenkins-ci.plugins matrix-project - 772.v494f19991984 + 785.v06b_7f47b_c631 test From 0143c86ee0234bcc37920a508961013ebb88b639 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 17 Jul 2022 09:50:44 -0700 Subject: [PATCH 06/20] Upgrade Spring Framework from 5.3.21 to 5.3.22 (#6844) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- bom/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/pom.xml b/bom/pom.xml index 6b8d1b3c6246..37895381a44b 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -56,7 +56,7 @@ THE SOFTWARE. org.springframework spring-framework-bom - 5.3.21 + 5.3.22 pom import From 4a7c9ae3947ed548972514d466e668da6c8c68c0 Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Mon, 18 Jul 2022 12:40:27 +0100 Subject: [PATCH 07/20] Rework "Updates" table checkbox selection controls (#6806) Co-authored-by: Tim Jacomb --- .../main/java/hudson/model/UpdateCenter.java | 6 + .../resources/hudson/PluginManager/_table.js | 84 ++++---- .../hudson/PluginManager/index.jelly | 6 - .../hudson/PluginManager/table.jelly | 16 +- .../hudson/PluginManager/table.properties | 3 +- .../lib/layout/rowSelectionController.jelly | 66 ++++++ war/.eslintrc.js | 2 +- .../row-selection-controller/index.js | 74 +++++++ .../modules/row-selection-controller.less | 196 ++++++++++++++++++ war/src/main/less/styles.less | 1 + .../main/resources/images/symbols/check.svg | 1 + .../resources/images/symbols/compatible.svg | 1 + .../images/symbols/indeterminate.svg | 1 + .../main/resources/images/symbols/none.svg | 1 + .../main/webapp/scripts/hudson-behavior.js | 13 -- war/webpack.config.js | 2 + 16 files changed, 409 insertions(+), 64 deletions(-) create mode 100644 core/src/main/resources/lib/layout/rowSelectionController.jelly create mode 100644 war/src/main/js/components/row-selection-controller/index.js create mode 100644 war/src/main/less/modules/row-selection-controller.less create mode 100644 war/src/main/resources/images/symbols/check.svg create mode 100644 war/src/main/resources/images/symbols/compatible.svg create mode 100644 war/src/main/resources/images/symbols/indeterminate.svg create mode 100644 war/src/main/resources/images/symbols/none.svg diff --git a/core/src/main/java/hudson/model/UpdateCenter.java b/core/src/main/java/hudson/model/UpdateCenter.java index 3aad76f1f043..1307381b48ff 100644 --- a/core/src/main/java/hudson/model/UpdateCenter.java +++ b/core/src/main/java/hudson/model/UpdateCenter.java @@ -1065,6 +1065,12 @@ public List getUpdates() { return new ArrayList<>(pluginMap.values()); } + // for Jelly + @Restricted(NoExternalUse.class) + public boolean hasIncompatibleUpdates(PluginManager.MetadataCache cache) { + return getUpdates().stream().anyMatch(plugin -> !plugin.isCompatible(cache)); + } + @Restricted(NoExternalUse.class) public List getPluginsWithUnavailableUpdates() { Map pluginMap = new LinkedHashMap<>(); diff --git a/core/src/main/resources/hudson/PluginManager/_table.js b/core/src/main/resources/hudson/PluginManager/_table.js index 45e431338cdf..8f32bb57273f 100644 --- a/core/src/main/resources/hudson/PluginManager/_table.js +++ b/core/src/main/resources/hudson/PluginManager/_table.js @@ -1,13 +1,3 @@ -function checkPluginsWithoutWarnings() { - var inputs = document.getElementsByTagName('input'); - for(var i = 0; i < inputs.length; i++) { - var candidate = inputs[i]; - if(candidate.type === "checkbox" && !candidate.disabled) { - candidate.checked = candidate.dataset.compatWarning === 'false'; - } - } -} - Behaviour.specify("#filter-box", '_table', 0, function(e) { function applyFilter() { var filter = e.value.toLowerCase().trim(); @@ -80,11 +70,11 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { /** * Wait for document onload. - */ - Element.observe(window, "load", function() { + */ + Element.observe(window, "load", function() { var pluginsTable = select('#plugins'); var pluginTRs = selectAll('.plugin', pluginsTable); - + if (!pluginTRs) { return; } @@ -93,16 +83,16 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { function i18n(messageId) { return pluginI18n.getAttribute('data-' + messageId); } - + // Create a map of the plugin rows, making it easy to index them. var plugins = {}; for (var i = 0; i < pluginTRs.length; i++) { var pluginTR = pluginTRs[i]; var pluginId = pluginTR.getAttribute('data-plugin-id'); - + plugins[pluginId] = pluginTR; } - + function getPluginTR(pluginId) { return plugins[pluginId]; } @@ -114,24 +104,24 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { return pluginId; } } - + function processSpanSet(spans) { var ids = []; for (var i = 0; i < spans.length; i++) { var span = spans[i]; var pluginId = span.getAttribute('data-plugin-id'); var pluginName = getPluginName(pluginId); - + span.update(pluginName); ids.push(pluginId); } return ids; } - + function markAllDependentsDisabled(pluginTR) { var jenkinsPluginMetadata = pluginTR.jenkinsPluginMetadata; var dependentIds = jenkinsPluginMetadata.dependentIds; - + if (dependentIds) { // If the only dependent is jenkins-core (it's a bundle plugin), then lets // treat it like all its dependents are disabled. We're really only interested in @@ -151,7 +141,7 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { pluginTR.removeClassName('all-dependents-disabled'); return; } - + // The dependent is a plugin.... var dependentPluginTr = getPluginTR(dependentId); if (dependentPluginTr && dependentPluginTr.jenkinsPluginMetadata.enableInput.checked) { @@ -161,14 +151,14 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { } } } - + pluginTR.addClassName('all-dependents-disabled'); } function markHasDisabledDependencies(pluginTR) { var jenkinsPluginMetadata = pluginTR.jenkinsPluginMetadata; var dependencyIds = jenkinsPluginMetadata.dependencyIds; - + if (dependencyIds) { for (var i = 0; i < dependencyIds.length; i++) { var dependencyPluginTr = getPluginTR(dependencyIds[i]); @@ -179,10 +169,10 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { } } } - + pluginTR.removeClassName('has-disabled-dependency'); } - + function setEnableWidgetStates() { for (var i = 0; i < pluginTRs.length; i++) { var pluginMetadata = pluginTRs[i].jenkinsPluginMetadata; @@ -195,7 +185,7 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { markHasDisabledDependencies(pluginTRs[i]); } } - + function addDependencyInfoRow(pluginTR, infoTR) { infoTR.addClassName('plugin-dependency-info'); pluginTR.insert({ @@ -212,7 +202,7 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { } } - function populateEnableDisableInfo(pluginTR, infoContainer) { + function populateEnableDisableInfo(pluginTR, infoContainer) { var pluginMetadata = pluginTR.jenkinsPluginMetadata; // Remove all existing class info @@ -224,9 +214,9 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { var dependencySpans = pluginMetadata.dependencies; infoContainer.update('
' + i18n('cannot-enable') + '
' + i18n('disabled-dependencies') + '.
'); - + // Go through each dependency element. Show the spans where the dependency is - // disabled. Hide the others. + // disabled. Hide the others. for (var i = 0; i < dependencySpans.length; i++) { var dependencySpan = dependencySpans[i]; var pluginId = dependencySpan.getAttribute('data-plugin-id'); @@ -244,15 +234,15 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { dependencySpan.setStyle({display: 'inline-block'}); } } - + dependenciesDiv.setStyle({display: 'inherit'}); infoContainer.appendChild(dependenciesDiv); - + return true; } if (pluginTR.hasClassName('has-dependents')) { if (!pluginTR.hasClassName('all-dependents-disabled')) { var dependentIds = pluginMetadata.dependentIds; - + // If the only dependent is jenkins-core (it's a bundle plugin), then lets // treat it like all its dependents are disabled. We're really only interested in // dependent plugins in this case. @@ -273,7 +263,7 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { infoContainer.appendChild(getDependentsDiv(pluginTR, true)); return true; } - + return false; } @@ -333,7 +323,7 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { var dependentsDiv = select('.dependent-list', pluginTR); var enableTD = select('td.enable', pluginTR); var uninstallTD = select('td.uninstall', pluginTR); - + pluginTR.jenkinsPluginMetadata = { enableInput: enableInput, dependenciesDiv: dependenciesDiv, @@ -348,7 +338,7 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { pluginTR.jenkinsPluginMetadata.dependents = selectAll('span', dependentsDiv); pluginTR.jenkinsPluginMetadata.dependentIds = processSpanSet(pluginTR.jenkinsPluginMetadata.dependents); } - + // Setup event handlers... if (enableInput) { // Toggling of the enable/disable checkbox requires a check and possible @@ -357,8 +347,8 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { setEnableWidgetStates(); }); } - - // + + // var infoTR = document.createElement("tr"); var infoTD = document.createElement("td"); var infoDiv = document.createElement("div"); @@ -377,7 +367,7 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { } showInfoTimeout = undefined; } - + // Handle mouse in/out of the enable/disable cell (left most cell). if (enableTD) { Element.observe(enableTD, 'mouseenter', function() { @@ -416,11 +406,25 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) { for (var i = 0; i < pluginTRs.length; i++) { initPluginRowHandling(pluginTRs[i]); } - + setEnableWidgetStates(); }); }()); Element.observe(window, "load", function() { document.getElementById('filter-box').focus(); -}); \ No newline at end of file + + const compatibleCheckbox = document.querySelector("[data-select='compatible']"); + if (compatibleCheckbox) { + compatibleCheckbox.addEventListener("click", () => { + const inputs = document.getElementsByTagName('input'); + for (let i = 0; i < inputs.length; i++) { + const candidate = inputs[i]; + if (candidate.type === "checkbox" && !candidate.disabled) { + candidate.checked = candidate.dataset.compatWarning === 'false'; + } + } + window.updateTableHeaderCheckbox(); + }) + } +}); diff --git a/core/src/main/resources/hudson/PluginManager/index.jelly b/core/src/main/resources/hudson/PluginManager/index.jelly index 27970a070912..73a52662420a 100644 --- a/core/src/main/resources/hudson/PluginManager/index.jelly +++ b/core/src/main/resources/hudson/PluginManager/index.jelly @@ -29,12 +29,6 @@ THE SOFTWARE.
- - ${%Select}: ${%All}, - ${%Compatible}, - ${%None}
-
- ${%UpdatePageDescription}
${%UpdatePageLegend(rootURL+'/updateCenter/')}
diff --git a/core/src/main/resources/hudson/PluginManager/table.jelly b/core/src/main/resources/hudson/PluginManager/table.jelly index 4f0976b5e121..5ffcfebf7d21 100644 --- a/core/src/main/resources/hudson/PluginManager/table.jelly +++ b/core/src/main/resources/hudson/PluginManager/table.jelly @@ -59,12 +59,23 @@ THE SOFTWARE.
+ + - @@ -77,7 +88,6 @@ THE SOFTWARE. -
- ${%Install} + + + + + + ${%Name}
This plugin is deprecated. In general, this means that it is either obsolete, no longer being developed, or may no longer work. Learn more. -loading=Loading \ No newline at end of file +loading=Loading +CompatibleTooltip=Selects plugin updates that are compatible with their current version diff --git a/core/src/main/resources/lib/layout/rowSelectionController.jelly b/core/src/main/resources/lib/layout/rowSelectionController.jelly new file mode 100644 index 000000000000..addcedf1f21b --- /dev/null +++ b/core/src/main/resources/lib/layout/rowSelectionController.jelly @@ -0,0 +1,66 @@ + + + + + + Controls checkbox selections in tables + + +
+ + + + + + + + + +
+ ${%Select} + + + +
+
+
+ + +
diff --git a/war/.eslintrc.js b/war/.eslintrc.js index 928e89e7891d..2df6887785cd 100644 --- a/war/.eslintrc.js +++ b/war/.eslintrc.js @@ -7,7 +7,7 @@ module.exports = { // Uses eslint default ruleset extends: "eslint:recommended", parserOptions: { - ecmaVersion: 2018, + ecmaVersion: 2020, sourceType: "module" }, rules: { diff --git a/war/src/main/js/components/row-selection-controller/index.js b/war/src/main/js/components/row-selection-controller/index.js new file mode 100644 index 000000000000..3b23aa466303 --- /dev/null +++ b/war/src/main/js/components/row-selection-controller/index.js @@ -0,0 +1,74 @@ +const rowSelectionControllers = document.querySelectorAll(".jenkins-table__checkbox") + +rowSelectionControllers.forEach(headerCheckbox => { + const table = headerCheckbox.closest(".jenkins-table") + const tableCheckboxes = table.querySelectorAll("input[type='checkbox']") + const moreOptionsButton = table.querySelector(".jenkins-table__checkbox-options") + const moreOptionsDropdown = table.querySelector(".jenkins-table__checkbox-dropdown") + const moreOptionsAllButton = table.querySelector("[data-select='all']") + const moreOptionsNoneButton = table.querySelector("[data-select='none']") + + if (tableCheckboxes.length === 0) { + headerCheckbox.disabled = true; + moreOptionsButton.disabled = true; + } + + const allCheckboxesSelected = () => { + return tableCheckboxes.length === [...tableCheckboxes].filter(e => e.checked).length + } + + const anyCheckboxesSelected = () => { + return [...tableCheckboxes].filter(e => e.checked).length > 0 + } + + tableCheckboxes.forEach(checkbox => { + checkbox.addEventListener("change", () => { + updateIcon() + }) + }) + + headerCheckbox.addEventListener("click", () => { + const newValue = !allCheckboxesSelected() + tableCheckboxes.forEach(e => e.checked = newValue) + updateIcon() + }) + + moreOptionsAllButton?.addEventListener("click", () => { + tableCheckboxes.forEach(e => e.checked = true) + updateIcon() + }) + + moreOptionsNoneButton?.addEventListener("click", () => { + tableCheckboxes.forEach(e => e.checked = false) + updateIcon() + }) + + function updateIcon() { + headerCheckbox.classList.remove("jenkins-table__checkbox--all") + headerCheckbox.classList.remove("jenkins-table__checkbox--indeterminate") + moreOptionsDropdown.classList.remove("jenkins-table__checkbox-dropdown--visible") + + if (allCheckboxesSelected()) { + headerCheckbox.classList.add("jenkins-table__checkbox--all") + return + } + + if (anyCheckboxesSelected()) { + headerCheckbox.classList.add("jenkins-table__checkbox--indeterminate") + } + } + + document.addEventListener("click", event => { + if (moreOptionsDropdown?.contains(event.target) || event.target === moreOptionsButton) { + return + } + + moreOptionsDropdown?.classList.remove("jenkins-table__checkbox-dropdown--visible") + }) + + moreOptionsButton?.addEventListener("click", () => { + moreOptionsDropdown.classList.toggle("jenkins-table__checkbox-dropdown--visible") + }) + + window.updateTableHeaderCheckbox = updateIcon +}) diff --git a/war/src/main/less/modules/row-selection-controller.less b/war/src/main/less/modules/row-selection-controller.less new file mode 100644 index 000000000000..533b267f69ec --- /dev/null +++ b/war/src/main/less/modules/row-selection-controller.less @@ -0,0 +1,196 @@ +.jenkins-table__checkbox-container { + position: relative; + display: flex; +} + +.jenkins-table__checkbox { + appearance: none; + display: inline-grid; + width: 1.375rem; + height: 1.375rem; + background: transparent; + outline: none; + border: none; + box-shadow: 0 0 0 10px transparent, inset 0 0 0 0.125rem var(--input-border); + border-radius: 6px; + transition: var(--standard-transition); + cursor: pointer; + padding: 0; + + &:hover { + box-shadow: 0 0 0 10px transparent, inset 0 0 0 0.3125rem var(--input-border-hover); + } + + &:active, &:focus { + box-shadow: 0 0 0 5px var(--focus-input-glow), inset 0 0 0 0.3125rem var(--focus-input-border); + } + + svg { + width: 0.85rem; + height: 0.85rem; + grid-column-start: 1; + grid-row-start: 1; + justify-self: center; + align-self: center; + transition: var(--elastic-transition); + transform: scale(0); + color: var(--text-color); + opacity: 0; + + * { + stroke-width: 60px; + } + } + + .jenkins-table__checkbox__all-symbol { + color: var(--background); + } + + &--indeterminate { + .jenkins-table__checkbox__indeterminate-symbol { + transform: scale(1); + opacity: 1; + } + } + + &--all { + box-shadow: 0 0 0 10px transparent, inset 0 0 0 0.6875rem var(--focus-input-border); + + &:hover { + box-shadow: 0 0 0 10px transparent, inset 0 0 0 1.375rem var(--focus-input-border); + } + + &:active, &:focus { + box-shadow: 0 0 0 5px var(--focus-input-glow), inset 0 0 0 1.375rem var(--focus-input-border); + } + + .jenkins-table__checkbox__all-symbol { + transform: scale(1); + opacity: 1; + } + } + + &[disabled], &:disabled { + pointer-events: none; + opacity: 0.2; + transition: none; + } +} + +.jenkins-table__checkbox-options { + height: 1.375rem; + min-height: unset; + padding: 0 0.2rem; + margin-left: 0.2rem; + border-radius: 6px; + + svg { + width: 0.9rem; + height: 0.9rem; + pointer-events: none; + color: var(--input-border); + transition: var(--standard-transition); + + * { + stroke-width: 60px; + } + } + + &:hover { + svg { + color: var(--input-border-hover); + } + } + + &:active, &:focus { + svg { + color: var(--focus-input-border); + } + } + + &[disabled], &:disabled { + pointer-events: none; + opacity: 0.2; + transition: none; + } +} + +.jenkins-table__checkbox-dropdown { + position: absolute; + top: 1.375rem; + left: 1.375rem; + display: flex; + flex-direction: column; + border-radius: 10px; + z-index: 999; + padding: 0.4rem; + box-shadow: inset 0 0 0 2px rgba(white, 0.05), 0 0 0 1px rgba(black, 0.05), 0 5px 10px rgba(black, 0.1), 0 5px 20px rgba(black, 0.3); + min-width: 170px; + opacity: 0; + visibility: collapse; + transform: scale(0.9); + transform-origin: top left; + transition: var(--standard-transition); + + &::before, &::after { + content: ""; + position: absolute; + inset: 0; + border-radius: inherit; + z-index: -1; + } + + &::before { + backdrop-filter: brightness(3) blur(5px); + } + + &::after { + background: var(--background); + opacity: 0.9; + } + + span { + font-size: 0.8rem; + color: var(--text-color-secondary); + padding: 0.5rem 0.75rem; + } + + .jenkins-button { + justify-content: flex-start; + gap: 0.75rem; + border-radius: 10px; + } + + &--visible { + opacity: 1; + visibility: visible; + transform: scale(1); + } +} + +.jenkins-table__checkbox-dropdown__icon { + position: relative; + width: 1.1rem; + height: 1.1rem; + display: flex; + align-items: center; + justify-content: center; + + &::before { + content: ""; + position: absolute; + inset: -0.15rem; + border: 0.1rem solid var(--text-color-secondary); + border-radius: 6px; + opacity: 0.25; + } + + svg { + width: 0.9rem; + height: 0.9rem; + + * { + stroke-width: 50px; + } + } +} diff --git a/war/src/main/less/styles.less b/war/src/main/less/styles.less index 5970cbd42b90..7efe73289a1d 100644 --- a/war/src/main/less/styles.less +++ b/war/src/main/less/styles.less @@ -32,6 +32,7 @@ @import "./modules/side-panel-widgets"; @import "./modules/spinner"; @import "./modules/progress-animation"; +@import "./modules/row-selection-controller"; @import "./modules/table"; @import "./modules/tabs"; @import "./modules/tooltips"; diff --git a/war/src/main/resources/images/symbols/check.svg b/war/src/main/resources/images/symbols/check.svg new file mode 100644 index 000000000000..d1de5267065b --- /dev/null +++ b/war/src/main/resources/images/symbols/check.svg @@ -0,0 +1 @@ +Checkmark \ No newline at end of file diff --git a/war/src/main/resources/images/symbols/compatible.svg b/war/src/main/resources/images/symbols/compatible.svg new file mode 100644 index 000000000000..1827802ee520 --- /dev/null +++ b/war/src/main/resources/images/symbols/compatible.svg @@ -0,0 +1 @@ +Checkmark Done \ No newline at end of file diff --git a/war/src/main/resources/images/symbols/indeterminate.svg b/war/src/main/resources/images/symbols/indeterminate.svg new file mode 100644 index 000000000000..8c00ac661465 --- /dev/null +++ b/war/src/main/resources/images/symbols/indeterminate.svg @@ -0,0 +1 @@ +Remove \ No newline at end of file diff --git a/war/src/main/resources/images/symbols/none.svg b/war/src/main/resources/images/symbols/none.svg new file mode 100644 index 000000000000..d1ccc185379c --- /dev/null +++ b/war/src/main/resources/images/symbols/none.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/war/src/main/webapp/scripts/hudson-behavior.js b/war/src/main/webapp/scripts/hudson-behavior.js index 01c7ac69028a..fbfc5a2ebfd4 100644 --- a/war/src/main/webapp/scripts/hudson-behavior.js +++ b/war/src/main/webapp/scripts/hudson-behavior.js @@ -2205,19 +2205,6 @@ function buildFormTree(form) { } } -/** - * @param {boolean} toggle - * When true, will check all checkboxes in the page. When false, unchecks them all. - */ -var toggleCheckboxes = function(toggle) { - var inputs = document.getElementsByTagName("input"); - for(var i=0; i ({ "sortable-drag-drop": [path.join(__dirname, "src/main/js/sortable-drag-drop.js")], "section-to-sidebar-items": [path.join(__dirname, "src/main/js/section-to-sidebar-items.js")], "section-to-tabs": [path.join(__dirname, "src/main/js/section-to-tabs.js")], + "components/row-selection-controller": + [path.join(__dirname, "src/main/js/components/row-selection-controller")], "filter-build-history": [path.join(__dirname, "src/main/js/filter-build-history.js")], "simple-page": [path.join(__dirname, "src/main/less/simple-page.less")], "styles": [path.join(__dirname, "src/main/less/styles.less")], From d03b1ba20af7a10b485f456cbca4d234521afe07 Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Mon, 18 Jul 2022 12:40:41 +0100 Subject: [PATCH 08/20] Remove the 'New View' sidebar link (#6703) Co-authored-by: Alexander Brandes --- .../main/java/jenkins/model/NewViewLink.java | 67 ------------------ .../jenkins/model/Messages.properties | 2 - .../jenkins/model/Messages_de.properties | 2 - .../jenkins/model/Messages_fr.properties | 2 - .../jenkins/model/Messages_it.properties | 1 - .../jenkins/model/Messages_ja.properties | 2 - .../jenkins/model/Messages_ko.properties | 2 - .../jenkins/model/Messages_pt_BR.properties | 1 - .../jenkins/model/Messages_ru.properties | 1 - .../jenkins/model/Messages_tr.properties | 1 - .../jenkins/model/Messages_zh_TW.properties | 2 - .../resources/lib/layout/tabNewItem.jelly | 2 +- .../java/jenkins/model/NewViewLinkTest.java | 70 ------------------- .../JenkinsLocationConfigurationTest.java | 3 +- 14 files changed, 3 insertions(+), 155 deletions(-) delete mode 100644 core/src/main/java/jenkins/model/NewViewLink.java delete mode 100644 core/src/test/java/jenkins/model/NewViewLinkTest.java diff --git a/core/src/main/java/jenkins/model/NewViewLink.java b/core/src/main/java/jenkins/model/NewViewLink.java deleted file mode 100644 index 65ce84928c34..000000000000 --- a/core/src/main/java/jenkins/model/NewViewLink.java +++ /dev/null @@ -1,67 +0,0 @@ -package jenkins.model; - -import com.google.common.annotations.VisibleForTesting; -import hudson.Extension; -import hudson.model.Action; -import hudson.model.ModifiableViewGroup; -import hudson.model.TransientViewActionFactory; -import hudson.model.View; -import hudson.model.ViewGroup; -import java.util.Collections; -import java.util.List; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; - -@Extension -@Restricted(NoExternalUse.class) -public class NewViewLink extends TransientViewActionFactory { - - @VisibleForTesting - static final String ICON_FILE_NAME = "folder"; - @VisibleForTesting - public static final String URL_NAME = "newView"; - - @Override - public List createFor(final View v) { - // do not show the action if the viewgroup is not modifiable - ViewGroup vg = v.getOwner(); - if (vg instanceof ModifiableViewGroup) { - return List.of(new NewViewLinkAction((ModifiableViewGroup) vg)); - } - return Collections.emptyList(); - } - - private static class NewViewLinkAction implements Action { - - private ModifiableViewGroup target; - - private NewViewLinkAction(ModifiableViewGroup target) { - this.target = target; - } - - @Override - public String getIconFileName() { - if (hasPermission()) { - return ICON_FILE_NAME; - } - return null; - } - - @Override - public String getDisplayName() { - return Messages.NewViewLink_NewView(); - } - - @Override - public String getUrlName() { - // getUrl returns the path from the root (without the context and no leading slash) - // we need to add the slash so that this is not relative to the current URL but to the context - return "/" + target.getUrl() + URL_NAME; - } - - private boolean hasPermission() { - return target.hasPermission(View.CREATE); - } - - } -} diff --git a/core/src/main/resources/jenkins/model/Messages.properties b/core/src/main/resources/jenkins/model/Messages.properties index de5287bc8c20..9f956b5639da 100644 --- a/core/src/main/resources/jenkins/model/Messages.properties +++ b/core/src/main/resources/jenkins/model/Messages.properties @@ -61,8 +61,6 @@ JenkinsLocationConfiguration.does_not_look_like_an_email_address=Does not look l Mailer.Localhost.Error=Please set a valid host name, instead of localhost Mailer.NotHttp.Error=The URL is invalid, please ensure you are using http:// or https:// with a valid domain. -NewViewLink.NewView=New View - PatternProjectNamingStrategy.DisplayName=Pattern PatternProjectNamingStrategy.NamePatternRequired=Name Pattern is required PatternProjectNamingStrategy.NamePatternInvalidSyntax=regular expression''s syntax is invalid. diff --git a/core/src/main/resources/jenkins/model/Messages_de.properties b/core/src/main/resources/jenkins/model/Messages_de.properties index a4aafa192b44..c213d03ebf8d 100644 --- a/core/src/main/resources/jenkins/model/Messages_de.properties +++ b/core/src/main/resources/jenkins/model/Messages_de.properties @@ -49,8 +49,6 @@ DefaultProjectNamingStrategy.DisplayName=keine Einschränkung Mailer.Address.Not.Configured=Adresse nicht konfiguriert Mailer.Localhost.Error=Bitte verwenden Sie einen konkreten Hostnamen anstelle von localhost . -NewViewLink.NewView=Ansicht anlegen - PatternProjectNamingStrategy.DisplayName=Muster PatternProjectNamingStrategy.NamePatternRequired=Der Reguläre Ausdruck darf nicht leer sein. PatternProjectNamingStrategy.NamePatternInvalidSyntax=Der Reguläre Ausdruck ist ungültig. diff --git a/core/src/main/resources/jenkins/model/Messages_fr.properties b/core/src/main/resources/jenkins/model/Messages_fr.properties index 063e401c59a1..86a486bebe38 100644 --- a/core/src/main/resources/jenkins/model/Messages_fr.properties +++ b/core/src/main/resources/jenkins/model/Messages_fr.properties @@ -52,5 +52,3 @@ ParameterizedJobMixIn.build_now=Lancer un build BlockedBecauseOfBuildInProgress.shortDescription=Le build #{0} est déjà en cours {1} BlockedBecauseOfBuildInProgress.ETA=\ (fin prévue à : {0}) BuildDiscarderProperty.displayName=Supprimer les anciens builds - -NewViewLink.NewView=Créer une Vue diff --git a/core/src/main/resources/jenkins/model/Messages_it.properties b/core/src/main/resources/jenkins/model/Messages_it.properties index 2388568df935..fb207588ae44 100644 --- a/core/src/main/resources/jenkins/model/Messages_it.properties +++ b/core/src/main/resources/jenkins/model/Messages_it.properties @@ -72,7 +72,6 @@ Mailer.Address.Not.Configured=Indirizzo non ancora configurato \ Mailer.Localhost.Error=Impostare un nome host valido anziché localhost Mailer.NotHttp.Error=L''URL non è valido, assicurarsi di utilizzare http:// o \ https:// con un dominio valido. -NewViewLink.NewView=Nuova vista ParameterizedJobMixIn.build_now=Compila ora ParameterizedJobMixIn.build_with_parameters=Compila con parametri PatternProjectNamingStrategy.DisplayName=Pattern diff --git a/core/src/main/resources/jenkins/model/Messages_ja.properties b/core/src/main/resources/jenkins/model/Messages_ja.properties index 26c941c645e7..0eb0d10b1c0c 100644 --- a/core/src/main/resources/jenkins/model/Messages_ja.properties +++ b/core/src/main/resources/jenkins/model/Messages_ja.properties @@ -52,8 +52,6 @@ CLI.safe-shutdown.shortDescription=\ DefaultProjectNamingStrategy.DisplayName=デフォルト -NewViewLink.NewView=新規ビュー - PatternProjectNamingStrategy.DisplayName=パターン PatternProjectNamingStrategy.NamePatternRequired=パターンは必須です。 PatternProjectNamingStrategy.NamePatternInvalidSyntax=正規表現に誤りがあります。 diff --git a/core/src/main/resources/jenkins/model/Messages_ko.properties b/core/src/main/resources/jenkins/model/Messages_ko.properties index 0ffab2537d1c..be79243c6eff 100644 --- a/core/src/main/resources/jenkins/model/Messages_ko.properties +++ b/core/src/main/resources/jenkins/model/Messages_ko.properties @@ -4,8 +4,6 @@ Hudson.ViewAlreadyExists="{0}" 이름의 뷰가 이미 존재합니다. CLI.restart.shortDescription=Jenkins를 안전하게 재시작 -NewViewLink.NewView=새로운 뷰 - ParameterizedJobMixIn.build_with_parameters=파라미터와 함께 빌드 ParameterizedJobMixIn.build_now=지금 빌드 BlockedBecauseOfBuildInProgress.shortDescription=빌드 #{0} 가 이미 진행중입니다{1} diff --git a/core/src/main/resources/jenkins/model/Messages_pt_BR.properties b/core/src/main/resources/jenkins/model/Messages_pt_BR.properties index 94cdc13fb189..5813f57d7b97 100644 --- a/core/src/main/resources/jenkins/model/Messages_pt_BR.properties +++ b/core/src/main/resources/jenkins/model/Messages_pt_BR.properties @@ -62,7 +62,6 @@ Mailer.NotHttp.Error=A URL é inválida, por favor se certifique que você está válido. Hudson.Computer.IncorrectNumberOfExecutors=Campo "Número de executores" incorreto. Ele precisa ser um número não \ negativo. -NewViewLink.NewView=Nova visão EnforceSlaveAgentPortAdministrativeMonitor.displayName=Impor porta TCP para o agente BuiltInNodeMigration.DisplayName=Nome do nó embutido e migração de rótulo SimpleGlobalBuildDiscarderStrategy.displayName=Descartador de construção específico diff --git a/core/src/main/resources/jenkins/model/Messages_ru.properties b/core/src/main/resources/jenkins/model/Messages_ru.properties index da2b98ea3eac..ffe1e499f1ce 100644 --- a/core/src/main/resources/jenkins/model/Messages_ru.properties +++ b/core/src/main/resources/jenkins/model/Messages_ru.properties @@ -55,4 +55,3 @@ PatternProjectNamingStrategy.NamePatternInvalidSyntax=Некорректное ParameterizedJobMixIn.build_with_parameters=Собрать с параметрами CLI.disable-job.shortDescription=Запретить задачу CLI.enable-job.shortDescription=Разрешить задачу -NewViewLink.NewView=Новое представление \ No newline at end of file diff --git a/core/src/main/resources/jenkins/model/Messages_tr.properties b/core/src/main/resources/jenkins/model/Messages_tr.properties index e4634eb73d00..f3108bf971ec 100644 --- a/core/src/main/resources/jenkins/model/Messages_tr.properties +++ b/core/src/main/resources/jenkins/model/Messages_tr.properties @@ -30,7 +30,6 @@ Hudson.NoJavaInPath=java, PATH içerisinde değil. JDK Hudson.NoName=İsim belirtilmedi Hudson.UnsafeChar=''{0}'' güvenli olmayan bir karakter Hudson.ViewName=Hepsi -NewViewLink.NewView=Yeni Görünüm ParameterizedJobMixIn.build_now=Şimdi Yapılandır BlockedBecauseOfBuildInProgress.shortDescription=Yapılandırma #{0} zaten işlemde {1} BlockedBecauseOfBuildInProgress.ETA=\ (ETA: {0}) diff --git a/core/src/main/resources/jenkins/model/Messages_zh_TW.properties b/core/src/main/resources/jenkins/model/Messages_zh_TW.properties index b3a9630243dd..897c1d17f266 100644 --- a/core/src/main/resources/jenkins/model/Messages_zh_TW.properties +++ b/core/src/main/resources/jenkins/model/Messages_zh_TW.properties @@ -55,8 +55,6 @@ JenkinsLocationConfiguration.does_not_look_like_an_email_address=這看起來不 Mailer.Localhost.Error=請設定有效的主機名稱,不要用 localhost Mailer.NotHttp.Error=URL 無效,請確保您使用 http\:// 或 https\:// 和有效的網域名稱。 -NewViewLink.NewView=新增視景 - PatternProjectNamingStrategy.DisplayName=樣式 PatternProjectNamingStrategy.NamePatternRequired=一定要輸入名稱樣式 PatternProjectNamingStrategy.NamePatternInvalidSyntax=正規表示式語法錯誤。 diff --git a/core/src/main/resources/lib/layout/tabNewItem.jelly b/core/src/main/resources/lib/layout/tabNewItem.jelly index f0d98237cfb3..a8ead8d8f4a4 100644 --- a/core/src/main/resources/lib/layout/tabNewItem.jelly +++ b/core/src/main/resources/lib/layout/tabNewItem.jelly @@ -10,7 +10,7 @@ diff --git a/core/src/test/java/jenkins/model/NewViewLinkTest.java b/core/src/test/java/jenkins/model/NewViewLinkTest.java deleted file mode 100644 index c33baaf0c31c..000000000000 --- a/core/src/test/java/jenkins/model/NewViewLinkTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package jenkins.model; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasSize; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import hudson.model.Action; -import hudson.model.ModifiableViewGroup; -import hudson.model.View; -import hudson.model.ViewGroup; -import java.util.List; -import org.junit.Before; -import org.junit.Test; - -public class NewViewLinkTest { - - private NewViewLink newViewLink; - - private View view = mock(View.class); - private ViewGroup group = mock(ModifiableViewGroup.class); - private final String viewGroupURL = "abc/"; - - @Before - public void initTests() { - when(view.getOwner()).thenReturn(group); - when(group.getUrl()).thenReturn(viewGroupURL); - - newViewLink = new NewViewLink(); - } - - @Test - public void getActionsHasPermission() { - when(group.hasPermission(any())).thenReturn(true); - - final List actions = newViewLink.createFor(view); - - assertEquals(1, actions.size()); - final Action action = actions.get(0); - assertEquals(Messages.NewViewLink_NewView(), action.getDisplayName()); - assertEquals(NewViewLink.ICON_FILE_NAME, action.getIconFileName()); - assertEquals("/" + viewGroupURL + NewViewLink.URL_NAME, action.getUrlName()); - } - - @Test - public void getActionsNoPermission() { - when(group.hasPermission(any())).thenReturn(false); - - final List actions = newViewLink.createFor(view); - - assertEquals(1, actions.size()); - final Action action = actions.get(0); - assertNull(action.getIconFileName()); - assertEquals("/" + viewGroupURL + NewViewLink.URL_NAME, action.getUrlName()); - } - - @Test - public void getActionsNotModifiableOwner() { - ViewGroup vg = mock(ViewGroup.class); - when(view.getOwner()).thenReturn(vg); - when(vg.hasPermission(any())).thenReturn(true); - - final List actions = newViewLink.createFor(view); - assertThat(actions, hasSize(0)); - } - -} diff --git a/test/src/test/java/jenkins/model/JenkinsLocationConfigurationTest.java b/test/src/test/java/jenkins/model/JenkinsLocationConfigurationTest.java index e18b02b74e9a..5392f4461eab 100644 --- a/test/src/test/java/jenkins/model/JenkinsLocationConfigurationTest.java +++ b/test/src/test/java/jenkins/model/JenkinsLocationConfigurationTest.java @@ -127,8 +127,9 @@ public void doNotAcceptNonHttpBasedRootURL_fromConfigXml() { @Test @Issue("SECURITY-1471") - public void cannotInjectJavaScriptUsingRootUrl_inNewViewLinkAction() throws Exception { + public void cannotInjectJavaScriptUsingRootUrl_inNewViewLink() throws Exception { JenkinsRule.WebClient wc = j.createWebClient(); + j.createFreeStyleProject(); settingRootURL("javascript:alert(123);//"); From 536ee99c52f9e7ac3afaa65f4a5ac2f6f7302746 Mon Sep 17 00:00:00 2001 From: Jan Faracik <43062514+janfaracik@users.noreply.github.com> Date: Mon, 18 Jul 2022 12:40:49 +0100 Subject: [PATCH 09/20] [JENKINS-68986] Remove link element from breadcrumb items without href (#6837) Co-authored-by: Daniel Beck <1831569+daniel-beck@users.noreply.github.com> --- .../resources/lib/layout/breadcrumb.jelly | 21 ++++++++++++---- .../resources/lib/layout/breadcrumbBar.jelly | 17 ++++--------- war/src/main/less/modules/breadcrumbs.less | 24 ++++++++++--------- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/core/src/main/resources/lib/layout/breadcrumb.jelly b/core/src/main/resources/lib/layout/breadcrumb.jelly index 56117a62d83d..69c451477aaf 100644 --- a/core/src/main/resources/lib/layout/breadcrumb.jelly +++ b/core/src/main/resources/lib/layout/breadcrumb.jelly @@ -26,6 +26,9 @@ THE SOFTWARE. Used inside <l:layout> to render additional breadcrumb items. + + Display name of the breadcrumb. + URL that the breadcrumb item links to. Can be omitted. @@ -33,16 +36,24 @@ THE SOFTWARE. If specified, this ID will be assigned to the LI element. This is useful for programmatically adding the context menu - - Display name of the breadcrumb. + + If true, this breadcrumb item will include a '⌄' symbol to display a dropdown menu with items + from the '{breadcrumb.href}/contextMenu' path. Since TODO
  • - - ${attrs.title} - + + + ${attrs.title} + + + + ${attrs.title} + + +
  • diff --git a/core/src/main/resources/lib/layout/breadcrumbBar.jelly b/core/src/main/resources/lib/layout/breadcrumbBar.jelly index 770e4ee15beb..cb2047167653 100644 --- a/core/src/main/resources/lib/layout/breadcrumbBar.jelly +++ b/core/src/main/resources/lib/layout/breadcrumbBar.jelly @@ -42,19 +42,10 @@ THE SOFTWARE.