<!doctype html>
<html ng-app="App" ng-controller="Controller" id="page-html">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Rally | {{title}}</title>
  
  
  <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.3/angular.min.js"></script>
  

  <script type="text/javascript">
    "use strict";
    function changeFrameHeight(){
  var ifm= document.getElementById("embeddedChart");
  ifm.height=document.documentElement.clientHeight;
  ifm.width=document.documentElement.clientWidth;
}
function hideEmbeddedChartLoading(){
  document.getElementById('embeddedChartLoader').style.display = 'none';
}
window.onresize=function(){
  changeFrameHeight();
}
var widgetDirective = function($compile) {
  var Chart = {
    _render: function(node, data, chart, do_after){
      nv.addGraph(function() {
        d3.select(node)
          .datum(data).transition().duration(0)
          .call(chart);
        if (typeof do_after === "function") {
          do_after(node, chart)
        }
        nv.utils.windowResize(chart.update);
      })
    },
    _widgets: {
      Pie: "pie",
      StackedArea: "stack",
      Lines: "lines",
      Histogram: "histogram"
    },
    get_chart: function(widget) {
      if (widget in this._widgets) {
        var name = this._widgets[widget];
        return Chart[name]
      }
      return function() { console.log("Error: unexpected widget:", widget) }
    },
    pie: function(node, data, opts, do_after) {
      var chart = nv.models.pieChart()
        .x(function(d) { return d.key })
        .y(function(d) { return d.values })
        .showLabels(true)
        .labelType("percent")
        .donut(true)
        .donutRatio(0.25)
        .donutLabelsOutside(true)
        .color(function(d){
          if (d.data && d.data.color) { return d.data.color }
        });
      var colorizer = new Chart.colorizer("errors"), data_ = [];
      for (var i in data) {
        data_.push({key:data[i][0], values:data[i][1], color:colorizer.get_color(data[i][0])})
      }
      Chart._render(node, data_, chart)
    },
    colorizer: function(failure_key, failure_color) {
      this.failure_key = failure_key || "failed_duration";
      this.failure_color = failure_color || "#d62728";  // red
      this.color_idx = -1;
      /* NOTE(amaretskiy): this is actually a result of
         d3.scale.category20().range(), excluding red color (#d62728)
         which is reserved for errors */
      this.colors = ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c",
                     "#98df8a", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b",
                     "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7",
                     "#bcbd22", "#dbdb8d", "#17becf", "#9edae5"];
      this.get_color = function(key) {
        if (key === this.failure_key) {
          return this.failure_color
        }
        if (this.color_idx > (this.colors.length - 2)) {
          this.color_idx = 0
        } else {
          this.color_idx++
        }
        return this.colors[this.color_idx]
      }
    },
    stack: function(node, data, opts, do_after) {
      var chart = nv.models.stackedAreaChart()
        .x(function(d) { return d[0] })
        .y(function(d) { return d[1] })
        .useInteractiveGuideline(opts.guide)
        .showControls(opts.controls)
        .clipEdge(true);
      chart.xAxis
        .axisLabel(opts.xname)
        .tickFormat(opts.xformat)
        .showMaxMin(opts.showmaxmin);
      chart.yAxis
        .orient("left")
        .tickFormat(d3.format(opts.yformat || ",.3f"));
      var colorizer = new Chart.colorizer(), data_ = [];
      for (var i in data) {
        data_.push({key:data[i][0], values:data[i][1], color:colorizer.get_color(data[i][0])})
      }
      Chart._render(node, data_, chart, do_after);
    },
    lines: function(node, data, opts, do_after) {
      var chart = nv.models.lineChart()
        .x(function(d) { return d[0] })
        .y(function(d) { return d[1] })
        .useInteractiveGuideline(opts.guide)
        .clipEdge(true);
      chart.xAxis
        .axisLabel(opts.xname)
        .tickFormat(opts.xformat)
        .rotateLabels(opts.xrotate)
        .showMaxMin(opts.showmaxmin);
      chart.yAxis
        .orient("left")
        .tickFormat(d3.format(opts.yformat || ",.3f"));
      var colorizer = new Chart.colorizer(), data_ = [];
      for (var i in data) {
        data_.push({key:data[i][0], values:data[i][1], color:colorizer.get_color(data[i][0])})
      }
      Chart._render(node, data_, chart, do_after)
    },
    histogram: function(node, data, opts) {
      var chart = nv.models.multiBarChart()
        .reduceXTicks(true)
        .showControls(false)
        .transitionDuration(0)
        .groupSpacing(0.05);
      chart
        .legend.radioButtonMode(true);
      chart.xAxis
        .axisLabel("Duration (seconds)")
        .tickFormat(d3.format(",.2f"));
      chart.yAxis
        .axisLabel("Iterations (frequency)")
        .tickFormat(d3.format("d"));
      Chart._render(node, data, chart)
    }
  };

  return {
    restrict: "A",
    scope: { data: "=" },
    link: function(scope, element, attrs) {
      scope.$watch("data", function(data) {
        if (! data) { return console.log("Chart has no data to render!") }
        if (attrs.widget === "Table") {
          var template = "<table class='striped'><thead>" +
            "<tr><th ng-repeat='i in data.cols track by $index'>{{i}}<tr>" +
            "</thead><tbody>" +
            "<tr ng-class='data.styles[$index] ? data.styles[$index] : \"\"' ng-repeat='row in data.rows track by $index'>" +
            "<td ng-repeat='i in row track by $index'>{{i}}" +
            "<tr>" +
            "</tbody></table>";
          var el = element.empty().append($compile(template)(scope)).children()[0]
        } else if (attrs.widget === "TextArea") {
          var template = "<div style='padding:0 0 5px' ng-repeat='str in data track by $index'>{{str}}</div><div style='height:10px'></div>";
          var el = element.empty().append($compile(template)(scope)).children()[0]
        }
        else if (attrs.widget == "EmbedChart") {
          if (typeof data === "string"){
            // backward compatibility
            data = {"embedded": data};
          }

          if (data.source !== undefined && data.source !== null){
            var template = "<span><div id='embeddedChartLoader'>Loading embedded chart. Please wait...</div><iframe id='embeddedChart' frameborder='0' onload='changeFrameHeight(); hideEmbeddedChartLoading()' style='width:100%;' src='" + data.source + "'></iframe></span>";
            var embeddedData = undefined;
          } else {
            var template = "<iframe scrolling='no' id='embeddedChart' frameborder='0' onload='changeFrameHeight()' style='width:100%;'></iframe>";
            /* NOTE(chenxu): tag <\/script> in javascript string will be parsed incorrectly.
             so we convert <\/script> to <\\/script> in python and convert it back here. */
            var embeddedData = data.embedded.replace(/\\\/script>/ig, "\/script>");
          }
          var el = element.empty().append($compile(template)(scope)).children()[0];
          if (embeddedData !== undefined) {
            var iframe = el.contentWindow || (el.contentDocument.document || el.contentDocument);
            iframe.document.open();
            iframe.document.write(embeddedData);
            iframe.document.close();
          }
        }
        else {
          var el_chart = element.addClass("chart").css({display:"block"});
          var el = el_chart.html("<svg></svg>").children()[0];

          var do_after = null;

          if (attrs.widget in {StackedArea:0, Lines:0}) {

            /* Hide widget if not enough data */
            if ((! data.length) || (data[0].length < 1) || (data[0][1].length < 2)) {
              return element.empty().css({display:"none"})
            }

            /* NOTE(amaretskiy): Dirty fix for changing chart width in case
               if there are too long Y values that overlaps chart box. */
            var do_after = function(node, chart){
              var g_box = angular.element(el_chart[0].querySelector(".nv-y.nv-axis"));

              if (g_box && g_box[0] && g_box[0].getBBox) {

                try {
                  // 30 is padding aroung graphs
                  var width = g_box[0].getBBox().width + 30;
                } catch (err) {
                  // This happens sometimes, just skip silently
                  return
                }

                // 890 is chart width (set by CSS)
                if (typeof width === "number" && width > 890) {
                  width = (890 * 2) - width;
                  if (width > 0) {
                    angular.element(node).css({width:width+"px"});
                    chart.update()
                  }
                }
              }
            }
          }
          else if (attrs.widget === "Pie") {
            if (! data.length) {
              return element.empty().css({display:"none"})
            }
          }

          var opts = {
            xname: attrs.nameX || "",
            xrotate: attrs.rotateX || 0,
            yformat: attrs.formatY || ",.3f",
            controls: attrs.controls === "true",
            guide: attrs.guide === "true",
            showmaxmin: attrs.showmaxmin === "true"
          };
          if (attrs.formatDateX) {
            opts.xformat = function(d) { return d3.time.format(attrs.formatDateX)(new Date(d)) }
          } else {
            opts.xformat = d3.format(attrs.formatX || "d")
          }
          Chart.get_chart(attrs.widget)(el, data, opts, do_after);
        }

        if (attrs.nameY) {
          /* NOTE(amaretskiy): Dirty fix for displaying Y-axis label correctly.
             I believe sometimes NVD3 will allow doing this in normal way */
          var label_y = angular.element("<div>").addClass("chart-label-y").text(attrs.nameY);
          angular.element(el).parent().prepend(label_y)
        }

        if (attrs.description) {
          var desc_el = angular.element("<div>").addClass(attrs.descriptionClass || "h3").text(attrs.description);
          angular.element(el).parent().prepend(desc_el)
        }

        if (attrs.title) {
          var title_el = angular.element("<div>").addClass(attrs.titleClass || "h2").text(attrs.title);
          angular.element(el).parent().prepend(title_el)
        }

        angular.element(el).parent().append(angular.element("<div style='clear:both'>"))
      });
    }
  }
};

    var controllerFunction = function($scope, $location) {
      $scope.data = {"uuids": ["ea6c3d69-e92d-41c6-a78c-44a2d3d5ab70"], "verifications": {"ea6c3d69-e92d-41c6-a78c-44a2d3d5ab70": {"started_at": "2020-06-04T09:31:28", "finished_at": "2020-06-04T09:31:36", "status": "finished", "run_args": {"load_list": ["tempest_horizon.tests.scenario.test_dashboard_basic_ops.TestDashboardBasicOps.test_basic_scenario[id-4f8851b1-0e69-482b-b63b-84c6e76f6c80,smoke]"]}, "tests_count": 1, "tests_duration": 3.789, "skipped": 0, "success": 1, "expected_failures": 0, "unexpected_success": 0, "failures": 0}}, "tests": {"tempest_horizon.tests.scenario.test_dashboard_basic_ops.TestDashboardBasicOps.test_basic_scenario[id-4f8851b1-0e69-482b-b63b-84c6e76f6c80,smoke]": {"tags": ["id-4f8851b1-0e69-482b-b63b-84c6e76f6c80", "smoke"], "name": "tempest_horizon.tests.scenario.test_dashboard_basic_ops.TestDashboardBasicOps.test_basic_scenario", "by_verification": {"ea6c3d69-e92d-41c6-a78c-44a2d3d5ab70": {"status": "success", "duration": "3.109", "details": null}}, "has_details": false}}, "show_comparison_note": false};

      /* Calculate columns width in percent */
      var td_ctr_width = 4;
      var td_result_width = Math.round(1 / ($scope.data.uuids.length+3) * 100);

      $scope.td_width_ = {
        counter: td_ctr_width,
        test_name: (100 - td_ctr_width - (td_result_width * $scope.data.uuids.length)),
        test_result: td_result_width
      }

      $scope.td_width = (function(vers_num) {
        var uuid_w = Math.round(1 / (vers_num+3) * 100);
        return {test: 100 - (uuid_w * vers_num),
                uuid: uuid_w}
      })($scope.data.uuids.length)

      var bitmask = {"success": 1,
                     "skip": 2,
                     "xfail": 4,
                     "uxsuccess": 8,
                     "fail": 16};

      for (var i in $scope.data.tests) {
        var t = $scope.data.tests[i];
        var bits = 0;
        for (var uuid in t.by_verification) {
          var status = t.by_verification[uuid].status;
          if (status in bitmask) {
            bits |= bitmask[status]
          }
        }
        $scope.data.tests[i].filter = bits;
      }

      $scope.set_filter = function(status) {
        if (status in $scope.state) {
          $scope.state[status] = !$scope.state[status];
          $scope.filter_bits ^= bitmask[status]
        }
      }

      $scope.state = {"success": true,
                      "skip": true,
                      "xfail": true,
                      "uxsuccess": true,
                      "fail": true};

      $scope.filter_by_status = function(test, index, arr) {
        return test.filter & $scope.filter_bits
      }

      $scope.filter_bits = (function(filter){
        var bits = 0;
        for (var status in $scope.state){
          if ($scope.state[status]) { bits ^= bitmask[status] }
        }
        return bits
      })();

      $scope.toggle_filters_flag = true;
      $scope.toggle_filters = function() {
        if ($scope.toggle_filters_flag) {
          $scope.toggle_filters_flag = false;
          $scope.state = {"success": false,
                          "skip": false,
                          "xfail": false,
                          "uxsuccess": false,
                          "fail": false};
          $scope.filter_bits = 0
        } else {
          $scope.toggle_filters_flag = true
          $scope.state = {"success": true,
                          "skip": true,
                          "xfail": true,
                          "uxsuccess": true,
                          "fail": true};
          $scope.filter_bits = 31
        }
      }

      var title = "verification result";

      if ($scope.data.uuids.length > 1) {
        title = "verifications results"
      }

      $scope.title = title;

      $scope.srt_dir = false;

      $scope.get_tests_count = function() {
        var ctr = 0;
        for (var i in $scope.data.tests) {
          if ($scope.data.tests[i].filter & $scope.filter_bits) {
            ctr++
          }
        }
        return ctr
      }

      var title = angular.element(document.getElementById("page-header"));
      var header = angular.element(document.getElementById("content-header"));
      var tests = angular.element(document.getElementById("tests"));
      var sync_positions = function() {
        var title_h = title[0].offsetHeight;
        var header_h = header[0].offsetHeight;
        header.css({top:title_h+"px"})
        tests.css({"margin-top": (title_h+header_h)+"px"});
      }

      /* Make page head sticky */
      window.onload = function() {
        title.css({position:"fixed", top:0, width:"100%"});
        header.css({position:"fixed", width:"100%", background:"#fff"});

        sync_positions();
        window.onresize = sync_positions;

        var goup = document.getElementById("button-goup");
        goup.onclick = function () { scrollTo(0, 0) };
        window.onscroll = function() {
          if (window.scrollY > 50) {
            goup.style.display = "block";
          } else {
            goup.style.display = "none";
          }
        }
      }

      $scope.show_header = true;
      $scope.toggle_header = (function(e) {
        return function() {
          $scope.show_header = (e.style.display === "none");
          e.style.display = $scope.show_header ? "table" : "none";
          sync_positions()
        }
      })(document.getElementById("verifications"))

    };

    if (typeof angular === "object") {
      angular.module("App", [])
        .controller("Controller", ["$scope", "$location", controllerFunction])
        .directive("widget", widgetDirective)
    }
</script>
  <style>
    body { margin:0; padding:0 0 50px; font-size:14px; font-family:Helvetica,Arial,sans-serif }
    a, a:active, a:focus, a:visited { text-decoration:none; outline:none }
    p { margin:0; padding:5px 0 }
    p.thesis { padding:10px 0 }
    h1 { color:#666; margin:0 0 20px; font-size:30px; font-weight:normal }
    h2, .h2 { color:#666; margin:24px 0 6px; font-size:25px; font-weight:normal }
    h3, .h3 { color:#777; margin:12px 0 4px; font-size:18px; font-weight:normal }
    table { border-collapse:collapse; border-spacing:0; width:100%; font-size:12px; margin:0 0 10px }
    table th { text-align:left; padding:8px; color:#000; border:2px solid #ddd; border-width:0 0 2px 0 }
    table th.sortable { cursor:pointer }
    table td { text-align:left; border-top:1px solid #ddd; padding:8px; color:#333 }
    table.compact td { padding:4px 8px }
    table.striped tr:nth-child(odd) td { background:#f9f9f9 }
    table.linked tbody tr:hover { background:#f9f9f9; cursor:pointer }
    .pointer { cursor:pointer }
    .rich, .rich td { font-weight:bold }
    .oblique { font-style:italic }
    .code { padding:10px; font-size:13px; color:#333; background:#f6f6f6; border:1px solid #e5e5e5; border-radius:4px }

    .header { text-align:left; background:#333; font-size:18px; padding:13px 0; margin-bottom:20px; color:#fff; background-image:linear-gradient(to bottom, #444 0px, #222 100%) }
    .header a, .header a:visited, .header a:focus { color:#999 }

    .notify-error { padding:5px 10px; background:#fee; color:red }
    .status-skip, .status-skip td { color:grey }
    .status-pass, .status-pass td { color:green }
    .status-fail, .status-fail td { color:red }
    .capitalize { text-transform:capitalize }
    
    div.header {margin:0 !important}
    div.header .content-wrap { padding-left:10px }
    .status.status-success { background: #cfc; color: #333 }
    .status.status-uxsuccess { background: #ffd7af; color: #333 }
    .status.status-fail { background: #fbb; color: #333 }
    .status.status-xfail { background: #ffb; color: #333 }
    .status.status-skip { background: #ccf5ff; color: #333 }
    .status.checkbox { font-size:18px; text-align:center; cursor:pointer; padding:0 }
    .column { display:block; float:left; padding:4px 0 4px 8px; box-sizing:border-box;
              background:#fff; font-size:12px; font-weight:bold;
              border:#ccc solid; border-width:0 0 1px }
    .button { margin:0 5px; padding:0 8px 1px; background:#47a; color:#fff; cursor:pointer;
              border:1px #036 solid; border-radius:11px; font-size:12px; font-weight:normal;
              opacity:.8}
    .button:hover { opacity:1 }
    #button-goup { padding:3px 10px 5px; text-align:center; cursor:pointer;
                    background:#fff; color:#036; line-height:14px; font-size:14px;
                    position:fixed; bottom:0; right:10px;
                    border:#ccc solid; border-width:1px 1px 0; border-radius:15px 15px 0 0}

    .content-wrap {  margin:0 auto; padding:0 5px; width:100%; padding:0 }
    
  </style>
</head>
<body id="page-body" style="position:relative">

  <div class="header" id="page-header">
    <div class="content-wrap">
      <a href="https://github.com/openstack/rally">Rally</a>&nbsp;
      <span>{{title}}</span>
    </div>
  </div>

  <div class="content-wrap" id="page-content">
    

  
  <h3 ng-hide="true" style="padding-left:10px">processing ...</h3>

  <div id="content-header" ng-cloak>
    <table class="compact" id="verifications"
           style="border:#fff solid; border-width:2px 0 15px; margin:0">
      <thead>
        <tr>
          <th>Verification UUID
          <th>Status
          <th>Started at
          <th>Finished at
          <th>Tests count
          <th>Tests duration, sec
          <th style="width:9%">success
          <th style="width:9%">skipped
          <th style="width:9%">expected failures
          <th style="width:9%">unexpected success
          <th style="width:9%">failures
        </tr>
      </thead>
      <tbody>
        <tr ng-repeat="uuid in data.uuids">
          <td>{{uuid}}
          <td>{{data.verifications[uuid].status}}
          <td>{{data.verifications[uuid].started_at}}
          <td>{{data.verifications[uuid].finished_at}}
          <td>{{data.verifications[uuid].tests_count}}
          <td>{{data.verifications[uuid].tests_duration}}
          <td class="status status-success">{{data.verifications[uuid].success}}
          <td class="status status-skip">{{data.verifications[uuid].skipped}}
          <td class="status status-xfail">{{data.verifications[uuid].expected_failures}}
          <td class="status status-uxsuccess">{{data.verifications[uuid].unexpected_success}}
          <td class="status status-fail">{{data.verifications[uuid].failures}}
        </tr>
        <tr>
          <td colspan="6" style="text-align:right; font-weight:bold">
            Filter tests by status:
          <td class="checkbox status status-success" ng-click="set_filter('success')">
            <span ng-hide="state.success">&#x2610;</span>
            <span ng-show="state.success">&#x2611;</span>
          <td class="checkbox status status-skip" ng-click="set_filter('skip')">
            <span ng-hide="state.skip">&#x2610;</span>
            <span ng-show="state.skip">&#x2611;</span>
          <td class="checkbox status status-xfail" ng-click="set_filter('xfail')">
            <span ng-hide="state['xfail']">&#x2610;</span>
            <span ng-show="state['xfail']">&#x2611;</span>
          <td class="checkbox status status-uxsuccess" ng-click="set_filter('uxsuccess')">
            <span ng-hide="state['uxsuccess']">&#x2610;</span>
            <span ng-show="state['uxsuccess']">&#x2611;</span>
          <td class="checkbox status status-fail" ng-click="set_filter('fail')">
            <span ng-hide="state.fail">&#x2610;</span>
            <span ng-show="state.fail">&#x2611;</span>
        </tr>
      </tbody>
    </table>
    <!-- TODO(andreykurilin): display a note about the comparison strategy
    <div style="">
      <span ng-if="{{show_comparison_note}}">Note: </span>
    </div> -->

    <div style="text-align:left; padding:6px 3px; background:#fff">
      <span class="button" ng-click="toggle_header()">
        Toggle Header
      </span>
      <span class="button" ng-click="show_tags=!show_tags">
        Toggle Tags
      </span>
      <span class="button" style="float:right"
            ng-show="show_header" ng-click="toggle_filters()">
        Toggle All Filters
      </span>
    </div>
    <div style="clear:both"></div>

    <div style="width:{{td_width.test}}%" class="column">
      <span ng-click="srt_dir=!srt_dir" class="pointer">
        Test name
        <span style="color:#777">(shown {{get_tests_count()}})</span>
        <span style="color:orange">
          <span ng-hide="srt_dir">&#x25be;</span>
          <span ng-show="srt_dir">&#x25b4;</span>
        </span>
      </span>
    </div>
    <div ng-repeat="uuid in data.uuids"
         class="column"
        style="width:{{td_width.uuid}}%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis">
      {{uuid}}
    </div>
    <div style="clear:both"></div>
  </div>

  <table class="compact" id="tests" style="margin:0; width:100%" ng-cloak>
    <tbody ng-repeat="t in data.tests | orderBy:'name':srt_dir track by $index" ng-show="filter_by_status(t)">
      <tr ng-click="t.expanded=!t.expanded" ng-class="{pointer:t.has_details}">
        <td style="width:{{td_width.test}}%; word-break:break-all">
          {{t.name}}
          <div ng-show="show_tags" style="font-size:12px; color:#999; word-break:normal">
            <span ng-repeat="tag in t.tags"> {{tag}}</span>
          </div>
        <td ng-repeat="uuid in data.uuids"
            class="status status-{{t.by_verification[uuid].status}}"
            style="width:{{td_width.uuid}}%">
          <div ng-if="t.by_verification[uuid]">
            {{t.by_verification[uuid].status}} {{t.by_verification[uuid].duration}}
          </div>
          <div ng-if="!t.by_verification[uuid]" style="color:#999">
            &ndash;
          </div>
      </tr>
      <tr ng-if="t.has_details" ng-show="t.expanded" style="width:100%">
        <td colspan="{{3+data.uuids.length}}" style="padding:0">
          <div ng-repeat="uuid in data.uuids" ng-if="t.by_verification[uuid].details"
               class="status status-{{t.by_verification[uuid].status}}"
               style="padding:5px">
            <div style="font-weight:bold; color:#333">{{uuid}}</div>
            <pre style="text-overflow:hidden">{{t.by_verification[uuid].details}}</pre>
          </div>
      </tr>
    </tbody>
  </table>
  <span id="button-goup" style="display:none">Go Up</span>
  


  </div>

  <script type="text/javascript"></script>
</body>
</html>