Fathi Boudra | 422bf77 | 2019-12-02 11:10:16 +0200 | [diff] [blame] | 1 | //<![CDATA[ |
| 2 | // |
Leonardo Sandoval | 579c737 | 2020-10-23 15:23:32 -0500 | [diff] [blame] | 3 | // Copyright (c) 2019-2020 Arm Limited. All rights reserved. |
Fathi Boudra | 422bf77 | 2019-12-02 11:10:16 +0200 | [diff] [blame] | 4 | // |
| 5 | // SPDX-License-Identifier: BSD-3-Clause |
| 6 | // |
| 7 | // Get rid of all unhelpful and annoying orbs that Jenkins barfs to indicate sub |
| 8 | // job status. We'd have that in the HTML report anyway. Unhelpfully, Jenkins |
| 9 | // doesn't ID the element, nor does it assign a class to them. So, we: |
| 10 | // |
| 11 | // - Look for a h2 element with text "Subproject Builds" or "Subprojects"; |
| 12 | // |
| 13 | // - The orbs are placed in a <ul> immediately following the h2 element; so we |
| 14 | // remove it altogether. |
| 15 | // |
| 16 | document.querySelectorAll("h2").forEach(function(el) { |
| 17 | if ((el.innerText !== "Subproject Builds") && (el.innerText !== "Subprojects")) |
| 18 | return; |
| 19 | if (el.nextSibling.tagName !== "UL") |
| 20 | return; |
| 21 | el.nextSibling.remove(); |
| 22 | el.remove(); |
| 23 | }); |
| 24 | |
| 25 | // For failed jobs, there's this large "Identified problems" table that has no |
| 26 | // value. Get rid of that as well. |
| 27 | document.querySelectorAll("h2").forEach(function(el) { |
| 28 | if (el.innerText !== "Identified problems") |
| 29 | return; |
| 30 | el.closest("table").remove(); |
| 31 | }); |
| 32 | |
| 33 | function onResultHover(e) { |
| 34 | var title = this.getAttribute("title"); |
| 35 | var commandPre = document.querySelector("#tf-selected-commands"); |
| 36 | var localCmd = ""; |
| 37 | |
| 38 | if (!title || title === "") { |
| 39 | localCmd = "<i>No local command available!</i>"; |
| 40 | } else { |
| 41 | var titleElement = '<span style="color: red;">' + title + '</span>'; |
| 42 | |
| 43 | localCmd = "workspace=/tmp/workspace test_run=1 test_groups=" + titleElement + |
| 44 | " script/run_local_ci.sh"; |
| 45 | } |
| 46 | |
| 47 | commandPre.innerHTML = localCmd; |
| 48 | } |
| 49 | |
| 50 | // Disable re-trigger button |
| 51 | function retriggerDisable() { |
| 52 | var button = document.getElementById("tf-rebuild-button"); |
| 53 | button.setAttribute("disabled", ""); |
| 54 | } |
| 55 | |
| 56 | var checkedCount = 0; |
| 57 | |
| 58 | // Enable or disable retrigger button according to its count attribute |
| 59 | function retriggerEffectCount() { |
| 60 | var button = document.getElementById("tf-rebuild-button"); |
| 61 | |
| 62 | if (checkedCount === 0) |
| 63 | button.setAttribute("disabled", ""); |
| 64 | else |
| 65 | button.removeAttribute("disabled"); |
| 66 | } |
| 67 | |
| 68 | function resultCheckboxes() { |
| 69 | return document.querySelectorAll("#tf-report-main input[type=checkbox]"); |
| 70 | } |
| 71 | |
| 72 | function computeCheckCount() { |
| 73 | checkedCount = 0; |
| 74 | |
| 75 | resultCheckboxes().forEach(function(el) { |
| 76 | if (el.checked) |
| 77 | checkedCount++; |
| 78 | }); |
| 79 | |
| 80 | retriggerEffectCount(); |
| 81 | } |
| 82 | |
| 83 | function onConfigChange(e) { |
| 84 | var button = document.getElementById("tf-rebuild-button"); |
| 85 | |
| 86 | computeCheckCount(); |
| 87 | |
| 88 | // Collapse the re-build frame upon changing config selection |
| 89 | document.getElementById("tf-rebuild-frame").style.display = "none"; |
| 90 | } |
| 91 | |
| 92 | var retryCount = 0; |
| 93 | |
| 94 | function retryRebuild(frame, selectedConfigs, embed) { |
| 95 | var doc = frame.contentDocument; |
| 96 | var form = doc.querySelector("form[action=configSubmit]"); |
| 97 | var errMsg = "Error re-triggering. Are you logged in?" + |
| 98 | " If this happens repeatedly, please check the browser console for errors."; |
| 99 | |
| 100 | if (!form || !form.querySelector("button")) { |
| 101 | retryCount++; |
| 102 | if (retryCount > 50) |
| 103 | alert(errMsg); |
| 104 | else |
| 105 | setTimeout(retryRebuild, 100, frame, selectedConfigs, embed); |
| 106 | return; |
| 107 | } |
| 108 | |
| 109 | try { |
| 110 | var groups = form.querySelector("input[value=TEST_GROUPS]"); |
| 111 | groups = groups.nextElementSibling; |
| 112 | |
| 113 | // Set groups only if there were selections, or leave unchanged. |
| 114 | if (selectedConfigs) |
| 115 | groups.value = selectedConfigs.join(" "); |
| 116 | |
| 117 | // Clear the parameters derived from clone_repos.sh that had been passed |
| 118 | // over to the present job, which have now become stale. They are no more |
| 119 | // valid for a re-trigger, and have to be freshly set. |
| 120 | const paramsToClear = ["CI_SCRATCH"]; |
| 121 | paramsToClear.forEach(function(item) { |
| 122 | var el = form.querySelector("input[value=" + item + "]"); |
| 123 | if (!el) |
| 124 | return; |
| 125 | |
| 126 | // The value for this parameter is the next sibling, with name=value |
| 127 | // property attached. |
| 128 | el = el.nextElementSibling; |
| 129 | if (el.getAttribute("name") != "value") |
| 130 | throw "Unable to clear parameter '" + item + "'"; |
| 131 | |
| 132 | // Clear the parameter's value |
| 133 | el.value = ""; |
| 134 | }); |
| 135 | |
| 136 | if (embed) { |
| 137 | // Leave only the parameter form |
| 138 | try { |
| 139 | doc.querySelector("#side-panel").remove(); |
| 140 | doc.querySelector("#page-head").remove(); |
| 141 | doc.querySelector("footer").remove(); |
| 142 | |
| 143 | var mainPanel = doc.querySelector("#main-panel"); |
| 144 | mainPanel.style.marginLeft = "0px"; |
| 145 | mainPanel.style.padding = "10px"; |
| 146 | |
| 147 | doc.body.style.padding = "0px"; |
| 148 | } catch (e) { |
| 149 | } |
| 150 | |
| 151 | // Have the frame disappear after clicking, and remove event listener |
| 152 | var closer = form.querySelector("button").addEventListener("click", function(e) { |
| 153 | setTimeout(function() { |
| 154 | frame.style.display = "none"; |
| 155 | |
| 156 | // We had disabled the retrigger button when we opened the frame. Now |
| 157 | // that we're closing the frame, leave the button in the appropriate |
| 158 | // state. |
| 159 | retriggerEffectCount(); |
| 160 | |
| 161 | e.target.removeEventListener(e.type, closer); |
| 162 | alert("Build re-triggered for selected configurations."); |
| 163 | }); |
| 164 | }); |
| 165 | |
| 166 | frame.style.height = "700px"; |
| 167 | frame.style.width = "100%"; |
| 168 | frame.style.display = "block"; |
| 169 | |
| 170 | // Disable re-trigger until this frame is closed |
| 171 | retriggerDisable(); |
| 172 | |
| 173 | window.scrollTo(0, frame.getBoundingClientRect().top); |
| 174 | } else { |
| 175 | // Trigger rebuild |
| 176 | form.querySelector("button").click(); |
| 177 | if (selectedConfigs) |
| 178 | alert("Build re-triggered for selected configurations."); |
| 179 | else |
| 180 | alert("Job re-triggered."); |
| 181 | } |
| 182 | } catch (e) { |
| 183 | alert("Error triggering job: " + e); |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | function onRebuild(e) { |
| 188 | var selectedConfigs = []; |
| 189 | var parent; |
| 190 | var embed; |
| 191 | var configs; |
| 192 | |
| 193 | var loc = window.location.href.replace(/\/*$/, "").split("/"); |
| 194 | var buildNo = loc[loc.length - 1]; |
| 195 | if (!parseInt(buildNo)) { |
| 196 | alert("Please visit the page of a specifc build, and try again."); |
| 197 | return; |
| 198 | } |
| 199 | |
| 200 | resultCheckboxes().forEach(function(el) { |
| 201 | if (el.checked === true) { |
| 202 | parent = el.closest("td"); |
| 203 | selectedConfigs.push(parent.getAttribute("title")); |
| 204 | } |
| 205 | }); |
| 206 | |
| 207 | loc.push("rebuild"); |
| 208 | loc.push("parameterized"); |
| 209 | |
| 210 | // If shift key was pressed when clicking, just open a retrigger window |
| 211 | retryCount = 0; |
| 212 | if (e.shiftKey) |
| 213 | embed = true; |
| 214 | |
| 215 | var frame = document.getElementById("tf-rebuild-frame"); |
| 216 | frame.style.display = "none"; |
| 217 | frame.src = loc.join("/"); |
| 218 | |
| 219 | configs = (e.target.id === "tf-rebuild-button")? selectedConfigs: null; |
| 220 | setTimeout(retryRebuild, 250, frame, configs, embed); |
| 221 | } |
| 222 | |
| 223 | function onSelectAll(e) { |
| 224 | var selectClass = e.target.innerHTML.toLowerCase(); |
| 225 | |
| 226 | if (selectClass === "none") { |
| 227 | resultCheckboxes().forEach(function(checkbox) { |
| 228 | checkbox.checked = false; |
| 229 | }); |
| 230 | } else { |
| 231 | document.querySelectorAll("." + selectClass).forEach(function(result) { |
| 232 | var input = result.querySelector("input"); |
| 233 | if (input) |
| 234 | input.checked = true; |
| 235 | }); |
| 236 | } |
| 237 | |
| 238 | computeCheckCount(); |
| 239 | } |
| 240 | |
| 241 | function init() { |
| 242 | // The whole of Jenkins job result page is rendered in an HTML table. This |
| 243 | // means that anything that alters the size of content elements will cause a |
| 244 | // disruptive page layout reflow. That's exactly what happens with local |
| 245 | // commands when job results are hovered over. To avoid jitter when result |
| 246 | // hovering, fix the width of the element to its initial value. |
| 247 | var localCommands = document.querySelector("#tf-selected-commands"); |
| 248 | localCommands.style.width = window.getComputedStyle(localCommands).width; |
| 249 | |
| 250 | // Add result hover listeners |
| 251 | [".success", ".failure", ".unstable"].map(function(sel) { |
| 252 | return "#tf-report-main " + sel; |
| 253 | }).forEach(function(sel) { |
| 254 | document.querySelectorAll(sel).forEach(function(result) { |
| 255 | result.addEventListener("mouseover", onResultHover); |
| 256 | }); |
| 257 | }); |
| 258 | |
| 259 | // Add checkbox click listeners |
| 260 | resultCheckboxes().forEach(function(el) { |
| 261 | el.addEventListener("change", onConfigChange); |
| 262 | }); |
| 263 | |
| 264 | // Add re-trigger button listener |
| 265 | document.getElementById("tf-rebuild-button").addEventListener("click", onRebuild); |
| 266 | document.getElementById("tf-rebuild-all-button").addEventListener("click", onRebuild); |
| 267 | |
| 268 | // Add listener for select all widgets |
| 269 | document.querySelectorAll(".select-all").forEach(function(widget) { |
| 270 | widget.addEventListener("click", onSelectAll); |
| 271 | }); |
| 272 | |
| 273 | computeCheckCount(); |
| 274 | } |
| 275 | |
| 276 | document.addEventListener("DOMContentLoaded", init); |
| 277 | //]]> |