github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/cmd/deck/static/plugin-help/plugin-help.ts (about)

     1  import "dialog-polyfill";
     2  import {Command, Help, PluginHelp} from "../api/help";
     3  
     4  declare const dialogPolyfill: {
     5    registerDialog(element: HTMLDialogElement): void;
     6  };
     7  
     8  declare const allHelp: Help;
     9  
    10  function getParameterByName(name: string): string | null {  // http://stackoverflow.com/a/5158301/3694
    11      const match = new RegExp('[?&]' + name + '=([^&/]*)').exec(window.location.search);
    12      return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
    13  }
    14  
    15  function redrawOptions(): void {
    16      const rs = allHelp.AllRepos.sort();
    17      const sel = document.getElementById("repo") as HTMLSelectElement;
    18      while (sel.length > 1) {
    19          sel.removeChild(sel.lastChild!);
    20      }
    21      const param = getParameterByName("repo");
    22      rs.forEach((opt) => {
    23          const o = document.createElement("option");
    24          o.text = opt;
    25          o.selected = !!(param && opt === param);
    26          sel.appendChild(o);
    27      });
    28  }
    29  
    30  window.onload = function (): void {
    31      // set dropdown based on options from query string
    32      redrawOptions();
    33      redraw();
    34      // Register dialog
    35      const dialog = document.querySelector('dialog') as HTMLDialogElement;
    36      dialogPolyfill.registerDialog(dialog);
    37      dialog.querySelector('.close')!.addEventListener('click', () => {
    38          dialog.close();
    39      });
    40      // Set up dropdown
    41      document.querySelector<HTMLSelectElement>('#repo')!.onchange = redraw;
    42  };
    43  
    44  function selectionText(sel: HTMLSelectElement): string {
    45      return sel.selectedIndex === 0 ? "" : sel.options[sel.selectedIndex].text;
    46  }
    47  
    48  /**
    49   * Returns a section to the content of the dialog
    50   * @param title title of the section
    51   * @param body body of the section
    52   */
    53  function addDialogSection(title: string, body: string | HTMLElement[]): HTMLElement {
    54      const container = document.createElement("div");
    55      const sectionTitle = document.createElement("h5");
    56      const sectionBody = document.createElement("div");
    57  
    58      sectionBody.classList.add("dialog-section-body");
    59      if (Array.isArray(body)) {
    60          body.forEach(el => {
    61             sectionBody.appendChild(el);
    62          });
    63      } else {
    64          sectionBody.innerHTML = body;
    65      }
    66  
    67      sectionTitle.classList.add("dialog-section-title");
    68      sectionTitle.innerHTML = title;
    69  
    70      container.classList.add("dialog-section");
    71      container.appendChild(sectionTitle);
    72      container.appendChild(sectionBody);
    73  
    74      return container;
    75  }
    76  
    77  /**
    78   * Return a list of link elements that links to commands.
    79   * @param commands list of commands
    80   */
    81  function getLinkableCommands(commands: Command[]): HTMLAnchorElement[] {
    82      const result: HTMLAnchorElement[] = [];
    83      commands.forEach(command => {
    84         const commandName = extractCommandName(command.Examples[0]);
    85         const link = document.createElement("a");
    86         link.href = "/command-help#" + commandName;
    87         link.innerHTML = command.Examples[0];
    88         link.classList.add("plugin-help-command-link");
    89         result.push(link);
    90      });
    91      return result;
    92  }
    93  
    94  /**
    95   * Create a card for a plugin.
    96   * @param repo repo name
    97   * @param name name of the plugin
    98   * @param pluginObj plugin object
    99   * @return the card element that contains the plugin
   100   */
   101  function createPlugin(repo: string, name: string, pluginObj: {isExternal: boolean, plugin: PluginHelp}): HTMLElement {
   102      const isExternal = pluginObj.isExternal;
   103      const plugin = pluginObj.plugin;
   104  
   105      const title = document.createElement("h3");
   106      title.innerHTML = name;
   107      title.classList.add("mdl-card__title-text");
   108      const supportTitle = document.createElement("div");
   109      supportTitle.innerHTML = isExternal ? " external plugin" : "";
   110      supportTitle.classList.add("mdl-card__subtitle-text");
   111      const cardTitle = document.createElement("div");
   112      cardTitle.classList.add("mdl-card__title");
   113      cardTitle.appendChild(title);
   114      cardTitle.appendChild(supportTitle);
   115  
   116      const cardDesc = document.createElement("div");
   117      cardDesc.innerHTML = getFirstSentence(plugin.Description);
   118      cardDesc.classList.add("mdl-card__supporting-text");
   119  
   120      const cardAction = document.createElement("div");
   121      const actionButton = document.createElement("a");
   122      actionButton.innerHTML = "Details";
   123      actionButton.classList.add(...["mdl-button", "mdl-button--colored", "mdl-js-button", "mdl-js-ripple-effect"]);
   124      actionButton.addEventListener("click", () => {
   125          const dialog = document.querySelector("dialog") as HTMLDialogElement;
   126          const title = dialog.querySelector(".mdl-dialog__title")!;
   127          const content = dialog.querySelector(".mdl-dialog__content")!;
   128  
   129          while (content.firstChild) {
   130              content.removeChild(content.firstChild);
   131          }
   132  
   133          title.innerHTML = name;
   134          if (plugin.Description) {
   135              content.appendChild(addDialogSection("Description", plugin.Description));
   136          }
   137          if (plugin.Events) {
   138              const sectionContent = "[" + plugin.Events.sort().join(", ") + "]";
   139              content.appendChild(addDialogSection("Events handled", sectionContent));
   140          }
   141          if (plugin.Config) {
   142              let sectionContent = plugin.Config ? plugin.Config[repo] : "";
   143              let sectionTitle =
   144                  repo === "" ? "Configuration(global)" : "Configuration(" + repo + ")";
   145              if (sectionContent && sectionContent !== "") {
   146                  content.appendChild(addDialogSection(sectionTitle, sectionContent));
   147              }
   148          }
   149          if (plugin.Commands) {
   150              let sectionContent = getLinkableCommands(plugin.Commands);
   151              content.appendChild(addDialogSection("Commands", sectionContent));
   152          }
   153          dialog.showModal();
   154      });
   155      cardAction.appendChild(actionButton);
   156      cardAction.classList.add("mdl-card__actions", "mdl-card--border");
   157  
   158      const card = document.createElement("div");
   159      card.appendChild(cardTitle);
   160      card.appendChild(cardDesc);
   161      card.appendChild(cardAction);
   162  
   163      card.classList.add("plugin-help-card", "mdl-card", "mdl-shadow--2dp");
   164      if (isDeprecated(plugin.Description)) {
   165          card.classList.add("deprecated");
   166      }
   167      return card;
   168  }
   169  
   170  /**
   171   * Takes an org/repo string and a repo to plugin map and returns the plugins
   172   * that apply to the repo.
   173   * @param repoSel repo name
   174   * @param repoPlugins maps plugin name to plugin
   175   */
   176  function applicablePlugins(repoSel: string, repoPlugins: {[key: string]: string[]}): string[] {
   177      if (repoSel === "") {
   178          const all = repoPlugins[""];
   179          if (all) {
   180              return all.sort();
   181          }
   182          return [];
   183      }
   184      const parts = repoSel.split("/");
   185      const byOrg = repoPlugins[parts[0]];
   186      let plugins: string[] = [];
   187      if (byOrg && byOrg !== []) {
   188          plugins = plugins.concat(byOrg);
   189      }
   190      const pluginNames = repoPlugins[repoSel];
   191      if (pluginNames) {
   192          pluginNames.forEach((pluginName) => {
   193              if (!plugins.includes(pluginName)) {
   194                  plugins.push(pluginName);
   195              }
   196          });
   197      }
   198      return plugins.sort();
   199  }
   200  
   201  /**
   202   * Redraw plugin cards.
   203   * @param {string} repo repo name.
   204   * @param {Map<string, Object>} helpMap maps a plugin name to a plugin.
   205   */
   206  function redrawPlugin(repo: string, helpMap: Map<string, {isExternal: boolean, plugin: PluginHelp}>): void {
   207      const container = document.querySelector("#plugin-container")!;
   208      while (container.childElementCount !== 0) {
   209          container.removeChild(container.firstChild!);
   210      }
   211      const names = helpMap.keys();
   212      const nameArray = Array.from(names).sort();
   213      nameArray.forEach(name => {
   214          container.appendChild(createPlugin(repo, name, helpMap.get(name)!))
   215      });
   216  }
   217  
   218  /**
   219   * Redraws the content of the page.
   220   */
   221  function redraw(): void {
   222      const repoSel = selectionText(document.getElementById("repo") as HTMLSelectElement);
   223      if (window.history && window.history.replaceState !== undefined) {
   224          if (repoSel !== "") {
   225              history.replaceState(null, "", "/plugins?repo="
   226                  + encodeURIComponent(repoSel));
   227          } else {
   228              history.replaceState(null, "", "/plugins")
   229          }
   230      }
   231      redrawOptions();
   232  
   233      const plugins: Map<string, {isExternal: boolean, plugin: PluginHelp}> = new Map();
   234      applicablePlugins(repoSel, allHelp.RepoPlugins)
   235          .forEach((name) => {
   236              if (allHelp.PluginHelp[name]) {
   237                  plugins.set(
   238                      name,
   239                      {
   240                          isExternal: false,
   241                          plugin: allHelp.PluginHelp[name]
   242                      });
   243              }
   244          });
   245      applicablePlugins(repoSel, allHelp.RepoExternalPlugins)
   246          .forEach((name) => {
   247              if (allHelp.ExternalPluginHelp[name]) {
   248                  plugins.set(
   249                      name,
   250                      {
   251                          isExternal: true,
   252                          plugin: allHelp.ExternalPluginHelp[name]
   253                      });
   254              }
   255          });
   256      redrawPlugin(repoSel, plugins);
   257  }
   258  
   259  /**
   260   * Returns first sentence from plugin's example.
   261   */
   262  function getFirstSentence(text: string): string {
   263      const fullStop = text.indexOf(".");
   264      return fullStop === -1 ? text : text.slice(0, fullStop + 1);
   265  }
   266  
   267  /**
   268   * Returns true if the plugin is deprecated.
   269   */
   270  function isDeprecated(text: string): boolean {
   271      const dictionary = ["deprecated!"];
   272      text = text.toLowerCase();
   273      for (let i = 0; i < dictionary.length; i++) {
   274          if (text.indexOf(dictionary[i]) !== -1) {
   275              return true;
   276          }
   277      }
   278      return false;
   279  }
   280  
   281  /**
   282   * Extracts a command name from a command example. It takes the first example,
   283   * with out the slash, as the name for the command. Also, any '-' character is
   284   * replaced by '_' to make the name valid in the address.
   285   */
   286  function extractCommandName(commandExample: string): string {
   287      const command = commandExample.split(" ");
   288      if (!command || command.length === 0) {
   289          throw new Error("Cannot extract command name.");
   290      }
   291      return command[0].slice(1).split("-").join("_");
   292  }