github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/cmd/deck/static/tide/tide.ts (about) 1 import {PullRequest, TideData, TidePool} from '../api/tide'; 2 import {tooltip} from '../common/common'; 3 4 declare const tideData: TideData; 5 6 window.onload = function(): void { 7 const infoDiv = document.getElementById("info-div")!; 8 const infoH4 = infoDiv.getElementsByTagName("h4")[0]!; 9 infoH4.addEventListener("click", infoToggle(infoDiv.getElementsByTagName("span")[0]), true); 10 11 redraw(); 12 }; 13 14 function infoToggle(toToggle: HTMLElement): (event: Event) => void { 15 return function(event): void { 16 if (toToggle.className == "hidden") { 17 toToggle.className = ""; 18 (event.target as HTMLElement).textContent = "Merge Requirements: (click to collapse)"; 19 } else { 20 toToggle.className = "hidden"; 21 (event.target as HTMLElement).textContent = "Merge Requirements: (click to expand)"; 22 } 23 } 24 } 25 26 function redraw(): void { 27 redrawQueries(); 28 redrawPools(); 29 } 30 31 function createLink(href: string, text: string): HTMLAnchorElement { 32 const a = document.createElement("a"); 33 a.href = href; 34 a.appendChild(document.createTextNode(text)); 35 return a; 36 } 37 38 /** 39 * escapeLabel escaped label name that returns a valid name used for css 40 * selector. 41 */ 42 function escapeLabel(label: string): string { 43 if (label === "") return ""; 44 const toUnicode = function(index: number): string { 45 const h = label.charCodeAt(index).toString(16).split(''); 46 while (h.length < 6) h.splice(0, 0, '0'); 47 48 return 'x' + h.join(''); 49 }; 50 let result = ""; 51 const alphaNum = /^[0-9a-zA-Z]+$/; 52 53 for (let i = 0; i < label.length; i++) { 54 const c = label.charCodeAt(i); 55 if ((i === 0 && c > 47 && c < 58) || !label[i].match(alphaNum)) { 56 result += toUnicode(i); 57 continue; 58 } 59 result += label[i]; 60 } 61 62 return result 63 } 64 65 /** 66 * Creates a HTML element for the label given its name 67 */ 68 function createLabelEl(label: string): HTMLElement { 69 const el = document.createElement("span"); 70 const escapedName = escapeLabel(label); 71 el.classList.add("mdl-shadow--2dp", "label", escapedName); 72 el.textContent = label; 73 74 return el; 75 } 76 77 function createStrong(text: string): HTMLElement { 78 const s = document.createElement("strong"); 79 s.appendChild(document.createTextNode(text)); 80 return s; 81 } 82 83 function fillDetail(data: string | string[] | undefined, type: string, connector: string, container: HTMLElement, styleData: (content: string) => Node) { 84 if (!data || (Array.isArray(data) && data.length === 0)) { 85 return; 86 } 87 container.appendChild(createStrong(connector)); 88 container.appendChild(document.createTextNode(`the following ${type}: `)); 89 container.appendChild(document.createElement("br")); 90 91 const ul = document.createElement("ul"); 92 const li = document.createElement("li"); 93 ul.appendChild(li); 94 container.appendChild(ul); 95 96 if (typeof data === 'string') { 97 li.appendChild(document.createTextNode(data)); 98 } else if (Array.isArray(data)) { 99 for (let i = 0; i < data.length; i++) { 100 const v = data[i]; 101 li.appendChild(styleData(v)); 102 if (i + 1 < data.length) { 103 li.appendChild(document.createTextNode(" ")); 104 } 105 } 106 } 107 } 108 109 function redrawQueries(): void { 110 const queries = document.getElementById("queries")!; 111 while (queries.firstChild) 112 queries.removeChild(queries.firstChild); 113 114 if (!tideData.Queries) { 115 return; 116 } 117 for (let i = 0; i < tideData.Queries.length; i++) { 118 const query = tideData.Queries[i]; 119 const tideQuery = tideData.TideQueries[i]; 120 121 // create list entry for the query, all details will be within this element 122 const li = document.createElement("li"); 123 124 // GitHub query search link 125 const a = createLink( 126 "https://github.com/search?utf8=" + encodeURIComponent("\u2713") + "&q=" + encodeURIComponent(query), 127 "GitHub Search Link" 128 ); 129 li.appendChild(a); 130 li.appendChild(document.createTextNode(" - Meaning: Is an open Pull Request")); 131 132 // build the description 133 // all queries should implicitly mean this 134 // add the list of repos, defaulting to an empty array if no repos have been provided. 135 const orgs = tideQuery["orgs"] || []; 136 const repos = tideQuery["repos"] || []; 137 const excludedRepos = tideQuery["excludedRepos"] || []; 138 if (orgs.length > 0) { 139 li.appendChild(document.createTextNode(" in one of the following orgs: ")); 140 const ul = document.createElement("ul"); 141 const innerLi = document.createElement("li"); 142 for (let i = 0; i < orgs.length; i++) { 143 innerLi.appendChild(createLink("https://github.com/" + orgs[i], orgs[i])); 144 if (i + 1 < repos.length) { 145 innerLi.appendChild(document.createTextNode(", ")); 146 } 147 } 148 ul.appendChild(innerLi); 149 li.appendChild(ul); 150 } 151 if (repos.length > 0) { 152 let reposText = " in one of the following repos: "; 153 if (orgs.length > 0) { 154 reposText = " or " + reposText; 155 } 156 li.appendChild(document.createTextNode(reposText)); 157 const ul = document.createElement("ul"); 158 const innerLi = document.createElement("li"); 159 for (let j = 0; j < repos.length; j++) { 160 innerLi.appendChild(createLink("https://github.com/" + repos[j], repos[j])); 161 if (j + 1 < repos.length) { 162 innerLi.appendChild(document.createTextNode(", ")); 163 } 164 } 165 ul.appendChild(innerLi); 166 li.appendChild(ul); 167 } 168 if (excludedRepos.length > 0) { 169 li.appendChild(document.createTextNode(" but NOT in any of the following excluded repos: ")); 170 const ul = document.createElement("ul"); 171 const innerLi = document.createElement("li"); 172 for (let j = 0; j < excludedRepos.length; j++) { 173 innerLi.appendChild(createLink("https://github.com/" + excludedRepos[j], excludedRepos[j])); 174 if (j + 1 < excludedRepos.length) { 175 innerLi.appendChild(document.createTextNode(", ")); 176 } 177 } 178 ul.appendChild(innerLi); 179 li.appendChild(ul); 180 } 181 // required labels 182 fillDetail(tideQuery.labels, "labels", "with ", li, function(data) { 183 return createLabelEl(data); 184 }); 185 // required to be not present labels 186 fillDetail(tideQuery.missingLabels, "labels", "without ", li, function(data) { 187 return createLabelEl(data); 188 }); 189 // list milestone if existed 190 fillDetail(tideQuery.milestone, "milestone", "with ", li, function(data) { 191 return document.createTextNode(data); 192 }); 193 // list all excluded branches 194 fillDetail(tideQuery.excludedBranches, "branches", "exclude ", li, function(data) { 195 return document.createTextNode(data); 196 }); 197 // list all included branches 198 fillDetail(tideQuery.includedBranches, "branches", "targeting ", li, function(data) { 199 return document.createTextNode(data); 200 }); 201 // GitHub native review required 202 const reviewApprovedRequired = tideQuery.hasOwnProperty("reviewApprovedRequired") && tideQuery["reviewApprovedRequired"]; 203 if (reviewApprovedRequired) { 204 li.appendChild(document.createTextNode("and must be ")); 205 li.appendChild(createLink( 206 "https://help.github.com/articles/about-pull-request-reviews/", 207 "approved by GitHub review" 208 )); 209 } 210 211 // actually add the entry 212 queries.appendChild(li); 213 } 214 } 215 216 function redrawPools(): void { 217 const pools = document.getElementById("pools")!.getElementsByTagName("tbody")[0]; 218 while (pools.firstChild) 219 pools.removeChild(pools.firstChild); 220 221 if (!tideData.Pools) { 222 return; 223 } 224 for (let i = 0; i < tideData.Pools.length; i++) { 225 const pool = tideData.Pools[i]; 226 const r = document.createElement("tr"); 227 228 229 const deckLink = "/?repo="+pool.Org+"%2F"+pool.Repo; 230 const branchLink = "https://github.com/" + pool.Org + "/" + pool.Repo + "/tree/" + pool.Branch; 231 const linksTD = document.createElement("td"); 232 linksTD.appendChild(createLink(deckLink, pool.Org + "/" + pool.Repo)); 233 linksTD.appendChild(document.createTextNode(" ")); 234 linksTD.appendChild(createLink(branchLink, pool.Branch)); 235 r.appendChild(linksTD); 236 r.appendChild(createActionCell(pool)); 237 r.appendChild(createBatchCell(pool)); 238 r.appendChild(createPRCell(pool, pool.SuccessPRs)); 239 r.appendChild(createPRCell(pool, pool.PendingPRs)); 240 r.appendChild(createPRCell(pool, pool.MissingPRs)); 241 242 pools.appendChild(r); 243 } 244 } 245 246 function createActionCell(pool: TidePool): HTMLTableDataCellElement { 247 const targeted = pool.Target && pool.Target.length; 248 const blocked = pool.Blockers && pool.Blockers.length; 249 let action = pool.Action.replace("_", " "); 250 if (targeted || blocked) { 251 action += ": " 252 } 253 const c = document.createElement("td"); 254 c.appendChild(document.createTextNode(action)); 255 256 if (blocked) { 257 c.classList.add("blocked"); 258 addBlockersToElem(c, pool) 259 } else if (targeted) { 260 addPRsToElem(c, pool, pool.Target) 261 } 262 return c; 263 } 264 265 function createPRCell(pool: TidePool, prs: PullRequest[]): HTMLTableDataCellElement { 266 const c = document.createElement("td"); 267 addPRsToElem(c, pool, prs); 268 return c; 269 } 270 271 function createBatchCell(pool: TidePool): HTMLTableDataCellElement { 272 const td = document.createElement('td'); 273 if (pool.BatchPending) { 274 const numbers = pool.BatchPending.map(p => String(p.Number)); 275 const batchRef = pool.Branch + ',' + numbers.join(','); 276 const href = '/?repo=' + encodeURIComponent(pool.Org + '/' + pool.Repo) + 277 '&type=batch&pull=' + encodeURIComponent(batchRef); 278 const link = document.createElement('a'); 279 link.href = href; 280 for (let i = 0; i < pool.BatchPending.length; i++) { 281 const pr = pool.BatchPending[i]; 282 const text = document.createElement('span'); 283 text.appendChild(document.createTextNode("#" + String(pr.Number))); 284 text.id = "pr-" + pool.Org + "-" + pool.Repo + "-" + pr.Number + "-" + nextID(); 285 if (pr.Title) { 286 const tip = tooltip.forElem(text.id, document.createTextNode(pr.Title)); 287 text.appendChild(tip); 288 } 289 link.appendChild(text); 290 // Add a space after each PR number except the last. 291 if (i+1 < pool.BatchPending.length) { 292 link.appendChild(document.createTextNode(" ")); 293 } 294 } 295 td.appendChild(link); 296 } 297 return td; 298 } 299 300 // addPRsToElem adds a space separated list of PR numbers that link to the corresponding PR on github. 301 function addPRsToElem(elem: HTMLElement, pool: TidePool, prs?: PullRequest[]): void { 302 if (prs) { 303 for (let i = 0; i < prs.length; i++) { 304 const a = document.createElement("a"); 305 a.href = "https://github.com/" + pool.Org + "/" + pool.Repo + "/pull/" + prs[i].Number; 306 a.appendChild(document.createTextNode("#" + prs[i].Number)); 307 a.id = "pr-" + pool.Org + "-" + pool.Repo + "-" + prs[i].Number + "-" + nextID(); 308 if (prs[i].Title) { 309 const tip = tooltip.forElem(a.id, document.createTextNode(prs[i].Title)); 310 a.appendChild(tip); 311 } 312 elem.appendChild(a); 313 // Add a space after each PR number except the last. 314 if (i+1 < prs.length) { 315 elem.appendChild(document.createTextNode(" ")); 316 } 317 } 318 } 319 } 320 321 // addBlockersToElem adds a space separated list of Issue numbers that link to the 322 // corresponding Issues on github that are blocking merge. 323 function addBlockersToElem(elem: HTMLElement, pool: TidePool): void { 324 if (!pool.Blockers) { 325 return; 326 } 327 for (let i = 0; i < pool.Blockers.length; i++) { 328 const b = pool.Blockers[i]; 329 const a = document.createElement("a"); 330 a.href = b.URL; 331 a.appendChild(document.createTextNode("#" + b.Number)); 332 a.id = "blocker-" + pool.Org + "-" + pool.Repo + "-" + b.Number + "-" + nextID(); 333 a.appendChild(tooltip.forElem(a.id, document.createTextNode(b.Title))); 334 335 elem.appendChild(a); 336 // Add a space after each PR number except the last. 337 if (i+1 < pool.Blockers.length) { 338 elem.appendChild(document.createTextNode(" ")); 339 } 340 } 341 } 342 343 let idCounter = 0; 344 function nextID(): String { 345 idCounter++; 346 return "elemID-" + String(idCounter); 347 }