From 164f310938033c62b32242887b1898531ed976a1 Mon Sep 17 00:00:00 2001 From: Alexander Maretskiy Date: Tue, 29 Mar 2016 14:15:02 +0300 Subject: [PATCH] [Reports] Add Jinja2 template for upcoming Trends report This patch simply adds HTML template for upcoming Trends report, since this file is too big to be included in another patch. Next patch takes this template in use. Blueprint: trends-report Change-Id: If2e2fef7582dbe0a3d2745c112e5fcff9d16ecb5 --- rally/ui/templates/task/trends.html | 422 ++++++++++++++++++++++++++++ 1 file changed, 422 insertions(+) create mode 100644 rally/ui/templates/task/trends.html 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}}

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