{% extends "/base.html" %} {% block html_attr %} ng-app="TaskApp"{% endblock %} {% block title_text %}Rally Task Report{% endblock %} {% block libs %} {% if include_libs %} {% else %} {% endif %} {% endblock %} {% block js_before %} {% raw %} "use strict"; if (typeof angular === "object") { angular.module("TaskApp", []).controller( "TaskController", ["$scope", "$location", function($scope, $location) { {% endraw %} $scope.source = {{ source }}; $scope.scenarios = {{ data }}; {% raw %} $scope.location = { /* #/path/hash/sub/div */ normalize: function(str) { /* Remove unwanted characters from string */ if (typeof str !== "string") { return "" } return str.replace(/[^\w\-\.]/g, "") }, uri: function(obj) { /* Getter/Setter */ if (! obj) { var uri = {path: "", hash: "", sub: "", div: ""}; var arr = ["div", "sub", "hash", "path"]; angular.forEach($location.url().split("/"), function(value){ var v = $scope.location.normalize(value); if (v) { var k = arr.pop(); if (k) { this[k] = v }} }, uri); return uri } var arr = [obj.path, obj.hash, obj.sub, obj.div], res = []; for (var i in arr) { if (! arr[i]) { break }; res.push(arr[i]) } return $location.url("/" + res.join("/")) }, path: function(path, hash) { /* Getter/Setter */ if (path === "") { return this.uri({}) } path = this.normalize(path); var uri = this.uri(); if (! path) { return uri.path } uri.path = path; var _hash = this.normalize(hash); if (_hash || hash === "") { uri.hash = _hash } return this.uri(uri) }, hash: function(hash) { /* Getter/Setter */ if (hash) { this.uri({path:this.uri().path, hash:hash}) } return this.uri().hash } } /* Dispatch */ $scope.route = function(uri) { if (! $scope.scenarios_map) { return } if (uri.path in $scope.scenarios_map) { $scope.view = {is_scenario:true}; $scope.scenario = $scope.scenarios_map[uri.path]; $scope.nav_idx = $scope.nav_map[uri.path]; if ($scope.scenario.iterations.histogram.views.length) { $scope.mainHistogram = $scope.scenario.iterations.histogram.views[0] } if ($scope.scenario.atomic.histogram.views.length) { $scope.atomicHistogram = $scope.scenario.atomic.histogram.views[0] } $scope.outputIteration = 0; $scope.showTab(uri); } else { $scope.scenario = null; if (uri.path === "source") { $scope.view = {is_source:true} } else { $scope.view = {is_main:true} } } } $scope.$on("$locationChangeSuccess", function (event, newUrl, oldUrl) { $scope.route($scope.location.uri()) }); $scope.showNav = function(nav_idx) { $scope.nav_idx = nav_idx } /* Tabs */ $scope.tabs = [ { id: "overview", name: "Overview", visible: function(){ return !! $scope.scenario.iterations.pie.length } },{ id: "details", name: "Details", visible: function(){ return !! $scope.scenario.atomic.pie.length } },{ id: "output", name: "Scenario Data", visible: function(){ return $scope.scenario.output.length } },{ id: "failures", name: "Failures", visible: function(){ return !! $scope.scenario.errors.length } },{ id: "task", name: "Input task", visible: function(){ return !! $scope.scenario.config } } ]; $scope.tabs_map = {}; angular.forEach($scope.tabs, function(tab){ this[tab.id] = tab }, $scope.tabs_map); $scope.showTab = function(uri) { $scope.tab = uri.hash in $scope.tabs_map ? uri.hash : "overview"; if (! $scope.scenario.output) { var has_additive = !! $scope.scenario.additive_output.length; var has_complete = !! ($scope.scenario.complete_output.length && $scope.scenario.complete_output[0].length); $scope.scenario.output = { has_additive: has_additive, has_complete: has_complete, length: has_additive + has_complete, active: has_additive ? "additive" : (has_complete ? "complete" : "") } } if (uri.hash === "output") { if (uri.sub && $scope.scenario.output["has_" + uri.sub]) { $scope.scenario.output.active = uri.sub } } } for (var i in $scope.tabs) { if ($scope.tabs[i].id === $scope.location.hash()) { $scope.tab = $scope.tabs[i].id } $scope.tabs[i].isVisible = function() { if ($scope.scenario) { if (this.visible()) { return true } /* If tab should be hidden but is selected - show another one */ if (this.id === $scope.location.hash()) { for (var i in $scope.tabs) { var tab = $scope.tabs[i]; if (tab.id != this.id && tab.visible()) { $scope.tab = tab.id; return false } } } } return false } } $scope.showError = function(message) { return (function (e) { e.style.display = "block"; e.textContent = message })(document.getElementById("page-error")) } /* Initialization */ angular.element(document).ready(function(){ if (! $scope.scenarios.length) { return $scope.showError("No data...") } /* Compose data mapping */ $scope.nav = []; $scope.nav_map = {}; $scope.scenarios_map = {}; var met = [], itr = 0, cls_idx = 0; var prev_cls, prev_met; for (var idx in $scope.scenarios) { var sc = $scope.scenarios[idx]; if (! prev_cls) { prev_cls = sc.cls } else if (prev_cls !== sc.cls) { $scope.nav.push({cls:prev_cls, met:met, idx:cls_idx}); prev_cls = sc.cls; met = []; itr = 1; cls_idx += 1 } if (prev_met !== sc.met) { itr = 1 }; sc.ref = $scope.location.normalize(sc.cls+"."+sc.met+(itr > 1 ? "-"+itr : "")); $scope.scenarios_map[sc.ref] = sc; $scope.nav_map[sc.ref] = cls_idx; met.push({name:sc.name, itr:itr, idx:idx, ref:sc.ref}); prev_met = sc.met; itr += 1; } if (met.length) { $scope.nav.push({cls:prev_cls, met:met, idx:cls_idx}) } /* Start */ var uri = $scope.location.uri(); uri.path = $scope.location.path(); $scope.route(uri); $scope.$digest() }); }]) .directive("widget", function($compile) { var Chart = { _render: function(node, data, chart){ nv.addGraph(function() { d3.select(node).datum(data).transition().duration(0).call(chart); nv.utils.windowResize(chart.update) }) }, /* NOTE(amaretskiy): this is actually a result of d3.scale.category20().range(), excluding red color (#d62728) which is reserved for errors */ _colors: ["#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5"], _widgets: { Pie: "pie", StackedArea: "stack", 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) { 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 data_ = [], colors = [], colors_map = {errors: "#d62728"}; for (var i in data) { var key = data[i][0]; if (! (key in colors_map)) { if (! colors.length) { colors = Chart._colors.slice() } colors_map[key] = colors.shift() } data_.push({key:key, values:data[i][1], color:colors_map[key]}) } Chart._render(node, data_, chart) }, stack: function(node, data, opts) { 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 .tickFormat(d3.format(opts.xformat || "d")) .axisLabel(opts.xname || "") .showMaxMin(false); chart.yAxis .tickFormat(d3.format(opts.yformat || ",.3f")) .axisLabel(opts.yname || ""); var data_ = []; for (var i in data) { var d = {key:data[i][0], values:data[i][1]}; if (d.key === "failed_duration") { d.color = "#d62728" } data_.push(d) } Chart._render(node, data_, chart) }, histogram: function(node, data) { 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 ng_class = attrs.lastrowClass ? " ng-class='{"+attrs.lastrowClass+":$last}'" : ""; var template = "" + "" + "" + "" + "" + "
{{i}}
{{i}}" + "
"; var el = element.empty().append($compile(template)(scope)).children()[0] } else { /* Hide widget if not enough data */ if (attrs.widget === "StackedArea") { if ((! data.length) || (data[0].length < 1) || (data[0][1].length < 2)) { return element.empty().css({display:"none"}) } } else if (attrs.widget === "Pie") { if (! data.length) { return element.empty().css({display:"none"}) } } var options = { xname: attrs.nameX || "", yname: attrs.nameY || "", xformat: attrs.formatX || "d", yformat: attrs.formatY || ",.3f", controls: attrs.controls === "true", guide: attrs.guide === "true" }; var el = element.addClass("chart").css({display:"block"}).html("").children()[0]; Chart.get_chart(attrs.widget)(el, data, options) } if (attrs.description) { var desc_el = angular.element("
").addClass(attrs.descriptionClass || "h3").text(attrs.description); angular.element(el).parent().prepend(desc_el) } if (attrs.title) { var title_el = angular.element("
").addClass(attrs.titleClass || "h2").text(attrs.title); angular.element(el).parent().prepend(title_el) } }); } } }) } {% endraw %} {% endblock %} {% block css %} .aside { margin:0 20px 0 0; display:block; width:255px; float:left } .aside > div { margin-bottom: 15px } .aside > div div:first-child { border-top-left-radius:4px; border-top-right-radius:4px } .aside > div div:last-child { border-bottom-left-radius:4px; border-bottom-right-radius:4px } .navcls { color:#678; background:#eee; border:1px solid #ddd; margin-bottom:-1px; display:block; padding:8px 9px; font-weight:bold; text-align:left; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; cursor:pointer } .navcls.expanded { color:#469 } .navcls.active { background:#428bca; background-image:linear-gradient(to bottom, #428bca 0px, #3278b3 100%); border-color:#3278b3; color:#fff } .navmet { color:#555; background:#fff; border:1px solid #ddd; font-size:12px; display:block; margin-bottom:-1px; padding:8px 10px; text-align:left; text-overflow:ellipsis; white-space:nowrap; overflow:hidden; cursor:pointer } .navmet:hover { background:#f8f8f8 } .navmet.active, .navmet.active:hover { background:#428bca; background-image:linear-gradient(to bottom, #428bca 0px, #3278b3 100%); border-color:#3278b3; color:#fff } .tabs { list-style:outside none none; margin:0 0 5px; padding:0; border-bottom:1px solid #ddd } .tabs:after { clear:both } .tabs li { float:left; margin-bottom:-1px; display:block; position:relative } .tabs li div { border:1px solid transparent; border-radius:4px 4px 0 0; line-height:20px; margin-right:2px; padding:10px 15px; color:#428bca } .tabs li div:hover { border-color:#eee #eee #ddd; background:#eee; cursor:pointer; } .tabs li.active div { background:#fff; border-color:#ddd #ddd transparent; border-style:solid; border-width:1px; color:#555; cursor:default } .failure-mesg { color:#900 } .failure-trace { color:#333; white-space:pre; overflow:auto } .link { color:#428BCA; padding:5px 15px 5px 5px; text-decoration:underline; cursor:pointer } .link.active { color:#333; text-decoration:none } .chart { padding:0; margin:0 } .chart svg { height:300px; padding:0; margin:0 } .chart.lower svg { height:180px } .expandable { cursor:pointer } .clearfix { clear:both } .sortable > .arrow { display:inline-block; width:12px; height:inherit; color:#c90 } .content-main { margin:0 5px; display:block; float:left } {% endblock %} {% block media_queries %} @media only screen and (min-width: 320px) { .content-wrap { width:900px } .content-main { width:600px } } @media only screen and (min-width: 900px) { .content-wrap { width:880px } .content-main { width:590px } } @media only screen and (min-width: 1000px) { .content-wrap { width:980px } .content-main { width:690px } } @media only screen and (min-width: 1100px) { .content-wrap { width:1080px } .content-main { width:790px } } @media only screen and (min-width: 1200px) { .content-wrap { width:1180px } .content-main { width:890px } } {% endblock %} {% block body_attr %} ng-controller="TaskController"{% endblock %} {% block header_text %}task results{% endblock %} {% block content %} {% raw %}

Task overview

Scenario Load duration (s) Full duration (s) Iterations Runner Errors Success (SLA)
{{sc.ref}} {{sc.load_duration | number:3}} {{sc.full_duration | number:3}} {{sc.iterations_count}} {{sc.runner}} {{sc.errors.length}}

Input file

{{source}}

{{scenario.cls}}.{{scenario.name}} ({{scenario.full_duration | number:3}}s)

  • {{t.name}}
{% endraw %} {% endblock %} {% block js_after %} if (! window.angular) {(function(f){ f(document.getElementById("content-nav"), "none"); f(document.getElementById("content-main"), "none"); f(document.getElementById("page-error"), "block").textContent = "Failed to load AngularJS framework" })(function(e, s){e.style.display = s; return e})} {% endblock %}