diff --git a/rally/ui/templates/task/trends.html b/rally/ui/templates/task/trends.html new file mode 100644 index 0000000000..fe376752a8 --- /dev/null +++ b/rally/ui/templates/task/trends.html @@ -0,0 +1,422 @@ +{% extends "/base.html" %} + +{% block html_attr %} ng-app="App"{% endblock %} + +{% block title_text %}Rally Tasks Trends{% endblock %} + +{% block libs %} + {% if include_libs %} + + + {% else %} + + + + + {% endif %} +{% endblock %} + +{% block js_before %} + "use strict"; + {% include "/task/directive_widget.js" %} + var controllerFunction = function($scope, $location) { + $scope.data = {{ 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.wload_map) { return } + if (uri.path in $scope.wload_map) { + $scope.view = {is_wload:true}; + $scope.wload = $scope.wload_map[uri.path]; + $scope.nav_idx = $scope.nav_map[uri.path]; + $scope.showTab(uri); + } else { + $scope.wload = null; + $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: "total", + name: "Total", + visible: function(){ return true } + }, { + id: "atomic", + name: "Atomic actions", + visible: function(){ return (! $scope.wload.single) && $scope.wload.atomic.length } + }, { + id: "config", + name: "Configuration", + visible: function(){ return !! $scope.wload.config.length } + } + ]; + $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 : "total" + } + + for (var i in $scope.tabs) { + $scope.tabs[i].isVisible = function() { + if ($scope.wload) { + 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.data.length) { + return $scope.showError("No data...") + } + + /* Compose data mapping */ + + $scope.nav = []; + $scope.nav_map = {}; + $scope.wload_map = {}; + var prev_cls, prev_met, met = [], itr = 0, cls_idx = 0; + + for (var idx in $scope.data) { + var w = $scope.data[idx]; + if (! prev_cls) { + prev_cls = w.cls + } + else if (prev_cls !== w.cls) { + $scope.nav.push({name:prev_cls, met:met, idx:cls_idx}); + prev_cls = w.cls; + met = []; + itr = 1; + cls_idx += 1 + } + + if (prev_met !== w.met) { itr = 1 }; + w.ref = $scope.location.normalize(w.cls+"."+w.met+(itr > 1 ? "-"+itr : "")); + w.order_idx = itr > 1 ? " ["+itr+"]" : "" + $scope.wload_map[w.ref] = w; + $scope.nav_map[w.ref] = cls_idx; + met.push({name:w.met, itr:itr, idx:idx, order_idx:w.order_idx, + ref:w.ref, single:w.single}); + prev_met = w.met; + itr += 1; + } + + if (met.length) { + $scope.nav.push({name:prev_cls, met:met, idx:cls_idx}) + } + + /* Start */ + + $scope.route($scope.location.uri()); + $scope.$digest() + }); + }; + + if (typeof angular === "object") { + angular.module("App", []) + .controller("Controller", ["$scope", "$location", controllerFunction]) + .directive("widget", widgetDirective) + } +{% 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 } + .navmet.single, .single, .single td { color:#999 } + .navmet.active.single { color:#ccc } + + .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; width:890px } + .chart svg { height:300px; padding:0; margin:0; overflow:visible; float:right } + .chart.lower svg { height:180px } + .chart-label-y { font-size:12px; position:relative; top:5px; padding:0; margin:0 } + + .clearfix { clear:both } + .sortable > .arrow { display:inline-block; width:12px; height:inherit; color:#c90 } + .content-main { margin:0 5px; display:block; float:left } + .content-wrap { width:900px } + + .chart-title { color:#f60; font-size:20px; padding:8px 0 3px } +{% 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="Controller"{% endblock %} + +{% block header_text %}tasks trends report{% endblock %} + +{% block content %} +{% raw %} + + +
+
+ +
+
+ + +
+
+ +
+ + +
+

Trends overview

+ + + + + + + + + +
+ Scenario + + + + + + Number of runs + + + + + + Min duration + + + + + + Max duration + + + + + + Avg duration + + + + + + SLA + + + + +
{{w.ref}} + {{w.seq}} + + - + {{w.stat.min | number:4}} + + - + {{w.stat.max | number:4}} + + - + {{w.stat.avg | number:4}} + + + +
+
+ +
+
Compare workload runs
+

{{wload.cls}}.{{wload.met}}{{wload.order_idx}}

+ +
+ + + + + + +
+ +
+
+{% 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 %}