github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/cmd/deck/static/spyglass/spyglass.ts (about)

     1  import {ProwJobState} from "../api/prow";
     2  import {createAbortProwJobIcon} from "../common/abort";
     3  import {createRerunProwJobIcon} from "../common/rerun";
     4  import {getParameterByName} from "../common/urls";
     5  import {isTransitMessage, serialiseHashes} from "./common";
     6  
     7  declare const src: string;
     8  declare const lensArtifacts: {[index: string]: string[]};
     9  declare const lensIndexes: number[];
    10  declare const csrfToken: string;
    11  declare const rerunCreatesJob: boolean;
    12  declare const prowJob: string;
    13  declare const prowJobName: string;
    14  declare const prowJobState: ProwJobState;
    15  
    16  // Loads views for this job
    17  function loadLenses(): void {
    18    const hashes = parseHash();
    19    for (const lensIndex of lensIndexes) {
    20      const frame = document.querySelector<HTMLIFrameElement>(`#iframe-${lensIndex}`)!;
    21      let url = urlForLensRequest(frame.dataset.lensName!, Number(frame.dataset.lensIndex!), 'iframe');
    22      url += `&topURL=${escape(location.href.split('#')[0])}&lensIndex=${lensIndex}`;
    23      const hash = hashes[lensIndex];
    24      if (hash) {
    25        url += hash;
    26      }
    27      frame.src = url;
    28    }
    29  }
    30  
    31  function queryForLens(lens: string, index: number): string {
    32    const data = {
    33      artifacts: lensArtifacts[index],
    34      index,
    35      src,
    36    };
    37    return `req=${encodeURIComponent(JSON.stringify(data))}`;
    38  }
    39  
    40  function urlForLensRequest(lens: string, index: number, request: string): string {
    41    return `/spyglass/lens/${lens}/${request}?${queryForLens(lens, index)}`;
    42  }
    43  
    44  function frameForMessage(e: MessageEvent): HTMLIFrameElement {
    45    for (const frame of Array.from(document.querySelectorAll('iframe'))) {
    46      if (frame.contentWindow === e.source) {
    47        return frame;
    48      }
    49    }
    50    throw new Error("MessageEvent from unknown frame!?");
    51  }
    52  
    53  function updateHash(index: number, hash: string): void {
    54    const currentHash = parseHash();
    55    if (hash !== '') {
    56      currentHash[index] = hash;
    57    } else {
    58      delete currentHash[index];
    59    }
    60    location.hash = serialiseHashes(currentHash);
    61  }
    62  
    63  function parseHash(): {[index: string]: string} {
    64    const parts = location.hash.substr(1).split(';');
    65    const result: { [index: string]: string } = {};
    66    for (const part of parts) {
    67      if (part === '') {
    68        continue;
    69      }
    70      const [index, hash] = part.split(':');
    71      result[index] = `#${  unescape(hash)}`;
    72    }
    73    return result;
    74  }
    75  
    76  function getLensRequestOptions(reqBody: string): RequestInit {
    77    return {body: reqBody, method: 'POST', headers: {'X-CSRF-Token': csrfToken}, credentials: 'same-origin'};
    78  }
    79  
    80  window.addEventListener('message', async (e) => {
    81    const data = e.data;
    82    if (isTransitMessage(data)) {
    83      const {id, message} = data;
    84      const frame = frameForMessage(e);
    85      const lens = frame.dataset.lensName!;
    86      const index = Number(frame.dataset.lensIndex!);
    87  
    88      const respond = (response: string): void => {
    89        frame.contentWindow.postMessage({id, message: {type: 'response', data: response}}, '*');
    90      };
    91  
    92      switch (message.type) {
    93        case "contentUpdated":
    94          frame.style.height = `${message.height}px`;
    95          frame.style.visibility = 'visible';
    96          if (frame.dataset.hideTitle) {
    97            frame.parentElement.parentElement.classList.add('hidden-title');
    98          }
    99          document.querySelector<HTMLElement>(`#${lens}-loading`)!.style.display = 'none';
   100          respond('');
   101          break;
   102        case "request": {
   103          const req = await fetch(urlForLensRequest(lens, index, 'callback'),
   104            getLensRequestOptions(message.data));
   105          respond(await req.text());
   106          break;
   107        }
   108        case "requestPage": {
   109          const req = await fetch(urlForLensRequest(lens, index, 'rerender'),
   110            getLensRequestOptions(message.data));
   111          respond(await req.text());
   112          break;
   113        }
   114        case "updatePage": {
   115          const spinner = document.querySelector<HTMLElement>(`#${lens}-loading`)!;
   116          frame.style.visibility = 'visible';
   117          spinner.style.display = 'block';
   118          const req = await fetch(urlForLensRequest(lens, index, 'rerender'),
   119            getLensRequestOptions(message.data));
   120          respond(await req.text());
   121          break;
   122        }
   123        case "updateHash": {
   124          updateHash(index, message.hash);
   125          respond('');
   126          break;
   127        }
   128        case "showOffset": {
   129          const container = document.getElementsByTagName('main')[0]!;
   130          const containerOffset = {left: 0, top: 0};
   131          let parent: HTMLElement = frame;
   132          // figure out our cumulative offset from the root container <main> by
   133          // looping through our parents until we get to it or run out of parents.
   134          while (parent) {
   135            containerOffset.top += parent.offsetTop;
   136            containerOffset.left += parent.offsetLeft;
   137            if (parent.offsetParent instanceof HTMLElement && parent.offsetParent !== container) {
   138              parent = parent.offsetParent;
   139            } else {
   140              break;
   141            }
   142          }
   143          if (!parent) {
   144            console.error("Couldn't find parent for frame!", container, frame);
   145          }
   146          container.scrollTop = containerOffset.top + message.top;
   147          container.scrollLeft = containerOffset.left + message.left;
   148          break;
   149        }
   150        default:
   151          console.warn(`Unrecognised message type "${message.type}" from lens "${lens}":`, data);
   152          break;
   153      }
   154    }
   155  });
   156  
   157  window.addEventListener('hashchange', (e) => {
   158    const hashes = parseHash();
   159    for (const index of Object.keys(hashes)) {
   160      const iframe = document.querySelector<HTMLIFrameElement>(`#iframe-${index}`);
   161      if (!iframe || !iframe.contentWindow) {
   162        continue;
   163      }
   164      iframe.contentWindow.postMessage({type: 'hashUpdate', hash: hashes[index]}, '*');
   165    }
   166  });
   167  
   168  // We can't use DOMContentLoaded here or we end up with a bunch of flickering. This appears to be MDL's fault.
   169  window.addEventListener('load', () => {
   170    loadLenses();
   171    handleRerunButton();
   172    handleAbortButton();
   173  });
   174  
   175  function handleRerunButton() {
   176    // In case prowJob is unavailable, the rerun button shouldn't be shown
   177    if (!prowJobName) {
   178      return;
   179    }
   180  
   181    const rerunStatus = getParameterByName("rerun");
   182    const modal = document.getElementById('rerun')!;
   183    const modalContent = document.querySelector('.modal-content')!;
   184  
   185    const r = document.getElementById("header-title")!;
   186    const c = document.createElement("div");
   187    c.appendChild(createRerunProwJobIcon(modal, modalContent, prowJobName, rerunCreatesJob, csrfToken));
   188    r.appendChild(c);
   189  
   190    if (rerunStatus === "gh_redirect") {
   191      modal.style.display = "block";
   192      modalContent.innerHTML = "Rerunning that job requires GitHub login. Now that you're logged in, try again";
   193    }
   194  }
   195  
   196  function handleAbortButton(): void {
   197    // In case prowJob is unavailable, the abort button shouldn't be shown
   198    if (!prowJobName) {
   199      return;
   200    }
   201  
   202    const modal = document.getElementById('rerun')!;
   203    const modalContent = document.querySelector('.modal-content')!;
   204  
   205    const r = document.getElementById("header-title")!;
   206    const c = document.createElement("div");
   207    c.appendChild(createAbortProwJobIcon(modal, modalContent, prowJob, prowJobState, prowJobName, csrfToken));
   208    r.appendChild(c);
   209  }