whylabs/whylogs-python

View on GitHub
python/whylogs/viz/html/templates/index-hbs-cdn-all-in-jupyter-constraints-report.html

Summary

Maintainability
Test Coverage
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="" />
    <meta name="author" content="" />

    <title>Profile Visualizer | whylogs</title>

    <link rel="icon" href="images/whylabs-favicon.png" type="image/png" sizes="16x16" />
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link href="https://fonts.googleapis.com/css2?family=Asap:wght@400;500;600;700&display=swap" rel="stylesheet" />
    <link rel="preconnect" href="https://fonts.gstatic.com" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" />

    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.7/handlebars.min.js"
      integrity="sha512-RNLkV3d+aLtfcpEyFG8jRbnWHxUqVZozacROI4J2F1sTaDqo1dPQYs01OMi1t1w9Y2FdbSCDSQ2ZVdAC8bzgAg=="
      crossorigin="anonymous"
      referrerpolicy="no-referrer"
    ></script>

    <style type="text/css">

      /* Screen on smaller screens */
      .no-responsive {
        display: none;
        position: fixed;
        top: 0;
        left: 0;
        z-index: 1031;
        width: 100vw;
        height: 100vh;
        background-color: var(--tealBackground);
        display: flex;
        align-items: center;
        justify-content: center;
      }

      @media screen and (min-width: 1000px) {
        .desktop-content {
          display: block;
        }
        .no-responsive {
          display: none;
        }
      }

      .no-responsive__content {
        max-width: 600px;
        width: 100%;
        padding: 0 24px;
      }

      .no-responsive__title {
        font-size: 96px;
        font-weight: 300;
        color: var(--brandSecondary900);
        line-height: 1.167;
      }

      .no-responsive__text {
        margin: 0;
        font-size: 16px;
        font-weight: 400;
        color: var(--brandSecondary900);
        line-height: 1.5;
      }

      .header-title {
        font-size: 26px;
        font-weight: 700;
        color: #444444;
      }

      .tooltip-full-number {
        position: relative;
        display: inline-block;
      }

      .tooltip-full-number .tooltiptext {
        visibility: hidden;
        background: black;
        color: white;
        border: 1px solid black;
        text-align: start;
        padding: 3px;
        position: absolute;
        z-index: 1;
        top: 0;
        left: 100%;
        margin-left: 5px;
        opacity: 0;
        transition: opacity 0.5s;
        font-size: 13px;
        font-weight: normal;
        line-height: 100%;
      }

      .tooltip-full-number:hover .tooltiptext {
        visibility: visible;
        opacity: 1;
      }


      .wl-compare-profile {
        position: relative;
        left: 0;
        padding: 30px;
        margin-bottom: 20px;
        background: var(--white);;
        border-bottom: 1px solid #CED4DA;
      }

      .alert-list {
        padding: 30px;
        padding-top: 0;
      }

    .drift-detection {
       justify-content: space-between;
       align-items: center;
     }

     .drift-detection-info-circle {
       width: 15px;
       height: 15px;
       border-radius: 50px;
       display: inline-block;
       margin-right: 8px;
     }

     .drift-detection-info-drifts-item {
         padding-right: 20px;
     }

     .drift-detection-info-title {
       font-family: Arial;
       font-weight: bold;
       font-size: 22px;
       line-height: 130%;
       color: #313B3D;
     }

     .drift-detection-info-drifts-item-count {
       font-family: Arial;
       font-weight: bold;
       font-size: 14px;
       line-height: 16px;
       color: #000000;
       padding-right: 8px;
     }

     .drift-detection-info-drifts-item-name {
       font-family: Arial;
       font-style: normal;
       font-weight: normal;
       font-size: 12px;
       line-height: 14px;
       color: #000000;
     }

     .drift-detection-search-input {
       display: flex;
       align-items: center;
       background: rgba(255, 255, 255, 0.7);
       border: 1px solid #DBE5E7;
       box-sizing: border-box;
       border-radius: 4px;
       width: 170px;
       padding-left: 10px;
     }

     .drift-detection-search-input img{
       margin-right: 5px;
     }

     .drift-detection-search-input input::placeholder {
       font-family: Arial;
       font-weight: normal;
       font-size: 13px;
       line-height: 16px;
       color: #313B3D;
     }

     .dropdown-container {
       position: absolute;
       right: 30px;
       top: 80px;
       z-index: 999;
       background: #FFFFFF;
       border: 1px solid #DBE5E7;
       box-sizing: border-box;
       box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.05);
       border-radius: 4px;
       padding: 10px !important;
       border: none !important;
     }

     .filter-options-title {
       width: 240px;
     }

     .filter-options-title p {
       margin: 0;
     }

     .form-check-input:checked {
       background-color: #0E7384;
       border-color: #0E7384;
     }

     .form-check-input[type=checkbox] {
       border-radius: 2px;
     }

     .search-input{
       padding-top: 0 !important;
       padding-bottom: 0 !important;
     }

     .search-input input{
       border: none;
       background: none;
       outline: none;
       height: 40px;
       width: 100%;
       font-size: 14px;
     }

     .search-input img{
       height: 19px;
       pointer-events: none;
     }

     input::placeholder {
       color: var(--secondaryLight1000);
     }

      .statistics {
        width: 100%;
      }

     .close-filter-button {
       display: flex;
       justify-content: center;
       align-items: center;
       background: rgba(255, 255, 255, 0.7);
       border: 1px solid #369BAC;
       box-sizing: border-box;
       border-radius: 4px;
       width: 40px;
       height: 40px;
       cursor: pointer;
       margin-left: 10px;
     }

      .statistic-number-title {
        font-family: Arial;
        font-weight: normal;
        font-size: 14px;
        line-height: 20px;
        color: #6C757D;
      }

      .statistic-number {
        font-family: Arial;
        font-weight: bold;
        font-size: 20px;
        line-height: 140%;
        display: flex;
        align-items: center;
        color: #4F595B;
      }

      .full-summary-statistics-wrap {
        padding: 20px;
      }

      .statistics {
        width: 100%;
      }

      .statistics-list {
        width: 100% ;
      }

      mark {
        padding: 5px;
        border-radius: 4px;
        font-family: Arial;
        font-weight: normal;
        font-size: 14px;
        line-height: 140%;
      }

      .blue-mark {
        background-color:  #369BAC1A;
        color: #369BAC;
      }

      .red-mark {
        background-color: #FFEFEE;
        color: #F5473C;
      }

      .alert-tag {
        height: 27px;
        padding: 5px;
        border-radius: 4px;
        font-family: Arial;
        font-weight: bold;
        font-size: 12px;
        line-height: 140%;
        color: #FFFFFF;
      }

      .turquoise-background-color {
        background-color: #1DBB42;
      }

      .bordeaux-background-color {
        background-color: #C6462A;
      }

      .border-solid-gray {
        border: 1px solid #CED4DA;
        border-radius: 4px;
      }

      .display-flex {
        display: flex;
      }

      .justify-content-space-between {
        justify-content: space-between;
      }

      .justify-content-center {
        justify-content: center;
      }

      .align-items-center {
        align-items: center;
      }

      .align-items-flex-start {
        align-items: flex-start;
      }

      .padding-right-30 {
        padding-right: 30px;
      }

      .notif-circle-container{
        position: absolute;
        top: 25px;
        right: 25px;
        padding: 5.3px;
        border-radius: 50%;
        background-color: white;
        cursor: pointer;
      }

      .notif-circle {
        position: absolute;
        top: 2px;
        right: 2px;
        padding: 3.3px;
        border-radius: 50%;
        background-color: #F2994A;
      }

      .alert-list-text {
        width: 70%
      }

     @media screen and (min-width: 500px) {
       .desktop-content {
         display: block;
       }
       .no-responsive {
         display: none;
       }
     }
    </style>
  </head>

  <body id="generated-html"></body>

  <script id="entry-template" type="text/x-handlebars-template">
    {{{{raw}}}}
      <div class="desktop-content">
        <div class="full-summary-statistics-wrap">
          <div class="full-summary-statistics">
            <div>
              <div class="display-flex justify-content-center">
                <div class="statistics-list border-solid-gray">
                  <div>
                    <div class="wl-compare-profile" id="compare-profile">
                        <div class="drift-detection-wrap">
                          <div class="drift-detection display-flex align-items-flex-start">
                            <div class="drift-detection-info flex-direction-colum">
                              <div class="drift-detection-info-title-wrap display-flex">
                                <p class="drift-detection-info-title">
                                  Constraints Report
                                </p>
                              </div>

                              <!-- </div> -->
                            </div>
                            <div class="drift-detection-search-input-wrap display-flex">
                              <div class="drift-detection-search-input search-input">
                                <input type="text" id="wl__feature-search" placeholder="Quick search..."/>
                                <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAGdSURBVHgBpZK7TkJBEIZnZoVocdTYyQNALxpNKPQBMEaNJsbCRMKhl8ISWwt6AaksCF5iTHgAGhJD4AHkAaAzGiwUsjvOQnO4SYh/cXbPzO43OxcEj9Zy92EiFSXNIfvPyE1kKFfdoxJMENpP6DrvLC0vJoEwCgwto7DWcxoIIHBYbA3NmKwnDltjAeuZhyul1DaTTlfPB6Nt5Z53DOgky4P875+nlctY2+unjZviLklkJhi5bPUa3y/7qJuQUM7PinMy7CdQc1Gh16vnBxPzrMROmlKQEgKNASAHLQCmSIGpS75O+O5pdQAgVXaIqTkNwDDXHmcnW3VmHZoGMLoTsOt88+NrAMCIZdu+iLTyTwKRa1Md6YKfOgXbzO7K8sWku5u5RxcRV5EpPezrzcHGbXEXWaUkgkweZ/UC9YrK3zqggFw5FBZfm8EUavHj7AjAKpIvBDrGn+pNnlcyhYgqbcC41idr1gvB4SdZkDbzQa21gwv0Vj07aPTtL07XdDOyDXohCDNoHIRmAVRie20f+RKybRDQDvxHkXy/7b/DrayncLbMwQAAAABJRU5ErkJggg=="/>
                              </div>
                              <div class="wl__dropdown_arrow-icon">
                                <div onclick="openFilter()" class="close-filter-button">
                                <div class="display-flex close-filter-icon d-none">
                                  <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAADFSURBVHgBfY+xDcIwEEXvnLQBZQkYAEhDwwKpEEK0CCZgAEjJCEmgjYSAygxAHTZgFRSOsyUjY5mcZFnn/+78PwBXf3+MoKWUPuYjVBPFnTwpr9t/oNJfcTfXsAhRAlDqDhhQIPYgpAqNMDqcUqSAYZT1epr9gAHt6uXshvYme4DYHQJNDKh0dD0m5WXB10Y3Fqjtuh7fROn3oREDWxfeMLyRsMnc0OgDzdduaA0Pi3Plgr7Q2kaAePeBqh6rueSNBVt6fgCwBV1JLF3rlAAAAABJRU5ErkJggg=="/>
                                </div>
                                <div class="display-flex filter-icon">
                                  <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAPCAYAAADtc08vAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACSSURBVHgBrZLBCYAwDEWTUjw7igcdwI1cxkNXUJzBEVzFAbSVKoKmaVrEB6GHJv/w+AACtRk7P9IONv1QOYUl96k0zv61m2tjARoLtSDI3EFsgIJ4uoXrMLazO72CRG2mzg/8BSdVlEjhpGZJjAWdAZJECpWalEhJSs1pHuUlMad5FFai1Lwg4Ckx1TxKIPFL8w55mEWd8VjPGAAAAABJRU5ErkJggg=="/>
                                </div>
                                </div>
                              <span class="notif-circle-container">
                                <span class="notif-circle"></span>
                              </span>
                            </div>
                            </div>
                          </div>
                        </div>
                        <div class="dropdown-container flex-direction-colum mb-2 d-none" id="dropdown-container">
                          <div class="filter-options">
                            <div class="filter-options-title space-between dropdown">
                              <p>Select a view:</p>
                            </div>
                            <div class="form-check mb-1 mt-2">
                              <input
                                class="form-check-input wl__feature-filter-input"
                                type="checkbox"
                                name="checkbox"
                                value="Discrete"
                                id="inferredDiscrete"
                                checked
                              />
                              <label class="form-check-label" for="inferredDiscrete">
                                Failed constraints (<span class="wl__feature-count--discrete"></span>)
                              </label>
                            </div>
                            <div class="form-check mb-1">
                              <input
                                class="form-check-input wl__feature-filter-input"
                                type="checkbox"
                                name="checkbox"
                                value="Non-discrete"
                                id="inferredNonDiscrete"
                                checked
                              />
                              <label class="form-check-label" for="inferredNonDiscrete">
                                Passed constraints (<span
                                  class="wl__feature-count--non-discrete"
                                ></span>)
                              </label>
                            </div>
                            <div class="form-check mb-1">
                              <input
                                class="form-check-input wl__feature-filter-input"
                                type="checkbox"
                                name="checkbox"
                                value="Unknown"
                                id="inferredUnknown"
                                checked
                              />
                              <label class="form-check-label" for="inferredUnknown">
                                All constraints (<span class="wl__feature-count--unknown"></span>)
                              </label>
                            </div>
                          </div>
                        </div>
                      </div>
                    <div class="alert-list" id="alert-list">
                      {{{alertLIst this}}}
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="no-responsive">
        <div class="no-responsive__content">
          <h1 class="no-responsive__title">Hold on! :)</h1>
          <p class="no-responsive__text">
            It looks like your current screen size or device is not yet supported by the WhyLabs Sandbox. The Sandbox is
            best experienced on a desktop computer. Please try maximizing this window or switching to another device. We
            are working on adding support for a larger variety of devices.
          </p>
        </div>
      </div>
    {{{{/raw}}}}
  </script>

  <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js" integrity="sha512-cd6CHE+XWDQ33ElJqsi0MdNte3S+bQY819f7p3NUHgwQQLXSKjE4cPZTeGNI+vaxZynk1wVU3hoHmow3m089wA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

  <script>
    function registerHandlebarHelperFunctions() {
      //helper fun
      function formatLabelDate(timestamp) {
        const date = new Date(timestamp);
        const format = d3.timeFormat("%Y-%m-%d %I:%M:%S %p %Z");
        return format(date);
      }

      function fixNumberTo(number, decimals = 3) {
        return parseFloat(number).toFixed(decimals);
      }

      const randomNumbers = (range) => Math.floor(Math.random() * range)

      const findFetureWithNumberSummary = (column) => {
        const fetureIndex = Object.values(column.columns)
              .findIndex((feture) => feture.numberSummary)

        return Object.keys(column.columns)[fetureIndex]
      }

      const alertListItemStatus = (status, passedItem, failedItem) => {
        if (status) {
          return passedItem
        } else {
          return failedItem
        }
      }

      const alertListElement = (name, text, status, summary) => {
        if (summary == null){
          return (
          `<div
             data-inferred-type=${alertListItemStatus(status, "passed", "failed")}
             class="alert-list-item display-flex justify-content-space-between align-items-center mb-2"
           >
            <div class="alert-list-text">
              ${
                name &&
                alertListItemStatus(
                  status,
                  `<mark class="blue-mark">${name}</mark>`,
                  `<mark class="red-mark">${name}</mark>`
                )
              }
              ${text}
            </div>
              ${
                alertListItemStatus(
                  status,
                  `
                  <div class="turquoise-background-color alert-tag">Passed</div>
                  `
                  ,
                  `
                  <div class="bordeaux-background-color alert-tag">Failed</div>
                  `
                )
              }
              </div>`
        )

        }
        return (
          `<div
             data-inferred-type=${alertListItemStatus(status, "passed", "failed")}
             class="alert-list-item display-flex justify-content-space-between align-items-center mb-2"
           >
            <div class="alert-list-text">
              ${
                name &&
                alertListItemStatus(
                  status,
                  `<mark class="blue-mark">${name}</mark>`,
                  `<mark class="red-mark">${name}</mark>`
                )
              }
              ${text}
            </div>
              ${
                alertListItemStatus(
                  status,
                  `
                  <div class="tooltip-full-number">
                    <div class="turquoise-background-color alert-tag">Passed
                      <span class="tooltiptext">
                          <pre class="mb-1"> ${JSON.stringify(summary, null, 2)} </pre>
                        </span>
                    </div>
                  </div>`

                  ,
                  `
                  <div class="tooltip-full-number">
                    <div class="bordeaux-background-color alert-tag">Failed
                      <span class="tooltiptext">
                        <pre class="mb-1"> ${JSON.stringify(summary, null, 2)} </pre>
                        </span>
                    </div>
                  </div>`
                )
              }
              </div>`
        )
      }

      let failedConstraints = 0;

      Handlebars.registerHelper("getProfileTimeStamp", function (column) {
        return formatLabelDate(+column.properties.dataTimestamp)
      });

      Handlebars.registerHelper("getProfileName", function (column) {
        return column.properties.tags.name
      });


      Handlebars.registerHelper("alertLIst", function (column) {
        let alertListItem = column.map((value) => {
          if (value[1][0]) {
            let alertListValue = value[1].map((cstr)=>{
              return alertListElement(value[0],cstr[0],cstr[cstr.length - 1] === 0 || (failedConstraints++, false), value[3])
            })
            return alertListValue.join(' ')
          } else {
            return alertListElement('', value[0], value[2] === 0 ||  (failedConstraints++, false), value[3])
          }
        })
         $(document).ready(() => {
           $(".wl__feature-count--discrete").append(failedConstraints)
           $(".wl__feature-count--non-discrete").append(column.length - failedConstraints)
           $(".wl__feature-count--unknown").append(column.length)
         })
        return alertListItem.join(' ')
      });

    }

    function openFilter() {
      const $filterOptions = $(".dropdown-container");
      const filterClass = $filterOptions.attr("class");

      if (filterClass.indexOf("d-none") > 0) {
        $filterOptions.removeClass("d-none");
        $(".filter-icon").addClass("d-none")
        $(".close-filter-icon").removeClass("d-none")
      } else {
        $filterOptions.addClass("d-none");
        $(".close-filter-icon").addClass("d-none")
        $(".filter-icon").removeClass("d-none")
      }
    }

    function initHandlebarsTemplate() {
      // Replace this context with JSON from .py file
      const context = {{{constraints_report}}};
      // Config handlebars and pass data to HBS template
      const source = document.getElementById("entry-template").innerHTML;
      const template = Handlebars.compile(source);
      const html = template(context);
      const target = document.getElementById("generated-html");
      target.innerHTML = html;
    }

    function initWebsiteScripts() {
      const $featureSearch = document.getElementById("wl__feature-search");
      const $alertList = document.getElementById("alert-list");
      const $discrete = document.getElementById("inferredDiscrete");
      const $nonDiscrete = document.getElementById("inferredNonDiscrete");
      const $unknown = document.getElementById("inferredUnknown");

      const activeTypes = {
        passed: true,
        failed: true
      };

      let searchString = "";

      function debounce(func, wait, immediate) {
        let timeout;

        return function () {
          const context = this;
          const args = arguments;
          const later = function () {
            timeout = null;
            if (!immediate) func.apply(context, args);
          };

          const callNow = immediate && !timeout;
          clearTimeout(timeout);
          timeout = setTimeout(later, wait);
          if (callNow) func.apply(context, args);
        };
      }

      function filterNotification() {
        const $notifCircleContainer = $(".notif-circle-container")
        const $boxes = $('.wl_filter-options>.form-check>input[name=checkbox]:checked');
        const item = Object.values($boxes).find(function(value) { return $(value)[0] === undefined});
        if (item === undefined) {
          $notifCircleContainer.removeClass("d-none")
        } else {
          $notifCircleContainer.addClass("d-none")
        }
      }

      function handleSearch() {
        const tableBodyChildren = $alertList.children;

        for (let i = 0; i < tableBodyChildren.length; i++) {
          const type = tableBodyChildren[i].dataset.inferredType.toLowerCase();
          const name = $(tableBodyChildren[i].children[0]).html().toLowerCase();
          if (activeTypes[type] && name.includes(searchString)) {
            tableBodyChildren[i].style.display = "";
          } else {
            tableBodyChildren[i].style.display = "none";
          }
        }
      }

      const checkedBoxes = () => {
        if ($('.form-check-input:checked').length === $(".form-check-input").length - 1) {
          $($(".form-check-input")[$(".form-check-input").length - 1]).prop( "checked", true );
        }
      }

      $featureSearch.addEventListener(
        "keyup",
        debounce((event) => {
          searchString = event.target.value.toLowerCase();
          handleSearch();
        }, 100),
      );

      $discrete.addEventListener("change", (event) => {
        const currentCheckbox = $(event.currentTarget);

        if (event.currentTarget.checked) {
          activeTypes["failed"] = true;
          checkedBoxes();
        } else {
          activeTypes["failed"] = false;
          $($(".form-check-input")[$(".form-check-input").length - 1]).prop( "checked", false );
        }
        handleSearch();
      });

      $nonDiscrete.addEventListener("change", (event) => {
        const currentCheckbox = $(event.currentTarget);

        if (event.currentTarget.checked) {
          activeTypes["passed"] = true;
          checkedBoxes();
        } else {
          activeTypes["passed"] = false;
          $($(".form-check-input")[$(".form-check-input").length - 1]).prop( "checked", false );
        }
        handleSearch();
      });

      $unknown.addEventListener("change", (event) => {
        const currentCheckbox = $(event.currentTarget);

        if (event.currentTarget.checked) {
          $(".form-check-input").prop( "checked", true );
          activeTypes["passed"] = true;
          activeTypes["failed"] = true;
          checkedBoxes();
        } else {
          $(".form-check-input").prop( "checked", false );
          activeTypes["passed"] = false;
          activeTypes["failed"] = false;
        }
        handleSearch();
      });
    }

    function checkedBoxes() {
      const $boxes = $('input[name=checkbox]:checked');
      const $notifCircleContainer = $(".notif-circle-container")

      if ($boxes.length) {
        $notifCircleContainer.removeClass("d-none")
      }
    }

    function openFilter() {
      const $filterOptions = $(".dropdown-container");
      const $notifCircleContainer = $(".notif-circle-container")
      const filterClass = $filterOptions.attr("class");

      if (filterClass.indexOf("d-none") > 0) {
        $notifCircleContainer.addClass("d-none")
        $filterOptions.removeClass("d-none");
        $(".filter-icon").addClass("d-none")
        $(".close-filter-icon").removeClass("d-none")
      } else {
        $filterOptions.addClass("d-none");
        $(".close-filter-icon").addClass("d-none")
        $(".filter-icon").removeClass("d-none")
        checkedBoxes()
      }
    }

    // Invoke functions -- keep in mind invokation order
    registerHandlebarHelperFunctions();
    initHandlebarsTemplate();
    initWebsiteScripts();
  </script>
</html>