Implement scripting for KPI reports
KPI reports can be generated from script in yaml format. Scripting provides a way to specify goals in human-readable form. Targets can be grouped by modules, companies and users. Single target can be of following types: to reach metric value, to reach position in top, to reach minimum percentage value or to become core engineer. The example is located in stackalytics/dashboard/templates/kpi/kpi_script.html Change-Id: Idec4db58f5134d84009ba982121af3f086e4ad46
This commit is contained in:
@@ -487,3 +487,9 @@ div.stackamenu li.current-menu-item a span {
|
||||
font-size: 16px;
|
||||
color: #972D24;
|
||||
}
|
||||
|
||||
.error {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
3
stackalytics/dashboard/static/js/js-yaml.min.js
vendored
Normal file
3
stackalytics/dashboard/static/js/js-yaml.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
401
stackalytics/dashboard/static/js/stackalytics-kpi.js
Normal file
401
stackalytics/dashboard/static/js/stackalytics-kpi.js
Normal file
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
Copyright (c) 2014 Mirantis Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
function showError(container, message) {
|
||||
container.append($("<pre class='error'>Error! " + message + "</pre>"));
|
||||
}
|
||||
|
||||
function appendKpiBlock(container_id, kpi_block) {
|
||||
var container = container_id;
|
||||
if (typeof container_id == "string") {
|
||||
container = $("#" + container_id);
|
||||
}
|
||||
var template = $("#kpi_block_template");
|
||||
if (template.length > 0) {
|
||||
container.append(template.tmpl(kpi_block));
|
||||
} else {
|
||||
container.append($("<pre>" + JSON.stringify(kpi_block) + "</pre>"));
|
||||
}
|
||||
}
|
||||
|
||||
function processStats(container_id, url, query_options, item_id, metric, text_goal, comparator, data_filter) {
|
||||
$.ajax({
|
||||
url: makeURI(url, query_options),
|
||||
dataType: "jsonp",
|
||||
success: function (data) {
|
||||
data = data["stats"];
|
||||
var position = -1;
|
||||
var index = 0;
|
||||
var sum = 0;
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
if (data_filter) {
|
||||
if (!data_filter(data[i])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
sum += data[i][metric];
|
||||
data[i].index = ++index; // re-index
|
||||
if (data[i].id == item_id) {
|
||||
position = i;
|
||||
}
|
||||
}
|
||||
|
||||
var result = {
|
||||
mark: false,
|
||||
text_goal: text_goal
|
||||
};
|
||||
|
||||
if (position < 0) {
|
||||
result.info = "Item " + item_id + " is not found in the stats";
|
||||
}
|
||||
else {
|
||||
var comparison_result = comparator(data[position], sum);
|
||||
result.mark = comparison_result.mark;
|
||||
result.info = comparison_result.info;
|
||||
}
|
||||
appendKpiBlock(container_id, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function goalPositionInTop(container_id, query_options, item_type, item_id, position, text_goal, data_filter) {
|
||||
$(document).ready(function () {
|
||||
processStats(container_id, "/api/1.0/stats/" + item_type, query_options, item_id, "metric", text_goal,
|
||||
function (item, sum) {
|
||||
var mark = item.index <= position;
|
||||
return {
|
||||
mark: mark,
|
||||
info: mark ? "Achieved position is " + item.index :
|
||||
"Position " + item.index + " is worse than the goal position " + position,
|
||||
value: item.index
|
||||
}
|
||||
}, data_filter);
|
||||
});
|
||||
}
|
||||
|
||||
function goalMetric(container_id, query_options, item_type, item_id, target, text_goal) {
|
||||
$(document).ready(function () {
|
||||
processStats(container_id, "/api/1.0/stats/" + item_type, query_options, item_id, "metric", text_goal,
|
||||
function (item, sum) {
|
||||
var mark = item.metric >= target;
|
||||
return {
|
||||
mark: mark,
|
||||
info: mark ? "Achieved metric " + item.metric :
|
||||
"Metric " + item.metric + " is worse than the goal in " + target,
|
||||
value: item.index
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function goalPercentageInTopLessThan(container_id, query_options, item_type, item_id, target_percentage, text_goal) {
|
||||
$(document).ready(function () {
|
||||
processStats(container_id, "/api/1.0/stats/" + item_type, query_options, item_id, "metric", text_goal,
|
||||
function (item, sum) {
|
||||
var percentage = item.metric / sum;
|
||||
var mark = percentage <= target_percentage;
|
||||
var percentage_formatted = Math.round(percentage * 100) + "%";
|
||||
var goal_percentage_formatted = Math.round(target_percentage * 100) + "%";
|
||||
return {
|
||||
mark: mark,
|
||||
info: mark ? "Achieved percentage " + percentage_formatted :
|
||||
"Value " + percentage_formatted + " is more than the goal " + goal_percentage_formatted,
|
||||
value: percentage_formatted
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function goalDisagreementRatioLessThan(container_id, query_options, item_id, target_percentage, text_goal) {
|
||||
$(document).ready(function () {
|
||||
processStats(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 goalCoreEngineerInProject(container_id, user_id, project, text_goal) {
|
||||
$(document).ready(function () {
|
||||
$.ajax({
|
||||
url: makeURI("/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
|
||||
};
|
||||
appendKpiBlock(container_id, result);
|
||||
},
|
||||
error: function () {
|
||||
var result = {
|
||||
mark: false,
|
||||
text_goal: text_goal,
|
||||
info: "Item " + user_id + " is not found in the stats"
|
||||
};
|
||||
appendKpiBlock(container_id, result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadAndShowUserProfile(container, user_id) {
|
||||
$.ajax({
|
||||
url: makeURI("/api/1.0/users/" + user_id),
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
var user = data["user"];
|
||||
container.html(user["user_name"] + " (" + user["user_id"] + ")");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadAndShowModuleDetails(container, module_id) {
|
||||
$.ajax({
|
||||
url: makeURI("/api/1.0/modules/" + module_id),
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
var module = data["module"];
|
||||
container.html(module["name"] + " (" + module["id"] + ")");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var now = Math.floor(Date.now() / 1000);
|
||||
|
||||
var release_pattern = /Release (\S+)/;
|
||||
var group_pattern = /Group (\S+)/;
|
||||
var company_pattern = /Company (\S+)/;
|
||||
var user_pattern = /User (\S+)/;
|
||||
|
||||
var in_pattern = /.*?(\s+in\s+(\S+)).*/;
|
||||
var during_pattern = /.*?(\s+during\s+(\d+)\s+days).*/;
|
||||
var make_pattern = /(make|draft|send|write|implement|file|fix|complete)\s+(\d+)\s+(\S+)/;
|
||||
var top_pattern = /top\s+(\d+)\s+by\s+(\S+)/;
|
||||
var core_pattern = /(become|stay)\s+core/;
|
||||
var not_less_than_pattern = /less\s+than\s+(\d+)%\s+by\s+(\S+)/;
|
||||
|
||||
function makeKpiRequestOptions(release, metric, module, duration) {
|
||||
var options = {metric: metric, module: module, project_type: "all"};
|
||||
if (duration) {
|
||||
options["start_date"] = now - duration * 60 * 60 * 24;
|
||||
options["release"] = "all";
|
||||
} else {
|
||||
options["release"] = release;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function runMakeStatement(statement, verb, count, noun, duration, item_type, item_id, module, release, container) {
|
||||
var metric = noun;
|
||||
|
||||
if (noun == "blueprints") {
|
||||
metric = (verb == "draft" || verb == "file") ? "bpd" : "bpc";
|
||||
}
|
||||
if (noun == "bugs") {
|
||||
metric = (verb == "file") ? "filed-bugs" : "resolved-bugs";
|
||||
}
|
||||
if (noun == "reviews") {
|
||||
metric = "marks";
|
||||
}
|
||||
|
||||
goalMetric(container, makeKpiRequestOptions(release, metric, module, duration),
|
||||
item_type, item_id, count, statement);
|
||||
}
|
||||
|
||||
function runTopStatement(statement, position, noun, duration, item_type, item_id, module, release, container) {
|
||||
var metric = noun;
|
||||
if (noun == "reviews") {
|
||||
metric = "marks";
|
||||
}
|
||||
|
||||
goalPositionInTop(container, makeKpiRequestOptions(release, metric, module, duration),
|
||||
item_type, item_id, position, statement);
|
||||
}
|
||||
|
||||
function runNotLessThanStatement(statement, percentage, noun, duration, item_type, item_id, module, release, container) {
|
||||
var metric = noun;
|
||||
if (noun == "reviews") {
|
||||
metric = "marks";
|
||||
}
|
||||
|
||||
goalPercentageInTopLessThan(container,
|
||||
makeKpiRequestOptions(release, metric, module, duration),
|
||||
item_type, item_id, percentage / 100.0, statement);
|
||||
}
|
||||
|
||||
function parseStatements(item_type, item_id, module, release, details, container) {
|
||||
for (var i in details) {
|
||||
var original_statement = details[i];
|
||||
var statement = original_statement;
|
||||
var local_module = module;
|
||||
var duration = null;
|
||||
|
||||
var pattern_match = in_pattern.exec(statement);
|
||||
if (pattern_match) {
|
||||
local_module = pattern_match[2];
|
||||
statement = statement.replace(pattern_match[1], "");
|
||||
}
|
||||
pattern_match = during_pattern.exec(statement);
|
||||
if (pattern_match) {
|
||||
duration = pattern_match[2];
|
||||
statement = statement.replace(pattern_match[1], "");
|
||||
}
|
||||
|
||||
statement = statement.trim();
|
||||
|
||||
pattern_match = make_pattern.exec(statement);
|
||||
if (pattern_match) {
|
||||
runMakeStatement(original_statement, pattern_match[1], pattern_match[2], pattern_match[3], duration,
|
||||
item_type, item_id, local_module, release, container);
|
||||
continue;
|
||||
}
|
||||
|
||||
pattern_match = top_pattern.exec(statement);
|
||||
if (pattern_match) {
|
||||
runTopStatement(original_statement, pattern_match[1], pattern_match[2], duration,
|
||||
item_type, item_id, local_module, release, container);
|
||||
continue;
|
||||
}
|
||||
|
||||
pattern_match = core_pattern.exec(statement);
|
||||
if (pattern_match) {
|
||||
goalCoreEngineerInProject(container, item_id, local_module, original_statement);
|
||||
continue;
|
||||
}
|
||||
|
||||
pattern_match = not_less_than_pattern.exec(statement);
|
||||
if (pattern_match) {
|
||||
runNotLessThanStatement(original_statement, pattern_match[1], pattern_match[2], duration,
|
||||
item_type, item_id, local_module, release, container);
|
||||
continue;
|
||||
}
|
||||
|
||||
showError(container, "Could not parse statement: '" + statement + "'");
|
||||
}
|
||||
}
|
||||
|
||||
function parseGroup(group, release, details, container) {
|
||||
var users = [];
|
||||
|
||||
for (var token in details) {
|
||||
var pattern_match = user_pattern.exec(token);
|
||||
if (pattern_match) {
|
||||
var user = pattern_match[1];
|
||||
users.push(user);
|
||||
|
||||
var body = $("<div id='u" + Math.random() + "'/>");
|
||||
var user_title_block = $("<h3>" + user + "</h3>");
|
||||
container.append(user_title_block).append(body);
|
||||
|
||||
loadAndShowUserProfile(user_title_block, user);
|
||||
|
||||
parseStatements("engineers", user, group, release, details[token], body);
|
||||
continue;
|
||||
}
|
||||
|
||||
pattern_match = company_pattern.exec(token);
|
||||
if (pattern_match) {
|
||||
var company = pattern_match[1];
|
||||
|
||||
body = $("<div/>");
|
||||
container.append($("<h3>" + company + "</h3>")).append(body);
|
||||
|
||||
parseStatements("companies", company, group, release, details[token], body);
|
||||
continue;
|
||||
}
|
||||
|
||||
showError(container, "Could not parse line: '" + details[token] + "'");
|
||||
}
|
||||
}
|
||||
|
||||
function parseRelease(release, details, container) {
|
||||
for (var token in details) {
|
||||
var pattern_match = group_pattern.exec(token);
|
||||
if (pattern_match) {
|
||||
var group = pattern_match[1];
|
||||
|
||||
var body = $("<div/>");
|
||||
var title_block = $("<h2>" + group + "</h2>");
|
||||
container.append(title_block).append(body);
|
||||
|
||||
loadAndShowModuleDetails(title_block, group);
|
||||
|
||||
parseGroup(group, release, details[token], body);
|
||||
continue;
|
||||
}
|
||||
|
||||
pattern_match = company_pattern.exec(token);
|
||||
if (pattern_match) {
|
||||
var company = pattern_match[1];
|
||||
|
||||
body = $("<div/>");
|
||||
container.append($("<h2>" + company + "</h2>")).append(body);
|
||||
|
||||
parseStatements("companies", company, "all", release, details[token], body);
|
||||
continue;
|
||||
}
|
||||
|
||||
showError(container, "Could not parse line: '" + token + "'");
|
||||
}
|
||||
}
|
||||
|
||||
function parseKpiScript(parsed_script, container) {
|
||||
for (var token in parsed_script) {
|
||||
var pattern_match = release_pattern.exec(token);
|
||||
if (pattern_match) {
|
||||
var release = pattern_match[1];
|
||||
|
||||
var body = $("<div/>");
|
||||
$(container).append($("<h1>" + release + "</h1>")).append(body);
|
||||
|
||||
parseRelease(release, parsed_script[token], body);
|
||||
continue;
|
||||
}
|
||||
showError(container, "Could not parse line: '" + token + "'");
|
||||
}
|
||||
}
|
||||
|
||||
function readKpiScript(kpi_script, container_id) {
|
||||
var root_container = $("#" + container_id).empty();
|
||||
|
||||
try {
|
||||
var parsed_script = jsyaml.safeLoad(kpi_script);
|
||||
parseKpiScript(parsed_script, root_container);
|
||||
} catch (e) {
|
||||
showError(root_container, "Could not parse script: '" + kpi_script + "'");
|
||||
}
|
||||
}
|
||||
@@ -48,13 +48,16 @@
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/jqplot.highlighter.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/select2.min.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery.tmpl.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery.timeago.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/md5.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery.gravatar.js') }}"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/js-yaml.min.js') }}"></script>
|
||||
|
||||
{% if active_tab == 'driverlog' %}
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/driverlog-ui.js') }}"></script>
|
||||
{% else %}
|
||||
<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-kpi.js') }}"></script>
|
||||
{% endif %}
|
||||
|
||||
{% block head %}{% endblock %}
|
||||
|
||||
@@ -2,150 +2,6 @@
|
||||
|
||||
{% block head %}
|
||||
|
||||
<script type="text/javascript">
|
||||
'use strict';
|
||||
function process_stats(container_id, url, query_options, item_id, metric, text_goal, comparator, data_filter) {
|
||||
$.ajax({
|
||||
url: makeURI(url, query_options),
|
||||
dataType: "jsonp",
|
||||
success: function (data) {
|
||||
data = data["stats"];
|
||||
var position = -1;
|
||||
var index = 0;
|
||||
var sum = 0;
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
if (data_filter) {
|
||||
if (!data_filter(data[i])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
sum += data[i][metric];
|
||||
data[i].index = ++ index; // re-index
|
||||
if (data[i].id == item_id) {
|
||||
position = i;
|
||||
}
|
||||
}
|
||||
|
||||
var result = {
|
||||
mark: false,
|
||||
text_goal: text_goal
|
||||
};
|
||||
|
||||
if (position < 0) {
|
||||
result.info = "Item " + item_id + " is not found in the stats";
|
||||
}
|
||||
else {
|
||||
var comparison_result = comparator(data[position], sum);
|
||||
result.mark = comparison_result.mark;
|
||||
result.info = comparison_result.info;
|
||||
}
|
||||
$("#kpi_block_template").tmpl(result).appendTo("#" + container_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function goal_position_in_top(container_id, query_options, item_type, item_id, position, text_goal, data_filter) {
|
||||
$(document).ready(function () {
|
||||
process_stats(container_id, "/api/1.0/stats/" + item_type, query_options, item_id, "metric", text_goal,
|
||||
function(item, sum) {
|
||||
var mark = item.index <= position;
|
||||
return {
|
||||
mark: mark,
|
||||
info: mark? "Achieved position is " + item.index:
|
||||
"Position " + item.index + " is worse than the goal position " + position,
|
||||
value: item.index
|
||||
}
|
||||
}, data_filter);
|
||||
});
|
||||
}
|
||||
|
||||
function goal_metric(container_id, query_options, item_type, item_id, target, text_goal) {
|
||||
$(document).ready(function () {
|
||||
process_stats(container_id, "/api/1.0/stats/" + item_type, query_options, item_id, "metric", text_goal,
|
||||
function(item, sum) {
|
||||
var mark = item.metric >= target;
|
||||
return {
|
||||
mark: mark,
|
||||
info: mark? "Achieved metric " + item.metric:
|
||||
"Metric " + item.metric + " is worse than the goal in " + target,
|
||||
value: item.index
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function goal_percentage_in_top_less_than(container_id, query_options, item_type, item_id, target_percentage, text_goal) {
|
||||
$(document).ready(function () {
|
||||
process_stats(container_id, "/api/1.0/stats/" + item_type, query_options, item_id, "metric", text_goal,
|
||||
function(item, sum) {
|
||||
var percentage = item.metric / sum;
|
||||
var mark = percentage <= target_percentage;
|
||||
var percentage_formatted = Math.round(percentage * 100) + "%";
|
||||
var goal_percentage_formatted = Math.round(target_percentage * 100) + "%";
|
||||
return {
|
||||
mark: mark,
|
||||
info: mark? "Achieved percentage " + percentage_formatted:
|
||||
"Value " + percentage_formatted + " is more than the goal " + goal_percentage_formatted,
|
||||
value: percentage_formatted
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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: makeURI("/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 id="kpi_block_template" type="text/x-jquery-tmpl">
|
||||
<div class="kpi_block">
|
||||
<div class="kpi_marker ${(mark ? "kpi_good" : "kpi_bad")} ">${(mark ? "✔" : "✖")}</div>
|
||||
@@ -164,7 +20,7 @@
|
||||
<div style="margin: 2em;">
|
||||
<div id="analytics_header" style="padding-bottom: 1em; border-bottom: 1px solid darkgrey;">
|
||||
<span id="logo"><a href="{{ url_for('overview') }}"><img src="{{ url_for('static', filename='images/stackalytics_logo.png') }}" alt="Stackalytics" style="width: 100%; max-width: 190px;"></a></span>
|
||||
<span id="slogan">| community heartbeat</span>
|
||||
<span id="slogan" style="position: relative; top: -15px;">| community heartbeat</span>
|
||||
</div>
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -11,29 +11,29 @@
|
||||
|
||||
var month_ago = Math.round(Date.now() / 1000) - 60 * 60 * 24 * 30;
|
||||
|
||||
goal_position_in_top("kpi_container_position", {release: "icehouse", metric: "commits", module: "openstack"},
|
||||
goalPositionInTop("kpi_container_position", {release: "icehouse", metric: "commits", module: "openstack"},
|
||||
"companies", "Mirantis", 5, "Be in top 5 by commits");
|
||||
goal_position_in_top("kpi_container_position", {release: "icehouse", metric: "marks", module: "openstack"},
|
||||
goalPositionInTop("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"},
|
||||
goalPositionInTop("kpi_container_position", {release: "icehouse", metric: "marks", module: "glance"},
|
||||
"engineers", "boris-42", 3, "Be in top 3 among reviewers");
|
||||
|
||||
goal_percentage_in_top_less_than("kpi_container_percentage",
|
||||
goalPercentageInTopLessThan("kpi_container_percentage",
|
||||
{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"},
|
||||
goalMetric("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},
|
||||
goalMetric("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",
|
||||
goalDisagreementRatioLessThan("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");
|
||||
goalCoreEngineerInProject("kpi_core_status", "lzy-dev", "glance", "Become core engineer in Glance");
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
46
stackalytics/dashboard/templates/kpi/script.html
Normal file
46
stackalytics/dashboard/templates/kpi/script.html
Normal file
@@ -0,0 +1,46 @@
|
||||
{% extends "kpi/base_kpi.html" %}
|
||||
|
||||
{% block title %}
|
||||
Example of scripted KPI report
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
readKpiScript($("#kpi_script").val(), "root_container");
|
||||
|
||||
$("#btn_update").click(function() {
|
||||
readKpiScript($("#kpi_script").val(), "root_container");
|
||||
})
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div>KPI report can be configured by script. See the example below:</div>
|
||||
|
||||
<label for="kpi_script"></label>
|
||||
<textarea id="kpi_script" style="width: 60em; height: 30em;">
|
||||
Release Icehouse:
|
||||
Company Mirantis:
|
||||
- top 5 by commits in official-integrated
|
||||
- less than 50% by commits in stackalytics
|
||||
Group glance-group:
|
||||
User lzy-dev:
|
||||
- top 3 by commits
|
||||
- top 4 by reviews in glance
|
||||
- make 10 commits during 30 days
|
||||
- become core
|
||||
User apevec:
|
||||
- make 15 reviews in python-glanceclient
|
||||
- draft 2 blueprints
|
||||
- implement 7 blueprints
|
||||
- send 10 emails
|
||||
Company Mirantis:
|
||||
- top 5 by locs
|
||||
</textarea>
|
||||
<button id="btn_update">Update</button>
|
||||
|
||||
<div id="root_container"></div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user