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  }