Skip to content

Commit

Permalink
Annotation line in graphs.
Browse files Browse the repository at this point in the history
Add UI for adding/updating annotation in build pages.
Add annotation lines in metrics with corresponding label.

Fixes #179.
  • Loading branch information
stevanradakovic committed Oct 18, 2018
1 parent 1b460a9 commit 1d5f6ac
Show file tree
Hide file tree
Showing 17 changed files with 232 additions and 20 deletions.
1 change: 1 addition & 0 deletions squad/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@
url(r'^data/(%s)/(%s)' % ((slug_pattern,) * 2), data.get),
url(r'^resubmit/([0-9]+)', ci.resubmit_job),
url(r'^forceresubmit/([0-9]+)', ci.force_resubmit_job),
url(r'^update-annotation/(%s)/(%s)/(%s)$' % ((slug_pattern,) * 3), views.update_annotation, name='update_annotation'),
]
19 changes: 19 additions & 0 deletions squad/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from squad.http import auth_write


from squad.core.models import Annotation
from squad.core.models import Group
from squad.core.models import Project
from squad.core.models import Build
Expand Down Expand Up @@ -107,3 +108,21 @@ def add_test_run(request, group_slug, project_slug, version, environment_slug):
return HttpResponse(str(e), status=400)

return HttpResponse('', status=201)


@csrf_exempt
@require_http_methods(["POST"])
@auth_write
def update_annotation(request, group_slug, project_slug, version):
group = Group.objects.get(slug=group_slug)
project = group.projects.get(slug=project_slug)
build = get_object_or_404(
project.builds.prefetch_related('test_runs'),
version=version,
)

annotation = Annotation.objects.get_or_create(build=build)[0]
annotation.description = request.POST.get("description", None)
annotation.save()

return HttpResponse('', status=201)
24 changes: 24 additions & 0 deletions squad/core/migrations/0092_annotation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.14 on 2018-10-09 09:31
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('core', '0091_notification_delivery_remove_unique_status'),
]

operations = [
migrations.CreateModel(
name='Annotation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('description', models.CharField(max_length=1024)),
('build', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='core.Build')),
],
),
]
8 changes: 8 additions & 0 deletions squad/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -968,3 +968,11 @@ def active_by_project_and_test(cls, project, test_name=None):
if test_name:
qs = qs.filter(test_name=test_name)
return qs.distinct()


class Annotation(models.Model):
description = models.CharField(max_length=1024)
build = models.OneToOneField(Build)

def __str__(self):
return '%s' % self.description
6 changes: 4 additions & 2 deletions squad/core/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ def get_metric_series(project, metric, environments):
'test_run__build__datetime',
'test_run__build__version',
'result',
'test_run__build__annotation__description',
)
entry[environment] = [
[int(p['test_run__build__datetime'].timestamp()), p['result'], p['test_run__build__version']] for p in series
[int(p['test_run__build__datetime'].timestamp()), p['result'], p['test_run__build__version'], "" if p['test_run__build__annotation__description'] is None else p['test_run__build__annotation__description']] for p in series
]
return entry

Expand All @@ -48,12 +49,13 @@ def get_tests_series(project, environments):
'test_run__build_id',
'test_run__build__datetime',
'test_run__build__version',
'test_run__build__annotation__description',
).annotate(
pass_percentage=100 * Sum('tests_pass') / Sum(tests_total)
).order_by('test_run__build__datetime')

results[environment] = [
[int(s['test_run__build__datetime'].timestamp()), s['pass_percentage'], s['test_run__build__version']]
[int(s['test_run__build__datetime'].timestamp()), s['pass_percentage'], s['test_run__build__version'], "" if s['test_run__build__annotation__description'] is None else s['test_run__build__annotation__description']]
for s in series
]
return results
1 change: 1 addition & 0 deletions squad/frontend/static/download.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ jquery.js https://code.jquery.com/jquery-3.2.1.js
floatThread https://github.com/mkoryak/floatThead/archive/2.0.3.zip d7112698ed5bf14f953fdef6252fdd9950af90bf
select2.js/select2.min.js https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/js/select2.min.js 42fe805a338908436c5c326dbf7e9aec0c8484c7
select2.js/select2.css https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/css/select2.css 03f47309050b0691af394398f394065e8dd38503
chartjs/chartjs-plugin-annotation.min.js https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-annotation/0.5.7/chartjs-plugin-annotation.min.js 27026bf430b2301317e0beaa65701151b7515e87
16 changes: 16 additions & 0 deletions squad/frontend/static/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,19 @@ table.test-results-details {
.range:hover {
cursor: grab;
}

.annotation-alert {
padding: 12px;
}

.annotation-text-col {
padding-top: 6px;
}

.annotation-description {
width: 100%;
}

.build-nav-header {
margin-top: 20px;
}
38 changes: 38 additions & 0 deletions squad/frontend/static/squad/annotation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
var app = angular.module('Annotation', []);

app.config(['$locationProvider', function($locationProvider) {
$locationProvider.html5Mode({
enabled: true,
requireBase: false
})
}])

function AnnotationController($scope, $http, $httpParamSerializerJQLike) {
$scope.updateAnnotation = function(group, project, version) {
$http({
method: "post",
url: '/api/update-annotation/' + group + "/" + project + "/" +
version,
data: $httpParamSerializerJQLike({
description: $scope.description
}),
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}).then(function(response) {
$("#annotation_text").html("<strong>Annotation: </strong>" +
$scope.description)
$("#annotation_text").removeClass("hidden")
$("#annotation_button").html("Update")
$("#annotation_modal").modal('hide')
})
}
}

app.controller(
'AnnotationController',
[
'$scope',
'$http',
'$httpParamSerializerJQLike',
AnnotationController
]
)
71 changes: 59 additions & 12 deletions squad/frontend/static/squad/charts.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ function ChartsController($scope, $http, $location, $compile) {
minDate = data[name][0][0]
}
})

this.minDate = minDate
}

Expand All @@ -41,6 +40,44 @@ function ChartsController($scope, $http, $location, $compile) {
this.maxDate = maxDate
}

ChartPanel.prototype.updateAnnotations = function(environmentIds, minLimit,
maxLimit) {
var data = this.data
var annotations = {}
var y_adjust = 0

_.each(environmentIds, function(name) {
_.each(data[name], function(elem) {
if (elem[3] != "") {
if (elem[0] < maxLimit && elem[0] > minLimit) {
// Offset the labels so they don't overlap
y_adjust += 25
if (y_adjust > 200) { y_adjust = 0 }

annotations[elem[0]] = {
type: "line",
mode: "vertical",
scaleID: "x-axis-0",
value: elem[0],
borderColor: "black",
borderWidth: 2,
label: {
backgroundColor: "#337ab7",
content: elem[3],
yAdjust: -100 + y_adjust,
enabled: true
},
}
}
}
})
})

this.scatterChart.options.annotation.annotations = Object.values(
annotations)
this.scatterChart.update()
}

ChartPanel.prototype.draw = function(target) {
var metric = this.metric
var data = this.data
Expand All @@ -51,16 +88,16 @@ function ChartsController($scope, $http, $location, $compile) {
return env.selected
})

if (typeof $scope.ranges[metric.name] !== 'undefined' && $scope.ranges[metric.name].length > 0) {
minCoefficient = $scope.ranges[metric.name][0] / 100
maxCoefficient = $scope.ranges[metric.name][1] / 100
}
var minLimit = minDate + Math.round((maxDate - minDate)*minCoefficient)
var maxLimit = minDate + Math.round((maxDate - minDate)*maxCoefficient)

var datasets = _.map(environments, function(env) {
var minCoefficient = $scope.slider.defaultResultsLimit
var maxCoefficient = 1
if (typeof $scope.ranges[metric.name] !== 'undefined' && $scope.ranges[metric.name].length > 0) {
minCoefficient = $scope.ranges[metric.name][0] / 100
maxCoefficient = $scope.ranges[metric.name][1] / 100
}

minLimit = minDate + Math.round((maxDate - minDate)*minCoefficient)
maxLimit = minDate + Math.round((maxDate - minDate)*maxCoefficient)

return {
label: env.name,
Expand Down Expand Up @@ -89,7 +126,7 @@ function ChartsController($scope, $http, $location, $compile) {
}
}

var scatterChart = new Chart(ctx, {
this.scatterChart = new Chart(ctx, {
type: 'line',
data: {
datasets: datasets
Expand Down Expand Up @@ -132,10 +169,16 @@ function ChartsController($scope, $http, $location, $compile) {
callback: formatDate
}
}]
},
annotation: {
drawTime: "afterDatasetsDraw",
annotations: [],
}
}
});

this.updateAnnotations($scope.getEnvironmentIds(), minLimit, maxLimit)

ctx.onclick = function(evt) {
var point = scatterChart.getElementAtEvent(evt)[0]
if (point) {
Expand All @@ -144,8 +187,6 @@ function ChartsController($scope, $http, $location, $compile) {
window.location.href= '/' + $scope.project + '/build/' + build + '/'
}
}

this.scatterChart = scatterChart
}

$scope.toggleEnvironments = function() {
Expand Down Expand Up @@ -256,7 +297,7 @@ function ChartsController($scope, $http, $location, $compile) {
metric.chart = chart
metric.drawn = true

var slider_container = "<slider-range metrics='selectedMetrics' ranges='ranges' filter-data='filter_data(data, minLimit, maxLimit)' update-url='updateURL()' value-min='" + min_value + "' value-max='" + max_value + "'></slider-range>"
var slider_container = "<slider-range metrics='selectedMetrics' ranges='ranges' filter-data='filter_data(data, minLimit, maxLimit)' update-url='updateURL()' get-environment-ids='getEnvironmentIds()' value-min='" + min_value + "' value-max='" + max_value + "'></slider-range>"
elem = $compile(slider_container)($scope)
$(target).append(elem)

Expand Down Expand Up @@ -454,6 +495,7 @@ app.directive('sliderRange', ['$document',function($document) {
ranges: "=ranges",
updateUrl: "&",
filterData: "&",
getEnvironmentIds: "&",
},
link: function postLink(scope, element, attrs) {
// Initilization
Expand Down Expand Up @@ -542,6 +584,11 @@ app.directive('sliderRange', ['$document',function($document) {
maxLimit: maxLimit
})
});
current.chart.updateAnnotations(
scope.getEnvironmentIds(),
minLimit,
maxLimit
)
current.chart.scatterChart.update()

// Update range.
Expand Down
49 changes: 48 additions & 1 deletion squad/frontend/templates/squad/build-nav.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,26 @@ <h2 class="page-header well">
» {% include "squad/_unfinished_build.html" %} <a class="h2 text-primary" href="{% build_url build %}">Build {{build.version}}</a>
</h2>

<ul class="page-header nav nav-pills">
<div class="highlight-row">
<div class="alert alert-info annotation-alert">
<div class="row">
<div id="annotation_text" class="col-md-11 annotation-text-col {% if not build.annotation %}hidden{% endif %}">
{% if build.annotation %}
<strong>Annotation:</strong>
{{ build.annotation.description }}
{% endif %}
</div>
<div class="col-md-1">
<a href="#" data-toggle="modal" data-target="#annotation_modal">
<button id="annotation_button" type="submit" class="btn btn-primary">{% if build.annotation %}Update{% else %}Add annotation{% endif %}</button>
</a>
</div>
</div>
</div>

</div>

<ul class="page-header nav nav-pills build-nav-header">
{% with url_name=request.resolver_match.url_name %}

<li role="presentation" {% if url_name == 'build' %}class="active"{% endif %}>
Expand Down Expand Up @@ -37,3 +56,31 @@ <h2 class="page-header well">

{% endwith %}
</ul>

<!-- Modal HTML -->
<div ng-app="Annotation" id="annotation_modal" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Build annotation</h4>
</div>
<form class="well" id="annotation_form" ng-controller="AnnotationController" ng-submit="updateAnnotation('{{ project.group.slug }}','{{ project.slug }}','{{ build.version }}')">
<div class="modal-body">
<div class="row">
<div class="col-md-2">
<label for="description">Description:</label>
</div>
<div class="col-md-10">
<input name="description" class="annotation-description" ng-model="description" placeholder="Annotation description" ng-init="description='{{ build.annotation.description }}'" />
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button class="btn btn-primary">Save</button>
</div>
</form>
</div>
</div>
</div>
1 change: 1 addition & 0 deletions squad/frontend/templates/squad/build.html
Original file line number Diff line number Diff line change
Expand Up @@ -198,4 +198,5 @@ <h2>Test runs</h2>

{% block javascript %}
<script type="text/javascript" src='{% static "squad/filter.js" %}'></script>
<script type="text/javascript" src='{% static "squad/annotation.js" %}'></script>
{% endblock %}
4 changes: 4 additions & 0 deletions squad/frontend/templates/squad/build_metadata.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@
<h2>Metadata</h2>
{% include "squad/_metadata.html" %}
{% endblock %}

{% block javascript %}
<script type="text/javascript" src='{% static "squad/annotation.js" %}'></script>
{% endblock %}
1 change: 1 addition & 0 deletions squad/frontend/templates/squad/metrics.html
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,5 @@
</script>
<script type="text/javascript" src="{% static "chartjs/Chart.bundle.js" %}"></script>
<script type="text/javascript" src='{% static "squad/charts.js" %}'></script>
<script type="text/javascript" src='{% static "chartjs/chartjs-plugin-annotation.min.js" %}'></script>
{% endblock %}
3 changes: 2 additions & 1 deletion squad/frontend/templates/squad/testjobs.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,6 @@ <h4>Definition</h4>
{% endblock %}

{% block javascript %}
<script type="text/javascript" src="{% static "squad/resubmit.js" %}"></script>
<script type="text/javascript" src="{% static "squad/resubmit.js" %}"></script>
<script type="text/javascript" src='{% static "squad/annotation.js" %}'></script>
{% endblock %}
1 change: 1 addition & 0 deletions squad/frontend/templates/squad/tests.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ <h2>All test results</h2>
{% block javascript %}
<script type="text/javascript" src='{% static "squad/filter.js" %}'></script>
<script type="text/javascript" src='{% static "squad/table.js" %}'></script>
<script type="text/javascript" src='{% static "squad/annotation.js" %}'></script>
{% endblock %}
Loading

0 comments on commit 1d5f6ac

Please sign in to comment.