KPI report enhancements
* Dynamic routing to templates (no Flask endpoint is needed for new reports) * Added KPI to become core engineer * Added KPI on disagreement ratio * Added more examples on KPI usage Change-Id: I8b91ad0864b18d8bf731e4a4677f23daa0a714e5
This commit is contained in:
@@ -21,8 +21,7 @@ from dashboard import decorators
|
|||||||
blueprint = flask.Blueprint('kpi', __name__, url_prefix='/kpi')
|
blueprint = flask.Blueprint('kpi', __name__, url_prefix='/kpi')
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/example')
|
@blueprint.route('/<path:path>')
|
||||||
@decorators.templated()
|
|
||||||
@decorators.exception_handler()
|
@decorators.exception_handler()
|
||||||
def example():
|
def kpi_report(path):
|
||||||
return
|
return flask.render_template('kpi/' + path + '.html'), 200
|
||||||
|
|||||||
@@ -38,8 +38,8 @@
|
|||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/stackalytics-ui.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='js/stackalytics-ui.js') }}"></script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
'use strict';
|
||||||
function process_stats(container_id, url, query_options, item_id, goal, comparator, data_filter) {
|
function process_stats(container_id, url, query_options, item_id, metric, text_goal, comparator, data_filter) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: make_uri(url, query_options),
|
url: make_uri(url, query_options),
|
||||||
dataType: "jsonp",
|
dataType: "jsonp",
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sum += data[i].metric;
|
sum += data[i][metric];
|
||||||
data[i].index = ++ index; // re-index
|
data[i].index = ++ index; // re-index
|
||||||
if (data[i].id == item_id) {
|
if (data[i].id == item_id) {
|
||||||
position = i;
|
position = i;
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
|
|
||||||
var result = {
|
var result = {
|
||||||
mark: false,
|
mark: false,
|
||||||
goal: goal
|
text_goal: text_goal
|
||||||
};
|
};
|
||||||
|
|
||||||
if (position < 0) {
|
if (position < 0) {
|
||||||
@@ -80,9 +80,9 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function goal_position_in_top(container_id, query_options, item_type, item_id, position, goal, data_filter) {
|
function goal_position_in_top(container_id, query_options, item_type, item_id, position, text_goal, data_filter) {
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
process_stats(container_id, "/api/1.0/stats/" + item_type, query_options, item_id, goal,
|
process_stats(container_id, "/api/1.0/stats/" + item_type, query_options, item_id, "metric", text_goal,
|
||||||
function(item, sum) {
|
function(item, sum) {
|
||||||
var mark = item.index <= position;
|
var mark = item.index <= position;
|
||||||
return {
|
return {
|
||||||
@@ -95,29 +95,29 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function goal_metric(container_id, query_options, item_type, item_id, metric, goal) {
|
function goal_metric(container_id, query_options, item_type, item_id, target, text_goal) {
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
process_stats(container_id, "/api/1.0/stats/" + item_type, query_options, item_id, goal,
|
process_stats(container_id, "/api/1.0/stats/" + item_type, query_options, item_id, "metric", text_goal,
|
||||||
function(item, sum) {
|
function(item, sum) {
|
||||||
var mark = item.metric > metric;
|
var mark = item.metric >= target;
|
||||||
return {
|
return {
|
||||||
mark: mark,
|
mark: mark,
|
||||||
info: mark? "Achieved metric " + item.metric:
|
info: mark? "Achieved metric " + item.metric:
|
||||||
"Metric " + item.metric + " is worse than the goal in " + metric,
|
"Metric " + item.metric + " is worse than the goal in " + target,
|
||||||
value: item.index
|
value: item.index
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function goal_percentage_in_top_less_than(container_id, query_options, item_type, item_id, goal_percentage, goal) {
|
function goal_percentage_in_top_less_than(container_id, query_options, item_type, item_id, target_percentage, text_goal) {
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
process_stats(container_id, "/api/1.0/stats/" + item_type, query_options, item_id, goal,
|
process_stats(container_id, "/api/1.0/stats/" + item_type, query_options, item_id, "metric", text_goal,
|
||||||
function(item, sum) {
|
function(item, sum) {
|
||||||
var percentage = item.metric / sum;
|
var percentage = item.metric / sum;
|
||||||
var mark = percentage < goal_percentage;
|
var mark = percentage <= target_percentage;
|
||||||
var percentage_formatted = Math.round(percentage * 100) + "%";
|
var percentage_formatted = Math.round(percentage * 100) + "%";
|
||||||
var goal_percentage_formatted = Math.round(goal_percentage * 100) + "%";
|
var goal_percentage_formatted = Math.round(target_percentage * 100) + "%";
|
||||||
return {
|
return {
|
||||||
mark: mark,
|
mark: mark,
|
||||||
info: mark? "Achieved percentage " + percentage_formatted:
|
info: mark? "Achieved percentage " + percentage_formatted:
|
||||||
@@ -128,13 +128,64 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goal_disagreement_ratio_less_than(container_id, query_options, item_id, target_percentage, text_goal) {
|
||||||
|
$(document).ready(function () {
|
||||||
|
process_stats(container_id, "/api/1.0/stats/engineers", query_options, item_id, "disagreement_ratio", text_goal,
|
||||||
|
function(item, sum) {
|
||||||
|
var percentage = parseFloat(item["disagreement_ratio"]);
|
||||||
|
var mark = percentage < target_percentage * 100;
|
||||||
|
var goal_percentage_formatted = Math.round(target_percentage * 100) + "%";
|
||||||
|
return {
|
||||||
|
mark: mark,
|
||||||
|
info: mark? "Achieved percentage " + item["disagreement_ratio"]:
|
||||||
|
"Value " + item["disagreement_ratio"] + " is more than the goal " + goal_percentage_formatted,
|
||||||
|
value: percentage
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function goal_core_engineer_in_project(container_id, user_id, project, text_goal) {
|
||||||
|
$(document).ready(function () {
|
||||||
|
$.ajax({
|
||||||
|
url: make_uri("/api/1.0/users/" + user_id),
|
||||||
|
dataType: "jsonp",
|
||||||
|
success: function (data) {
|
||||||
|
var user = data.user;
|
||||||
|
var is_core = false;
|
||||||
|
if (user.core) {
|
||||||
|
for (var i in user.core) {
|
||||||
|
if (user.core[i][0] == project) {
|
||||||
|
is_core = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var result = {
|
||||||
|
mark: is_core,
|
||||||
|
text_goal: text_goal,
|
||||||
|
info: user.user_name + " (" + user_id + ") is " + (is_core? "": "not ") + "core engineer in " + project
|
||||||
|
};
|
||||||
|
$("#kpi_block_template").tmpl(result).appendTo("#" + container_id);
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
var result = {
|
||||||
|
mark: false,
|
||||||
|
text_goal: text_goal,
|
||||||
|
info: "Item " + user_id + " is not found in the stats"
|
||||||
|
};
|
||||||
|
$("#kpi_block_template").tmpl(result).appendTo("#" + container_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script id="kpi_block_template" type="text/x-jquery-tmpl">
|
<script id="kpi_block_template" type="text/x-jquery-tmpl">
|
||||||
<div class="kpi_block">
|
<div class="kpi_block">
|
||||||
<div class="kpi_marker ${(mark ? "kpi_good" : "kpi_bad")} ">${(mark ? "✔" : "✖")}</div>
|
<div class="kpi_marker ${(mark ? "kpi_good" : "kpi_bad")} ">${(mark ? "✔" : "✖")}</div>
|
||||||
<div class="kpi_title_block">
|
<div class="kpi_title_block">
|
||||||
<div class="kpi_title">Goal: ${goal}</div>
|
<div class="kpi_title">Goal: ${text_goal}</div>
|
||||||
<div class="kpi_info">Result: ${info}</div>
|
<div class="kpi_info">Result: ${info}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,21 +6,34 @@
|
|||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
'use strict';
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
|
||||||
goal_position_in_top("kpi_container_position", {release: "icehouse", metric: "commits", module: "openstack"},
|
var month_ago = Math.round(Date.now() / 1000) - 60 * 60 * 24 * 30;
|
||||||
"companies", "Mirantis", 5, "Be in top 5 by commits");
|
|
||||||
goal_position_in_top("kpi_container_position", {release: "icehouse", metric: "marks", module: "openstack"},
|
|
||||||
"engineers", "boris-42", 5, "Be in top 5 by reviews");
|
|
||||||
goal_position_in_top("kpi_container_position", {release: "icehouse", metric: "marks", module: "glance"},
|
|
||||||
"engineers", "boris-42", 3, "Be in top 3 among non-core reviewers");
|
|
||||||
|
|
||||||
goal_percentage_in_top_less_than("kpi_container_percentage",
|
goal_position_in_top("kpi_container_position", {release: "icehouse", metric: "commits", module: "openstack"},
|
||||||
{release: "all", metric: "commits", module: "stackalytics"},
|
"companies", "Mirantis", 5, "Be in top 5 by commits");
|
||||||
"companies", "Mirantis", 0.8, "Mirantis contribution is less than 80%");
|
goal_position_in_top("kpi_container_position", {release: "icehouse", metric: "marks", module: "openstack"},
|
||||||
|
"engineers", "boris-42", 5, "Be in top 5 by reviews");
|
||||||
|
goal_position_in_top("kpi_container_position", {release: "icehouse", metric: "marks", module: "glance"},
|
||||||
|
"engineers", "boris-42", 3, "Be in top 3 among reviewers");
|
||||||
|
|
||||||
goal_metric("kpi_container_position", {release: "icehouse", metric: "bpd", module: "glance", exclude: "core"},
|
goal_percentage_in_top_less_than("kpi_container_percentage",
|
||||||
"modules", "glance", 10, "File at least 10 blueprints into Glance");
|
{release: "all", metric: "commits", project_type: "all", module: "stackalytics"},
|
||||||
|
"companies", "Mirantis", 0.8, "Mirantis contribution to Stackalytics is less than 80%");
|
||||||
|
|
||||||
|
goal_metric("kpi_container_position", {release: "icehouse", metric: "bpd", module: "glance"},
|
||||||
|
"modules", "glance", 50, "File at least 50 blueprints into Glance");
|
||||||
|
|
||||||
|
goal_metric("kpi_container_position", {release: "all", metric: "bpd", module: "glance", start_date: month_ago},
|
||||||
|
"modules", "glance", 20, "File at least 20 blueprints into Glance in last month");
|
||||||
|
|
||||||
|
goal_disagreement_ratio_less_than("kpi_container_percentage",
|
||||||
|
{release: "all", metric: "marks", project_type: "all", module: "glance"},
|
||||||
|
"lzy-dev", 0.08, "Disagreement ratio should be less than 8%");
|
||||||
|
|
||||||
|
|
||||||
|
goal_core_engineer_in_project("kpi_core_status", "lzy-dev", "glance", "Become core engineer in Glance");
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -34,4 +47,7 @@
|
|||||||
|
|
||||||
<h2>Percentage in top</h2>
|
<h2>Percentage in top</h2>
|
||||||
<div id="kpi_container_percentage"></div>
|
<div id="kpi_container_percentage"></div>
|
||||||
|
|
||||||
|
<h2>Core status</h2>
|
||||||
|
<div id="kpi_core_status"></div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user