diff options
author | Sawyer Bergeron <sbergeron@iol.unh.edu> | 2021-05-10 20:37:45 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@opnfv.org> | 2021-05-10 20:37:45 +0000 |
commit | 8086a7aa9aa95d5af341b67cba85b1377a168b98 (patch) | |
tree | 7123bb2b49dfaa8cab971274d34c5e7761b6b1bd | |
parent | 5b32eb4985460ff2e52fdaa89d5b7c94294a61dd (diff) | |
parent | 8269a6743c14cab1ca4105651255e6f908ee195c (diff) |
Merge "Analytics Board"
-rw-r--r-- | src/booking/stats.py | 54 | ||||
-rw-r--r-- | src/booking/urls.py | 2 | ||||
-rw-r--r-- | src/booking/views.py | 2 | ||||
-rw-r--r-- | src/templates/base/booking/stats.html | 338 | ||||
-rw-r--r-- | src/templates/base/layout.html | 6 |
5 files changed, 330 insertions, 72 deletions
diff --git a/src/booking/stats.py b/src/booking/stats.py index bdb478a..626ed79 100644 --- a/src/booking/stats.py +++ b/src/booking/stats.py @@ -6,8 +6,11 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## +import os from booking.models import Booking +from resource_inventory.models import ResourceQuery, ResourceProfile from datetime import datetime, timedelta +from collections import Counter import pytz @@ -23,9 +26,28 @@ class StatisticsManager(object): y values are the integer number of bookings/users active at time """ + anuket_colors = [ + '#6BDAD5', # Turquoise + '#E36386', # Pale Violet Red + '#F5B335', # Sandy Brown + '#007473', # Teal + '#BCE194', # Gainsboro + '#00CE7C', # Sea Green + ] + + lfedge_colors = [ + '#0049B0', + '#B481A5', + '#6CAFE4', + '#D33668', + '#28245A' + ] + x = [] y = [] users = [] + projects = [] + profiles = {str(profile): [] for profile in ResourceProfile.objects.all()} now = datetime.now(pytz.utc) delta = timedelta(days=span) @@ -49,10 +71,38 @@ class StatisticsManager(object): for booking in books: active_users += booking.collaborators.all().count() + 1 - x.append(str(start)) + x.append(str(start.month) + '-' + str(start.day)) y.append(books.count()) + + step_profiles = Counter([ + str(config.profile) + for book in books + for config in book.resource.template.getConfigs() + ]) + + for profile in ResourceProfile.objects.all(): + profiles[str(profile)].append(step_profiles[str(profile)]) users.append(active_users) start += timedelta(hours=12) - return {"booking": [x, y], "user": [x, users]} + in_use = len(ResourceQuery.filter(working=True, booked=True)) + not_in_use = len(ResourceQuery.filter(working=True, booked=False)) + maintenance = len(ResourceQuery.filter(working=False)) + + projects = [x.project for x in bookings] + proj_count = sorted(Counter(projects).items(), key=lambda x: x[1]) + + project_keys = [proj[0] for proj in proj_count[-5:]] + project_counts = [proj[1] for proj in proj_count[-5:]] + + resources = {key: [x, value] for key, value in profiles.items()} + + return { + "resources": resources, + "booking": [x, y], + "user": [x, users], + "utils": [in_use, not_in_use, maintenance], + "projects": [project_keys, project_counts], + "colors": anuket_colors if os.environ['TEMPLATE_OVERRIDE_DIR'] == 'laas' else lfedge_colors + } diff --git a/src/booking/urls.py b/src/booking/urls.py index d5287e9..cdf18ae 100644 --- a/src/booking/urls.py +++ b/src/booking/urls.py @@ -40,8 +40,6 @@ from booking.views import ( app_name = "booking" urlpatterns = [ - - url(r'^detail/(?P<booking_id>[0-9]+)/$', booking_detail_view, name='detail'), url(r'^(?P<booking_id>[0-9]+)/$', booking_detail_view, name='booking_detail'), url(r'^delete/$', BookingDeleteView.as_view(), name='delete_prefix'), diff --git a/src/booking/views.py b/src/booking/views.py index 66cb594..2b910e7 100644 --- a/src/booking/views.py +++ b/src/booking/views.py @@ -195,7 +195,7 @@ def booking_stats_view(request): return render( request, "booking/stats.html", - context={"data": StatisticsManager.getContinuousBookingTimeSeries(), "title": "Booking Statistics"} + context={"data": StatisticsManager.getContinuousBookingTimeSeries(), "title": ""} ) diff --git a/src/templates/base/booking/stats.html b/src/templates/base/booking/stats.html index 4c06b71..3429acf 100644 --- a/src/templates/base/booking/stats.html +++ b/src/templates/base/booking/stats.html @@ -1,78 +1,288 @@ {% extends "base.html" %} {% load staticfiles %} -{% block extrahead %} -{{ block.super }} -<script src="{% static "node_modules/plotly.js-dist/plotly.js" %}"></script> +{% block content %} +<div class="row"> + <div class="col-lg-12"> + <div class="card"> + <div class="card-header no-border-bottom"> + <h2 class="card-title">Booking Statistics</h2> + </div> + <div class="card-content"> + <div class="card-body"> + <div class="row justify-content-md-center"> + <div class="col-lg-4"> + <div class="container"> + <canvas id="util-gauge"></canvas> + </div> + </div> + <div class="col-4 border-left border-right"> + <div class="container"> + <canvas id="resources-time-series"></canvas> + </div> + </div> + <div class="col-lg-4"> + <div class="container"> + <canvas id="project-chart"></canvas> + </div> + </div> + </div> + <div class="row border-top"> + <div class="col-6"> + <div class="container"> + <canvas id="booking-time-series"></canvas> + </div> + </div> + <div class="col-6"> + <div class="container"> + <canvas id="users-time-series"></canvas> + </div> + </div> + </div> + </div> + </div> + </div> + </div> +</div> + +<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script> <script> -function drawGraph(data, graph_id, graph_title){ - var container = document.getElementById(graph_id); - var plot_data = { x: data[0], - y: data[1], - line: {shape: "hv"}, - type: "scatter", - mode: "lines+makers", + function processTimeSeriesData(data_dict, colors) { + let output = []; + let i = 0; + + for (let key in data_dict) { + output.push({ + label: key, + data: data_dict[key][1], + steppedLine: true, + fill: false, + backgroundColor: colors[i], + borderColor: colors[i] + }); + + i += 1; + } + + return output + } + + let booking_chart = document.getElementById('booking-time-series').getContext('2d'); + let users_chart = document.getElementById('users-time-series').getContext('2d'); + let util_chart = document.getElementById('util-gauge').getContext('2d'); + let project_chart = document.getElementById('project-chart').getContext('2d'); + let resources_chart = document.getElementById('resources-time-series').getContext('2d'); + + let data = {{data | safe}}; + let booking = data['booking']; + let users = data['user']; + let projects = data['projects']; + let colors = data['colors']; + + let primary_color = colors[0]; + let secondary_color = colors[1]; + let accent_color = colors[2]; + + let booking_config = { + type: 'line', + data: { + labels: booking[0], + datasets: [{ + label: 'Bookings', + data: booking[1], + steppedLine: true, + fill: false, + backgroundColor: primary_color, + borderColor: primary_color + }] + }, + options: { + responsive: true, + interaction: { + intersect: false, + axis: 'x' + }, + title: { + display: true, + text: 'Active Bookings' + }, + legend: { + display: true + }, + elements: { + point: { + radius: 0 + } + } + } }; - var layout = { - title: graph_title, - yaxis: { - rangemode: 'tozero', - autorange: true + + let resources_config = { + type: 'line', + data: { + labels: booking[0], + datasets: processTimeSeriesData(data['resources'], colors) + }, + options: { + responsive: true, + interaction: { + intersect: false, + axis: 'x' + }, + title: { + display: true, + text: 'Booked Resources' + }, + legend: { + display: true + }, + transitions: { + show: { + animations: { + x: { + from: 100 + }, + y: { + from: 1 + } + } + }, + hide: { + animations: { + x: { + to: 0 + }, + y: { + to: 100 + } + } + } + }, + elements: { + point: { + radius: 0 + } + } } }; - Plotly.newPlot(container, [plot_data], layout); -} -function getData() { - var req = new XMLHttpRequest(); - var url = "/booking/stats/json"; - var day_input = document.getElementById("number_days"); - var days = day_input.value; - //var days = document.getElementById("number_days").value; - if(days){ - url = url + "?days=" + days; - } - req.onreadystatechange = function(){ - if( req.readyState == XMLHttpRequest.DONE) { - var data = JSON.parse(req.responseText); - drawGraph(data["booking"], "booking_graph_container", "Active Bookings"); - drawGraph(data["user"], "user_graph_container", "Active Users"); + let users_config = { + type: 'line', + data: { + labels: users[0], + datasets: [{ + label: 'Users', + data: users[1], + fill: false, + steppedLine: true, + backgroundColor: primary_color, + borderColor: primary_color + }] + }, + options: { + responsive: true, + interaction: { + intersect: false, + axis: 'x' + }, + legend: { + display: true + }, + title: { + display: true, + text: 'Active Users' + }, + elements: { + point: { + radius: 0 + } + } } - } - req.open("GET", url, true); - req.send(); -} + }; -</script> -{% endblock %} + let utilization_config = { + type:"doughnut", + data: { + labels : ["In Use","Not In Use","Maitenance"], + datasets: [{ + label: 'Lab Utilization', + data : [data['utils'][0], data['utils'][1], data['utils'][2]], + backgroundColor: [ + primary_color, + secondary_color, + accent_color, + ] + }] + }, + options: { + circumference: Math.PI, + rotation : Math.PI, + cutoutPercentage : 80, + plugins: { + datalabels: { + backgroundColor: primary_color, + borderColor: secondary_color, + align: 'start', + anchor: 'start', + offset: 10, + borderRadius: 4, + borderWidth: 1, + } + }, + legend: { + display: false + }, + tooltips: { + enabled: true + }, + title: { + display: true, + text: "Lab Resources Utilization" + } + } + }; -{% block content %} -<div class="row"> - <div class="col-auto"> - <p>Number of days to plot: </p> - <div class="form-group d-flex align-content-center"> - <input id="number_days" type="number" class="form-control d-inline-block w-auto" min="1" step="1"/> - <button class="btn btn-primary ml-1" onclick="getData();">Submit</button> - </div> - </div> -</div> -<div class="row"> - <div class="col-12 col-md-10"> - <!-- These graphs do NOT get redrawn when the browser size is changed --> - <div id="all_graph_container border" class="mw-100"> - <div id="booking_graph_wrapper"> - <div id="booking_graph_container"/> - </div> - <div id="user_graph_wrapper"> - <div id="user_graph_container"/> - </div> - </div> - </div> -</div> -<script> -var data = {{data|safe}}; -drawGraph(data["booking"], "booking_graph_container", "Active Bookings"); -drawGraph(data["user"], "user_graph_container", "Active Users"); + let project_config = { + type: 'bar', + data: { + labels: projects[0], + datasets:[{ + label: 'Projects', + data: projects[1], + backgroundColor: primary_color, + borderColor: secondary_color + }] + }, + options: { + scales: { + yAxes: [{ + ticks: { + beginAtZero: true + } + }] + }, + elements: { + bar: { + borderWidth: 2, + } + }, + responsive: true, + legend: { + display: false, + position: 'right' + }, + title: { + display: true, + text: 'Top Represented Projects' + } + } + }; + + let bookingChart = new Chart(booking_chart, booking_config); + let usersChart = new Chart(users_chart, users_config); + let utilGauge = new Chart(util_chart, utilization_config); + let projectBars = new Chart(project_chart, project_config); + let resourceChart = new Chart(resources_chart, resources_config); </script> {% endblock content %} diff --git a/src/templates/base/layout.html b/src/templates/base/layout.html index edf9b6b..2132dc6 100644 --- a/src/templates/base/layout.html +++ b/src/templates/base/layout.html @@ -14,6 +14,9 @@ <title>OPNFV Laas {{ title }}</title> {% endblock head-title %} + <!-- jQuery --> + <script src="{% static "node_modules/jquery/dist/jquery.min.js" %}"></script> + <!-- Bootstrap Core CSS --> <link href="{% static "node_modules/bootstrap/dist/css/bootstrap.min.css" %}" rel="stylesheet"> @@ -27,9 +30,6 @@ <!-- Favicon --> <link rel="shortcut icon" href="{% static 'favicon.ico' %}"> - <!-- jQuery --> - <script src="{% static "node_modules/jquery/dist/jquery.min.js" %}"></script> - {% block extrahead %} {% endblock extrahead %} |