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

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