aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSean Smith <ssmith@iol.unh.edu>2021-04-06 15:18:06 -0400
committerSean Smith <ssmith@iol.unh.edu>2021-05-03 11:40:07 -0400
commit8269a6743c14cab1ca4105651255e6f908ee195c (patch)
tree7eabea6597cb232114cd84ae3b9d91f8d1ab43bf
parentf1456220fcc098cb0f5e9fc60124680ff8aba6af (diff)
Analytics Board
Signed-off-by: Sean Smith <ssmith@iol.unh.edu> Change-Id: Id942628ff04cd2f3808f8608ac45989360717f34
-rw-r--r--src/booking/stats.py54
-rw-r--r--src/booking/urls.py2
-rw-r--r--src/booking/views.py2
-rw-r--r--src/templates/base/booking/stats.html338
-rw-r--r--src/templates/base/layout.html6
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 %}