<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en-US">
<head profile="http://gmpg.org/xfn/11">
  <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>

  <title>Shaker | The distributed data-plane testing tool for OpenStack</title>

  <!-- LIBS -->

  <script type="application/javascript"
          src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
  <script type="application/javascript"
          src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js"></script>
  <script type="application/javascript"
          src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/js/bootstrap.js"></script>
  <script type="text/javascript"
          src="https://cdn.datatables.net/1.10.2/js/jquery.dataTables.min.js"></script>
  <script type="text/javascript"
          src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.min.js"></script>
  <script type="application/javascript"
          src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/3.2.7/js-yaml.min.js"></script>
  <script type="application/javascript"
          src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
  <script type="application/javascript"
          src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.js"></script>

  <!-- STYLES -->

  <link rel="stylesheet"
        href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.4/simplex/bootstrap.min.css">
  <link rel=stylesheet type=text/css
        href="https://cdn.datatables.net/1.10.2/css/jquery.dataTables.min.css">
  <link rel=stylesheet type=text/css
        href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.css">
  <link rel='stylesheet' type='text/css'
        href='http://fonts.googleapis.com/css?family=Open+Sans:400,700,400italic&subset=latin,cyrillic'>

  <script type="application/javascript">
    var report = [[[ report | json]]];
  </script>

  <script type="text/javascript">
    "use strict";

    $(function () {

      var fields = ["scenario", "test", "concurrency", "node", "agent"];
      var state = {
        scenario: "",
        test: "",
        concurrency: "",
        node: "",
        agent: ""
      };
      var tablePageSize = 10;

      // compiled templates
      var scenario_source_template = Handlebars.compile($("#scenario_source_template").html());
      var test_specification_template = Handlebars.compile($("#test_specification_template").html());
      var agents_table_template = Handlebars.compile($("#agents_table_template").html());
      var records_table_template = Handlebars.compile($("#records_table_template").html());
      var executor_template = Handlebars.compile($("#executor_template").html());
      var concurrency_summary_template = Handlebars.compile($("#concurrency_summary_template").html());
      var test_summary_template = Handlebars.compile($("#test_summary_template").html());
      var sla_table_template = Handlebars.compile($("#sla_table_template").html());
      var errors_template = Handlebars.compile($("#errors_template").html());

      function concatenate(strings) {
        // returns strings concatenated via comma (like join() but omits empty strings)
        var f = false;
        var result = "";
        $.each(strings, function (_i, string) {
          if (string.length > 0) {
            if (f) result += ", ";
            result += string;
            f = true;
          }
        });
        return result;
      }

      function initSingleSelector(name, values) {
        var selectorId = "#" + name + "_selector";

        var selector_inst = $(selectorId);
        selector_inst.empty();
        selector_inst.append($("<option>", {
          style: "font-style: italic; text-decoration: underline;",
          value: "",
          text: selector_inst.data('placeholder')
        }));

        $.each(values, function (key, item) {
          selector_inst.append($('<option>', {
            value: item, //(key + 1),
            text: item
          }));
        });

        selector_inst.val(state[name]);

        selector_inst.
            on("change", function (e) {
              state[name] = this.value;

              // convert state -> url
              var tokens = [];
              $.each(fields.sort(), function (i, field) {
                if (state[field] != "") {
                  tokens.push(field + "=" + state[field]);
                }
              });
              window.location.href = "#" + tokens.join(":");
            });
      }

      function getFieldValues(field, records) {
        var values_set = {};

        $.each(records, function (j, record) {
          if (record[field]) {
            values_set["" + record[field]] = true;
          }
        });

        return $.map(values_set, function (_i, value) {
          return value;
        }).sort();
      }

      function updateSelectors() {
        $.each(fields, function (i, field) {
          var values = getFieldValues(field, filterRecords(report.records, null, field));
          console.log(values);
          initSingleSelector(field, values);
        });
      }

      function filterRecords(records, type, except_field) {
        var res = [];
        $.each(records, function(_i, record){
          if (!type || typeof type === 'undefined' || record.type == type) {
            var flag = true;
            $.each(state, function (key, value) {
              if (key != except_field && value) {
                flag &= record[key] == value;
              }
            });
            if (flag) {
              res.push(record);
            }
          }
        });
        return res;
      }

      function getAgentsByIds(agent_ids) {
        var agents = [];
        $.each(agent_ids, function(agent_id){
          agents.push(report.agents[agent_id]);
        });
        return agents;
      }

      /* Template Rendering Funcs */

      function showScenarioSource(contentArea, scenario) {
        contentArea.append(scenario_source_template(
            {data: jsyaml.safeDump(scenario)}));
      }

      function showTestSpecification(contentArea, test) {
        contentArea.append(test_specification_template(
            {data: jsyaml.safeDump(test)}
        ))
      }

      function showAgentRecord(contentArea, record) {
        var chart_id = "chart_" + Math.round(Math.random() * 10000);
        var ext_options = {
          command_yaml: jsyaml.safeDump(record["command"]),
          chart_id: chart_id};
        if (record["stats"]) {
          ext_options["stats_yaml"] = jsyaml.safeDump(record["stats"]);
        }
        if ((record["start"]) && (record["finish"])) {
          ext_options["time_info"] = jsyaml.safeDump({
            "start": new Date(record["start"] * 1000).toISOString(),
            "finish": new Date(record["finish"] * 1000).toISOString()
          });
        }
        contentArea.append(executor_template(
            {record: $.extend({}, record, ext_options)}
        ));
        if (record["samples"]) {
          var meta = record["meta"];
          var axes = [];
          axes[meta[1][0]] = 'y';
          var axis = {
            x: {label: concatenate(meta[0])},
            y: {label: concatenate(meta[1]), min: 0}
          };
          if (meta.length > 2) {
            axes[meta[2][0]] = 'y2';
            axis['y2'] = {label: concatenate(meta[2]), min: 0, show: true}
          }

          var chart = [];
          $.each(meta, function(_i, meta_item){  // row titles
            chart.push([meta_item[0]]);
          });
          var samples = record["samples"];
          for (var i=0; i < samples.length; i++) {
            var point = samples[i];
            for (var j=0; j < point.length; j++) {
              chart[j].push(point[j]);
            }
          }

          c3.generate({
            bindto: "#" + chart_id,
            data: {
              x: 'time',
              columns: chart,
              axes: axes
            },
            axis: axis
          });
        }
      }

      function showConcurrencySummary(contentArea, record) {
        var chart_id = "chart_" + Math.round(Math.random() * 10000);
        var node_chart_id = "node_chart_" + Math.round(Math.random() * 10000);
        var ext_options = { chart_id: chart_id, node_chart_id: node_chart_id };
        if (record["stats"]) {
          ext_options["stats_yaml"] = jsyaml.safeDump(record["stats"]);
        }
        console.log(record);
        contentArea.append(concurrency_summary_template(
            {record: $.extend({}, record, ext_options)}
        ));
        if (record["node_chart"]) {
          c3.generate({
            bindto: "#" + node_chart_id,
            data: {
              x: 'x',
              columns: record["node_chart"],
              type: 'step',
              order: null
            },
            axis: {
              x: { type: 'category' }
            }
          });
        }
      }

      function showTestSummary(contentArea, record) {
        console.log(record);
        var chart_id = "chart_" + Math.round(Math.random() * 10000);
        var ext_options = { chart_id: chart_id };
        if (record["stats"]) {
          ext_options["stats_yaml"] = jsyaml.safeDump(record["stats"]);
        }
        contentArea.append(test_summary_template(
            {record: $.extend({}, record, ext_options)}
        ));
        if (record["chart"]) {
          var meta = record["meta"];
          var axes = [];
          axes[meta[1][0]] = 'y';
          var axis = {
            x: {label: concatenate(meta[0])},
            y: {label: concatenate(meta[1]), min: 0}
          };
          if (meta.length > 2) {
            axes[meta[2][0]] = 'y2';
            axis['y2'] = {label: concatenate(meta[2]), min: 0, show: true}
          }

          c3.generate({
            bindto: "#" + chart_id,
            data: {
              x: 'concurrency',
              columns: record["chart"],
              axes: axes
            },
            axis: axis
          });
        }
      }

      function showAgentsTable(contentArea, filteredRecords) {
        var agent_ids = {};
        $.each(filteredRecords, function(_i, record){
          if (record.agent) {
            agent_ids[record.agent] = true;
          }
        });

        var agents = getAgentsByIds(agent_ids);
        if (agents.length == 0) return;

        // add slave agents
        var slave_agent_ids = {};
        $.each(agents, function(_i, agent){
          if (agent.slave_id) {
            slave_agent_ids[agent.slave_id] = true;
          }
        });
        agents = $.merge(agents, getAgentsByIds(slave_agent_ids));

        contentArea.append(agents_table_template({
          agents: agents,
          table_id: "agents_table"
        }));
        $("#agents_table").dataTable({
          "autoWidth": true,
          "paging": false,
          "ordering": true,
          "info": false
        });
      }

      function makeURL(record, s) {
        var extra = {};
        extra[s] = record[s];
        return "#" + $.map($.extend({}, state, extra), function (val, index) {
          return val? (index + "=" + val) : null;
        }).join(":")
      }

      function showRecordsTable(contentArea, filteredRecords) {
        $.each(filteredRecords, function(_i, record) {
          $.each(fields, function(_j, field){
            if (record[field]) {
              record[field + "_url"] = makeURL(record, field);
            }
          });
        });

        contentArea.append(records_table_template({
          records: filteredRecords,
          table_id: "records_table"
        }));
        $("#records_table").dataTable({
          autoWidth: true,
          paging: filteredRecords.length > tablePageSize,
          ordering: true,
          info: false,
          searching: filteredRecords.length > tablePageSize
        });
      }

      function showSlaTable(contentArea, records) {
        if (records.length == 0) return;

        contentArea.append(sla_table_template({
          records: records,
          table_id: "sla_table"
        }));
        $("#records_table").dataTable({
          autoWidth: true,
          paging: records.length > tablePageSize,
          ordering: true,
          info: false,
          searching: records.length > tablePageSize
        });
      }

      function showErrors(contentArea, records) {
        if (records.length == 0) return;

        contentArea.append(errors_template({
          records: records,
          table_id: "errors_list"
        }));
      }

      /* Update cycle */

      function update() {
        var filteredRecords = filterRecords(report.records);
        console.log(filteredRecords);
        var slaRecords = [];
        var errorRecords = [];
        $.each(filterRecords(report.records), function(_i, record) {
          if (record["sla_info"]) {
            slaRecords.push(record);
          }
          if (record["is_status_error"]) {
            errorRecords.push(record);
          }
        });

        var contentArea = $("#content_area");
        contentArea.empty();

        updateSelectors();

        showRecordsTable(contentArea, filteredRecords);
        showSlaTable(contentArea, slaRecords);
        showErrors(contentArea, errorRecords);

        if (state["scenario"] != "" && state["test"] != "" &&
                state["concurrency"] != "" &&
                state["agent"] != "") {
          showAgentRecord(contentArea, filteredRecords[0]);
        }
        if (state["scenario"] != "" && state["test"] != "" &&
                state["concurrency"] != "" && state["node"] == "" &&
                state["agent"] == "") {
          var agg_recs = filterRecords(report.records, "concurrency");
          if (agg_recs.length > 0) {
            showConcurrencySummary(contentArea, agg_recs[0]);
          }
        }
        if (state["scenario"] != "" && state["test"] != "" &&
                state["concurrency"] == "" && state["node"] == "" &&
                state["agent"] == "") {
          var test_recs = filterRecords(report.records, "test");
          if (test_recs.length > 0) {
            showTestSummary(contentArea, test_recs[0]);
          }
        }
        if (state["test"] != "") {
          showTestSpecification(contentArea, report.tests[state["test"]]);
        }
        if (state["agent"] == "") {
          showAgentsTable(contentArea, filteredRecords);
        }
        if (state["scenario"] != "") {
          showScenarioSource(contentArea, report.scenarios[state["scenario"]]);
        }
      }

      function parseUrlToState() {
        var hashUrl = window.location.hash.substr(1);

        $.each(hashUrl.split(":"), function (_i, pair) {
          var kv = pair.split("=");
          if (kv.length == 2) {
            state[kv[0]] = decodeURIComponent(kv[1]);
          }
        });
      }

      // entry-point
      parseUrlToState();
      console.log(state);

      // pre-process records
      $.each(report.records, function(_i, record) {
        if (record.status) {
          record["is_status_ok"] = record.status == "ok";
          record["is_status_lost"] = record.status == "lost";
          record["is_status_interrupted"] = record.status == "interrupted";
          record["is_status_error"] = record.status == "error";
        }
      });

      // auto-select filters with 1 option
      $.each(fields, function(_i, field) {
        var values = getFieldValues(field, report.records);
        if (values.length == 1) {
          state[field] = values[0];
        }
      });

      update();

      $(window).on('hashchange', function() {
        parseUrlToState();
        update()
      });

      $('[data-toggle="tooltip"]').tooltip();
    });
  </script>

  <style type="text/css">
  </style>

</head>

<body>

<div class="container" id="container">
  <div class="row text-center">
    <h1>Shaker | The Report</h1>
  </div>
  <hr>
  <div class="row">
    <div class="col-md-2">
      <label for="scenario_selector" title="Scenario name">Scenario</label>
      <select id="scenario_selector" class="form-control" data-placeholder="- Any scenario -"></select>
    </div>
    <div class="col-md-2">
      <label for="test_selector" title="Test name">Test Case</label>
      <select id="test_selector" class="form-control" data-placeholder="- Any test case -"></select>
    </div>
    <div class="col-md-2">
      <label for="concurrency_selector" title="Concurrency">Concurrency</label>
      <select id="concurrency_selector" class="form-control" data-placeholder="- Any concurrency -"></select>
    </div>
    <div class="col-md-2">
      <label for="node_selector" title="Host / Compute node">Host</label>
      <select id="node_selector" class="form-control" data-placeholder="- Any node -"></select>
    </div>
    <div class="col-md-2">
      <label for="agent_selector" title="Agent">Agent</label>
      <select id="agent_selector" class="form-control" data-placeholder="- Any agent -"></select>
    </div>
  </div>

  <div id="content_area" class="row">
    <!-- Container for the screen content -->
  </div>

  <div id="footer" class="row">&nbsp;</div>
</div>

<!-- TEMPLATES --------------------------------------------------------------->

<!-- Scenario File -->
<script id="scenario_source_template" type="text/x-handlebars-template">
  <h3>Scenario</h3>
  <pre>{{ data }}</pre>
</script>

<!-- Test Specification -->
<script id="test_specification_template" type="text/x-handlebars-template">
  <h3>Test Specification</h3>
  <pre>{{ data }}</pre>
</script>

<!-- Executor Result -->
<script id="executor_template" type="text/x-handlebars-template">
  <h3>Execution Summary</h3>
  {{#with record}}
  <div id="{{ chart_id }}"></div>
  {{#if is_status_ok}}<span class="label label-success"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span>success</span>{{/if}}
  {{#if is_status_lost}}<span class="label label-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span>lost</span>{{/if}}
  {{#if is_status_interrupted}}<span class="label label-warning"><span class="glyphicon glyphicon-stop" aria-hidden="true"></span>interrupted</span>{{/if}}
  {{#if is_status_error}}<span class="label label-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span>error</span>{{/if}}
  {{#if stats_yaml}}
    <h4>Stats</h4>
    <pre>{{ stats_yaml }}</pre>
  {{/if}}
  {{#if time_info}}
    <h4>Time Info</h4>
    <pre>{{ time_info }}</pre>
  {{/if}}
  <h4>Command</h4>
  <pre>{{ command_yaml }}</pre>
  {{#if stdout}}
    <h4>Stdout</h4>
    <pre>{{ stdout }}</pre>
  {{/if}}
  {{#if verbose}}
    <h4>Verbose Info</h4>
    <pre>{{ verbose }}</pre>
  {{/if}}
  {{#if stderr}}
    <h4>Stderr</h4>
    <pre>{{ stderr }}</pre>
  {{/if}}
  {{/with}}
</script>

<!-- Concurrency summary -->
<script id="concurrency_summary_template" type="text/x-handlebars-template">
  {{#with record}}
  <div id="{{ chart_id  }}"></div>
  <h3>Per-node chart</h3>
  <div id="{{ node_chart_id  }}"></div>
  <h3>Stats</h3>
  <pre>{{ stats_yaml }}</pre>
  {{/with}}
</script>

<!-- Test summary -->
<script id="test_summary_template" type="text/x-handlebars-template">
  {{#with record}}
  <div id="{{ chart_id   }}"></div>
  {{#if stats_yaml}}
    <h3>Stats</h3>
    <pre>{{ stats_yaml }}</pre>
  {{/if}}
  {{/with}}
</script>

<!-- Records Table -->
<script id="records_table_template" type="text/x-handlebars-template">
  <h3>Records</h3>
  <table class="table table-striped" id="{{ table_id  }}">
    <thead>
    <tr>
      <th>Scenario</th>
      <th>Test Case</th>
      <th>Concurrency</th>
      <th>Host / Compute Node</th>
      <th>Agent</th>
      <th>Status</th>
    </tr>
    </thead>
    <tbody>
    {{#records}}
    <tr>
      <td><a href="{{ scenario_url }}">{{ scenario }}</a></td>
      <td><a href="{{ test_url }}">{{ test }}</a></td>
      <td><a href="{{ concurrency_url }}">{{ concurrency }}</a></td>
      <td><a href="{{ node_url }}">{{ node }}</a></td>
      <td><a href="{{ agent_url }}">{{ agent }}</a></td>
      <td>
        {{#if is_status_ok }}
          <span class="label label-success">
            <span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
            success
          </span>
        {{/if}}
        {{#if is_status_lost }}
          <span class="label label-danger">
            <span class="glyphicon glyphicon-alert" aria-hidden="true"></span>
            lost
          </span>
        {{/if}}
        {{#if is_status_interrupted }}
          <span class="label label-warning">
            <span class="glyphicon glyphicon-stop" aria-hidden="true"></span>
            interrupted
          </span>
        {{/if}}
        {{#if is_status_error }}
          <span class="label label-danger">
            <span class="glyphicon glyphicon-stop" aria-hidden="true"></span>
            error
          </span>
        {{/if}}
      </td>
    </tr>
    {{/records}}
    </tbody>
  </table>
</script>

<!-- Agents List -->
<script id="agents_table_template" type="text/x-handlebars-template">
  <h3>Agents</h3>
  <table class="table table-striped" id="{{ table_id }}">
    <thead>
    <tr>
      <th>Agent</th>
      <th>Mode</th>
      <th>IP</th>
      <th>Node</th>
      <th>AZ</th>
    </tr>
    </thead>
    <tbody>
    {{#agents}}
    <tr>
      <td>{{ id }}</td>
      <td>{{ mode }}</td>
      <td>{{ ip }}</td>
      <td>{{ node }}</td>
      <td>{{ zone }}</td>
    </tr>
    {{/agents}}
    </tbody>
  </table>
</script>

<!-- SLA List -->
<script id="sla_table_template" type="text/x-handlebars-template">
  <h3>Agents</h3>
  <table class="table table-striped" id="{{  table_id }}">
    <thead>
    <tr>
      <th>Condition</th>
      <th>Location</th>
      <th>Status</th>
    </tr>
    </thead>
    <tbody>
    {{#records}}
    <tr>
      <td>{{ sla }}</td>
      <td>{{ location }}</td>
      <td>
        {{#if is_status_ok }}
          <span class="text-success">
            <span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
            {{ status }}
          </span>
        {{else}}
          <span class="text-danger">
            <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
            {{ status }}
          </span>
        {{/if}}
      </td>
    </tr>
    {{/records}}
    </tbody>
  </table>
</script>

<!-- Errors -->
<script id="errors_template" type="text/x-handlebars-template">
  <h3>Errors</h3>
  <div class="text-danger">
    <ul>
    {{#records}}
      <li>{{ info }}<br/>{{ stderr }}{{ traceback }}</li>
    {{/records}}
    </ul>
  </div>
</script>

</body>
</html>
