Skip to content

Commit

Permalink
Add TestingExecutionTrendsView
Browse files Browse the repository at this point in the history
  • Loading branch information
asankov authored and atodorov committed Jul 16, 2019
1 parent c4beec6 commit 8ea00c2
Show file tree
Hide file tree
Showing 9 changed files with 282 additions and 3 deletions.
3 changes: 2 additions & 1 deletion tcms/settings/common.py
Expand Up @@ -105,7 +105,8 @@
(_('TELEMETRY'), [
(_('Testing'), [
(_('Breakdown'), reverse_lazy('testing-breakdown')),
(_('Status matrix'), reverse_lazy('testing-status-matrix'))
(_('Status matrix'), reverse_lazy('testing-status-matrix')),
(_('Execution trends'), reverse_lazy('testing-execution-trends')),
]),
('More coming soon',
'http://kiwitcms.org/blog/kiwi-tcms-team/2019/03/03/legacy-reports-become-telemetry/'),
Expand Down
63 changes: 62 additions & 1 deletion tcms/telemetry/api.py
Expand Up @@ -3,7 +3,7 @@
from modernrpc.core import rpc_method

from tcms.testcases.models import TestCase, TestCaseStatus
from tcms.testruns.models import TestExecution
from tcms.testruns.models import TestExecution, TestExecutionStatus


@rpc_method(name='Testing.breakdown')
Expand Down Expand Up @@ -115,3 +115,64 @@ def status_matrix(query=None):
'data': data_set,
'columns': columns
}


@rpc_method(name='Testing.execution_trends')
def execution_trends(query=None):

if query is None:
query = {}

data_set = {}
all_count = {}
categories = []
colors = []

count = {}

for status in TestExecutionStatus.objects.filter():
data_set[status.color_code()] = []
all_count[status.color_code()] = 0

color = status.color()
if color not in colors:
colors.append(color)

run_id = 0
for test_execution in TestExecution.objects.filter(**query).order_by('run_id'):
status = test_execution.status.color_code()

all_count[status] += 1

if test_execution.run_id == run_id:

if status in count:
count[status] += 1
else:
count[status] = 1

else:
_append_status_counts_to_result(count, data_set)

count = {}
run_id = test_execution.run_id
categories.append(run_id)

# append the last result
_append_status_counts_to_result(count, data_set)

for _key, value in data_set.items():
del value[0]

return {
'all_count': all_count,
'categories': categories,
'data_set': data_set,
'colors': colors
}


def _append_status_counts_to_result(count, result):
for status in TestExecutionStatus.chart_status_names:
status_count = count.get(status, 0)
result.get(status).append(status_count)
1 change: 1 addition & 0 deletions tcms/telemetry/static/telemetry/js/testing/breakdown.js
Expand Up @@ -142,6 +142,7 @@ function drawChart(data, type, selector) {
c3.generate(chartConfig);
}

// TODO: remove this in favour of the one in utils.js
function showOnlyRoundNumbers(number) {
if (number % 1 === 0) {
return number;
Expand Down
97 changes: 97 additions & 0 deletions tcms/telemetry/static/telemetry/js/testing/execution-trends.js
@@ -0,0 +1,97 @@
$(document).ready(() => {
loadInitialProduct();
loadInitialTestPlans();

document.getElementById('id_product').onchange = () => {
update_version_select_from_product();
update_build_select_from_product(true);
updateTestPlanSelectFromProduct(drawChart);
};

document.getElementById('id_version').onchange = drawChart;
document.getElementById('id_build').onchange = drawChart;
document.getElementById('id_test_plan').onchange = drawChart;

$('#id_after').on('dp.change', drawChart);
$('#id_before').on('dp.change', drawChart);

drawChart();
});

function drawChart() {
const query = {};

const productId = $('#id_product').val();
if (productId) {
query['case__plan__product'] = productId;
}

const versionId = $('#id_version').val();
if (versionId) {
query['case__plan__product_version'] = versionId;
}

const buildId = $('#id_build').val();
if (buildId) {
query['build_id'] = buildId;
}

const testPlanId = $('#id_test_plan').val();
if (testPlanId) {
query['case__plan__plan_id'] = testPlanId;
}

const dateBefore = $('#id_before');
if (dateBefore.val()) {
query['close_date__lte'] = dateBefore.data('DateTimePicker').date().format('YYYY-MM-DD 23:59:59');
}

const dateAfter = $('#id_after');
if (dateAfter.val()) {
query['close_date__gte'] = dateAfter.data('DateTimePicker').date().format('YYYY-MM-DD 00:00:00');
}

jsonRPC('Testing.execution_trends', query, data => {
const chartData = [];

Object.entries(data.data_set).forEach(entry => {
chartData.push([entry[0], ...entry[1]]);
});

$('#chart > svg').remove();

const c3ChartDefaults = $().c3ChartDefaults();
const config = c3ChartDefaults.getDefaultAreaConfig();
config.axis = {
x: {
categories: data.categories,
type: 'category',
tick: {
format: runId => `TR-${runId}`,
fit: false,
multiline: false,
},
},
y: {
tick: {
format: showOnlyRoundNumbers
}
}
};
config.bindto = '#chart';
config.color = {
pattern: data.colors
};
config.data = {
columns: chartData,
type: 'area-spline',
order: null
};
config.bar = {
width: {
ratio: 1
}
};
c3.generate(config);
});
}
4 changes: 4 additions & 0 deletions tcms/telemetry/static/telemetry/js/testing/utils.js
Expand Up @@ -22,3 +22,7 @@ function loadInitialProduct(callback = () => {}) {
function loadInitialTestPlans() {
jsonRPC('TestPlan.filter', {}, data => updateSelect(data, '#id_test_plan', 'plan_id', 'name'));
}

function showOnlyRoundNumbers(number) {
return number % 1 === 0 ? number : '';
}
95 changes: 95 additions & 0 deletions tcms/telemetry/templates/telemetry/testing/execution-trends.html
@@ -0,0 +1,95 @@
{% extends "base.html" %}
{% load i18n %}
{% load static %}

{% block title %}{% trans "Testing Status Matrix" %}{% endblock %}

{% block contents %}

<div class="container-fluid container-cards-pf">

<form class="form-horizontal">
<div class="form-group">
<label for="id_product" class="col-md-1 col-lg-1">
{% trans "Product" %}
</label>
<div class="col-md-3">
<select name="product" id="id_product" class="form-control selectpicker">
<option value="">----------</option>
</select>
</div>

<label for="id_version" class="col-md-1 col-lg-1">
{% trans "Version" %}
</label>
<div class="col-md-3">
<select name="version" id="id_version" class="form-control selectpicker">
<option value="">----------</option>
</select>
</div>

<label for="id_build" class="col-md-1 col-lg-1">
{% trans "Build" %}
</label>
<div class="col-md-3">
<select name="build" id="id_build" class="form-control selectpicker">
<option value="">----------</option>
</select>
</div>
</div>

<div class="form-group">
<label for="id_test_plan" class="col-md-1 col-lg-1">
{% trans "Test Plan" %}
</label>
<div class="col-md-3">
<select name="test_plan" id="id_test_plan" class="form-control selectpicker">
<option value="">----------</option>
</select>
</div>

<label class="col-md-1 col-lg-1" for="id_after">{% trans "After" %}</label>
<div class="col-md-3 col-lg-3">
<div class="input-group date-time-picker-pf">
<input type="text" class="form-control" id="id_after">
<span class="input-group-addon">
<span class="fa fa-calendar"></span>
</span>
</div>

{% include "datetimepicker_script.html" with selector="#id_after" %}
</div>

<label class="col-md-1 col-lg-1" for="id_before">{% trans "Before" %}</label>
<div class="col-md-3 col-lg-3">
<div class="input-group date-time-picker-pf">
<input type="text" class="form-control" id="id_before">
<span class="input-group-addon">
<span class="fa fa-calendar"></span>
</span>
</div>

{% include "datetimepicker_script.html" with selector="#id_before" %}
</div>
</div>
</form>

<div id="chart"></div>

<div id="area-chart-4"></div>
</div>

<script src="{% static 'moment/min/moment-with-locales.min.js' %}"></script>
<script src="{% static 'eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js' %}"></script>
<script src="{% static 'bootstrap-select/dist/js/bootstrap-select.min.js' %}"></script>
<script src="{% static 'bootstrap-switch/dist/js/bootstrap-switch.min.js' %}"></script>
<script src="{% static 'c3/c3.min.js' %}"></script>
<script src="{% static 'd3/d3.min.js' %}"></script>

<script src="{% static 'js/jsonrpc.js' %}"></script>
<script src="{% static 'js/utils.js' %}"></script>

<script src="{% static 'telemetry/js/testing/utils.js' %}"></script>
<script src="{% static 'telemetry/js/testing/execution-trends.js' %}"></script>

{% endblock %}
4 changes: 3 additions & 1 deletion tcms/telemetry/urls.py
Expand Up @@ -5,5 +5,7 @@
urlpatterns = [
url(r'^testing/breakdown/$', views.TestingBreakdownView.as_view(), name='testing-breakdown'),
url(r'^testing/status-matrix/$', views.TestingStatusMatrixView.as_view(),
name='testing-status-matrix')
name='testing-status-matrix'),
url(r'^testing/execution-trends/$', views.TestingExecutionTrendsView.as_view(),
name='testing-execution-trends'),
]
5 changes: 5 additions & 0 deletions tcms/telemetry/views.py
Expand Up @@ -9,3 +9,8 @@ class TestingBreakdownView(TemplateView): # pylint: disable=missing-permission-
class TestingStatusMatrixView(TemplateView): # pylint: disable=missing-permission-required

template_name = 'telemetry/testing/status-matrix.html'


class TestingExecutionTrendsView(TemplateView):

template_name = 'telemetry/testing/execution-trends.html'
13 changes: 13 additions & 0 deletions tcms/testruns/models.py
Expand Up @@ -255,11 +255,20 @@ class TestExecutionStatus(TCMSActionModel):
IDLE: 'idle',
FAILED: 'fail',
PASSED: 'pass',
BLOCKED: 'fail',
'ERROR': 'fail',
}

_colors = {
IDLE: '#72767b',
'PASS': '#92d400',
'FAIL': '#cc0000'
}

complete_status_names = (PASSED, 'ERROR', FAILED, 'WAIVED')
failure_status_names = ('ERROR', FAILED)
idle_status_names = (IDLE,)
chart_status_names = ('pass', 'fail', 'idle', 'other')

id = models.AutoField(db_column='case_run_status_id', primary_key=True)
name = models.CharField(max_length=60, blank=True, unique=True)
Expand All @@ -281,6 +290,10 @@ def icon(self):
with override('en'):
return self._icons.get(self.name)

def color(self):
with override('en'):
return self._colors.get(self.color_code().upper(), '#ec7a08')

def color_code(self):
with override('en'):
return self._css_classes.get(self.name, 'other')
Expand Down

0 comments on commit 8ea00c2

Please sign in to comment.