Implemented summary report on reviews per engineer

Partially implements blueprint review-stats

Change-Id: I011d81a10ad63746a9246a62379c0f6b4c814812
This commit is contained in:
Ilya Shakhat 2013-08-07 20:17:34 +04:00
parent 6779de9dc2
commit 7fcf69efdf
4 changed files with 95 additions and 52 deletions

View File

@ -86,7 +86,7 @@
{% endif %} {% endif %}
<h4>Marks</h4> <h3>Reviews</h3>
{% for mark in [-2, -1, 0, 1, 2] %} {% for mark in [-2, -1, 0, 1, 2] %}
<div>{{ mark }}: {{ marks[mark] }}</div> <div>{{ mark }}: {{ marks[mark] }}</div>
{% endfor %} {% endfor %}

View File

@ -114,6 +114,7 @@
var aggregate = 0; var aggregate = 0;
var index = 1; var index = 1;
var i; var i;
var hasComment = false;
for (i = 0; i < data.length; i++) { for (i = 0; i < data.length; i++) {
if (i < limit - 1) { if (i < limit - 1) {
@ -133,7 +134,12 @@
} else { } else {
var link = data[i].name var link = data[i].name
} }
tableData.push({"index": index_label, "link": link, "metric": data[i].metric}); var rec = {"index": index_label, "link": link, "metric": data[i].metric};
if (data[i].comment) {
rec["comment"] = data[i].comment;
hasComment = true;
}
tableData.push(rec);
} }
if (i == limit) { if (i == limit) {
@ -142,6 +148,15 @@
chartData.push(["others", aggregate]); chartData.push(["others", aggregate]);
} }
var tableColumns = [
{ "mData": "index" },
{ "mData": "link" },
{ "mData": "metric" }
];
if (hasComment) {
tableColumns.push({ "mData": "comment"})
}
$("#" + table_id).dataTable({ $("#" + table_id).dataTable({
"aLengthMenu": [ "aLengthMenu": [
[25, 50, -1], [25, 50, -1],
@ -153,11 +168,7 @@
"sPaginationType": "full_numbers", "sPaginationType": "full_numbers",
"iDisplayLength": 25, "iDisplayLength": 25,
"aaData": tableData, "aaData": tableData,
"aoColumns": [ "aoColumns": tableColumns
{ "mData": "index" },
{ "mData": "link" },
{ "mData": "metric" }
]
}); });
var plot = $.jqplot(chart_id, [chartData], { var plot = $.jqplot(chart_id, [chartData], {

View File

@ -7,6 +7,7 @@
{% block scripts %} {% block scripts %}
<script type="text/javascript"> <script type="text/javascript">
chartAndTableRenderer("/data/companies", "left_list", "left_chart", "/companies/", {module: "{{ module }}" }); chartAndTableRenderer("/data/companies", "left_list", "left_chart", "/companies/", {module: "{{ module }}" });
chartAndTableRenderer("/data/engineers", "right_list", "right_chart", "/engineers/", {module: "{{ module }}" });
timelineRenderer({module: "{{ module }}" }) timelineRenderer({module: "{{ module }}" })
</script> </script>
{% endblock %} {% endblock %}
@ -34,37 +35,24 @@
{% block right_frame %} {% block right_frame %}
<h2>Recent activity</h2> <h2>Contribution by engineers</h2>
{% for rec in commits %} <div id="right_chart" style="width: 100%; height: 350px;"></div>
<div style="padding-bottom: 1em;">
<div style='float: left; '>
<img src="{{ rec.author_email|gravatar(size=32) }}">
</div>
<div style="margin-left: 4em;">
<div>
{% if rec.launchpad_id %}
{{ rec.author_name|link('/engineers/' + rec.launchpad_id)|safe }}
{% else %}
{{ rec.author_name }}
{% endif %}
{% if rec.company_name %}
(
{{ rec.company_name|link('/companies/' + rec.company_name)|safe }}
)
{% endif %}
<em>{{ rec.date|datetimeformat }}</em>
</div>
{% if rec.correction_comment %} <table id="right_list" class="display">
<div style='font-weight: bold; color: red; padding-left: 2em;'>Commit corrected: {{ rec.correction_comment }}</div> <thead>
<tr>
<th>#</th>
<th>Engineer</th>
<th>{{ metric_label }}</th>
{% if metric == 'marks' %}
<th>-2|-1|+1|+2 (+/- ratio)</th>
{% endif %} {% endif %}
<div><b>{{ rec.subject }}</b></div> </tr>
<div style="white-space: pre-wrap;">{{ rec|commit_message|safe }}</div> </thead>
<div><span style="color: green">+ {{ rec.lines_added }}</span> <tbody>
<span style="color: red">- {{ rec.lines_deleted }}</span></div> </tbody>
</div> </table>
</div> <div class="spacer"></div>
{% endfor %}
{% endblock %} {% endblock %}

View File

@ -44,8 +44,7 @@ DEFAULTS = {
METRIC_LABELS = { METRIC_LABELS = {
'loc': 'Lines of code', 'loc': 'Lines of code',
'commits': 'Commits', 'commits': 'Commits',
'reviews': 'Reviews', 'marks': 'Reviews',
'marks': 'Marks',
} }
DEFAULT_RECORDS_LIMIT = 10 DEFAULT_RECORDS_LIMIT = 10
@ -277,17 +276,59 @@ def aggregate_filter():
@functools.wraps(f) @functools.wraps(f)
def aggregate_filter_decorated_function(*args, **kwargs): def aggregate_filter_decorated_function(*args, **kwargs):
def commit_filter(result, record, param_id):
result[record[param_id]]['metric'] += 1
def loc_filter(result, record, param_id):
result[record[param_id]]['metric'] += record['loc']
def mark_filter(result, record, param_id):
value = record['value']
result_by_param = result[record[param_id]]
result_by_param['metric'] += 1
if value in result_by_param:
result_by_param[value] += 1
else:
result_by_param[value] = 1
def mark_finalize(record):
new_record = {}
for key in ['id', 'metric', 'name']:
new_record[key] = record[key]
positive = 0
mark_distribution = []
for key in ['-2', '-1', '1', '2']:
if key in record:
if key in ['1', '2']:
positive += record[key]
mark_distribution.append(str(record[key]))
else:
mark_distribution.append('0')
new_record['comment'] = (
'|'.join(mark_distribution) +
' (%.1f%%)' % ((positive * 100.0) / record['metric']))
return new_record
metric_param = (flask.request.args.get('metric') or metric_param = (flask.request.args.get('metric') or
get_default('metric')) get_default('metric'))
metric = metric_param.lower() metric = metric_param.lower()
if metric in ['commits', 'reviews', 'marks']: aggregate_filter = None
metric_filter = lambda r: 1
if metric == 'commits':
metric_filter = commit_filter
elif metric == 'loc': elif metric == 'loc':
metric_filter = lambda r: r['loc'] metric_filter = loc_filter
elif metric == 'marks':
metric_filter = mark_filter
aggregate_filter = mark_finalize
else: else:
raise Exception('Invalid metric %s' % metric) raise Exception('Invalid metric %s' % metric)
kwargs['metric_filter'] = metric_filter kwargs['metric_filter'] = metric_filter
kwargs['finalize_handler'] = aggregate_filter
return f(*args, **kwargs) return f(*args, **kwargs)
return aggregate_filter_decorated_function return aggregate_filter_decorated_function
@ -471,16 +512,18 @@ def engineer_details(user_id, records):
# AJAX Handlers --------- # AJAX Handlers ---------
def _get_aggregated_stats(records, metric_filter, keys, param_id, def _get_aggregated_stats(records, metric_filter, keys, param_id,
param_title=None): param_title=None, finalize_handler=None):
param_title = param_title or param_id param_title = param_title or param_id
result = dict((c, 0) for c in keys) result = dict((c, {'metric': 0, 'id': c}) for c in keys)
titles = {}
for record in records: for record in records:
result[record[param_id]] += metric_filter(record) metric_filter(result, record, param_id)
titles[record[param_id]] = record[param_title] result[record[param_id]]['name'] = record[param_title]
response = [{'id': r, 'metric': result[r], 'name': titles[r]} if not finalize_handler:
for r in result if result[r]] finalize_handler = lambda x: x
response = [finalize_handler(result[r]) for r in result
if result[r]['metric']]
response.sort(key=lambda x: x['metric'], reverse=True) response.sort(key=lambda x: x['metric'], reverse=True)
return response return response
@ -489,7 +532,7 @@ def _get_aggregated_stats(records, metric_filter, keys, param_id,
@exception_handler() @exception_handler()
@record_filter() @record_filter()
@aggregate_filter() @aggregate_filter()
def get_companies(records, metric_filter): def get_companies(records, metric_filter, finalize_handler):
response = _get_aggregated_stats(records, metric_filter, response = _get_aggregated_stats(records, metric_filter,
get_memory_storage().get_companies(), get_memory_storage().get_companies(),
'company_name') 'company_name')
@ -500,7 +543,7 @@ def get_companies(records, metric_filter):
@exception_handler() @exception_handler()
@record_filter() @record_filter()
@aggregate_filter() @aggregate_filter()
def get_modules(records, metric_filter): def get_modules(records, metric_filter, finalize_handler):
response = _get_aggregated_stats(records, metric_filter, response = _get_aggregated_stats(records, metric_filter,
get_memory_storage().get_modules(), get_memory_storage().get_modules(),
'module') 'module')
@ -511,10 +554,11 @@ def get_modules(records, metric_filter):
@exception_handler() @exception_handler()
@record_filter() @record_filter()
@aggregate_filter() @aggregate_filter()
def get_engineers(records, metric_filter): def get_engineers(records, metric_filter, finalize_handler):
response = _get_aggregated_stats(records, metric_filter, response = _get_aggregated_stats(records, metric_filter,
get_memory_storage().get_user_ids(), get_memory_storage().get_user_ids(),
'user_id', 'author_name') 'user_id', 'author_name',
finalize_handler=finalize_handler)
return json.dumps(response) return json.dumps(response)