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 26, 2018
1 parent 8ec3270 commit 8c1f21c
Show file tree
Hide file tree
Showing 16 changed files with 270 additions and 27 deletions.
20 changes: 19 additions & 1 deletion squad/api/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from django.contrib.auth.models import Group as UserGroup
from squad.api.filters import ComplexFilterBackend
from squad.core.models import Group, Project, ProjectStatus, Build, TestRun, Environment, Test, Metric, EmailTemplate, KnownIssue, PatchSource, Suite, SuiteMetadata
from squad.core.models import Annotation, Group, Project, ProjectStatus, Build, TestRun, Environment, Test, Metric, EmailTemplate, KnownIssue, PatchSource, Suite, SuiteMetadata
from squad.core.notification import Notification
from squad.ci.models import Backend, TestJob
from django.http import HttpResponse
Expand Down Expand Up @@ -816,6 +816,23 @@ class KnownIssueViewSet(viewsets.ModelViewSet):
ordering_fields = ('title', 'id')


class AnnotationSerializer(serializers.ModelSerializer):

id = serializers.IntegerField(read_only=True)

class Meta:
model = Annotation
fields = '__all__'


class AnnotationViewSet(viewsets.ModelViewSet):

queryset = Annotation.objects.all()
serializer_class = AnnotationSerializer
filter_fields = ('description', 'build')
ordering_fields = ('id', 'build')


router = APIRouter()
router.register(r'groups', GroupViewSet)
router.register(r'usergroups', UserGroupViewSet, 'usergroups')
Expand All @@ -831,3 +848,4 @@ class KnownIssueViewSet(viewsets.ModelViewSet):
router.register(r'knownissues', KnownIssueViewSet)
router.register(r'patchsources', PatchSourceViewSet)
router.register(r'suitemetadata', SuiteMetadataViewset)
router.register(r'annotations', AnnotationViewSet)
1 change: 1 addition & 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
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 @@ -971,3 +971,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'], p['test_run__build__annotation__description'] or ""] 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'], s['test_run__build__annotation__description'] or ""]
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;
}
54 changes: 54 additions & 0 deletions squad/frontend/static/squad/annotation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
var app = angular.module('Annotation', []);

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

function AnnotationController($scope, $http, $httpParamSerializerJQLike) {
$scope.updateAnnotation = function(build_id) {
var method = 'post'
var url = '/api/annotations/'
var data = {
description: $scope.description,
build: build_id
}
if (typeof $scope.annotation_id !== "undefined") {
method = "put"
url += $scope.annotation_id + "/"
data["id"] = $scope.annotation_id
}
$http({
method: method,
url: url,
data: $httpParamSerializerJQLike(data),
headers: {'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFTOKEN': csrf_token}
}).then(function(response) {
$("#annotation_text").html("<strong>Annotation: </strong>" +
$scope.description)
$("#annotation_text").removeClass("hidden")
$("#annotation_button").html("Update")
$("#annotation_modal").modal('hide')
$scope.annotation_id = response.data.id
}, function(response) {
var msg = "There was an error while editing annotation.\n" +
"Status = " + response.status + " " + response.statusText +
"(" + response.xhrStatus + ")"
alert(msg)
$("#annotation_modal").modal('hide')
})
}
}

app.controller(
'AnnotationController',
[
'$scope',
'$http',
'$httpParamSerializerJQLike',
AnnotationController
]
)
87 changes: 69 additions & 18 deletions squad/frontend/static/squad/charts.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ function ChartsController($scope, $http, $location, $compile) {
var minDate = Math.round(new Date() / 1000)
var data = this.data
_.each(environmentIds, function(name) {
if (data[name][0][0] < minDate) {
minDate = data[name][0][0]
if (typeof data[name] !== "undefined" && data[name].length > 0) {
if (data[name][0][0] < minDate) {
minDate = data[name][0][0]
}
}
})

this.minDate = minDate
}

Expand All @@ -34,13 +35,53 @@ function ChartsController($scope, $http, $location, $compile) {
var maxDate = 0
var data = this.data
_.each(environmentIds, function(name) {
if (data[name][data[name].length-1][0] > maxDate) {
maxDate = data[name][data[name].length-1][0]
if (typeof data[name] !== "undefined" && data[name].length > 0) {
if (data[name][data[name].length-1][0] > maxDate) {
maxDate = data[name][data[name].length-1][0]
}
}
})
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,17 +92,16 @@ function ChartsController($scope, $http, $location, $compile) {
return env.selected
})

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)
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
}
var minLimit = minDate + Math.round((maxDate - minDate)*minCoefficient)
var maxLimit = minDate + Math.round((maxDate - minDate)*maxCoefficient)

var datasets = _.map(environments, function(env) {
return {
label: env.name,
fill: false,
Expand Down Expand Up @@ -132,9 +172,16 @@ function ChartsController($scope, $http, $location, $compile) {
callback: formatDate
}
}]
},
annotation: {
drawTime: "afterDatasetsDraw",
annotations: [],
}
}
});
this.scatterChart = scatterChart

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

ctx.onclick = function(evt) {
var point = scatterChart.getElementAtEvent(evt)[0]
Expand All @@ -144,8 +191,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 +301,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 +499,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 +588,11 @@ app.directive('sliderRange', ['$document',function($document) {
maxLimit: maxLimit
})
});
current.chart.updateAnnotations(
scope.getEnvironmentIds(),
minLimit,
maxLimit
)
current.chart.scatterChart.update()

// Update range.
Expand Down
Loading

0 comments on commit 8c1f21c

Please sign in to comment.