Skip to content

Commit

Permalink
Adding new functionality of visualizing which reports are related to …
Browse files Browse the repository at this point in the history
…a specific metric
Ederporto committed Nov 10, 2023
1 parent 7359394 commit b812fd3
Showing 5 changed files with 249 additions and 57 deletions.
4 changes: 3 additions & 1 deletion metrics/templates/metrics/list_metrics_per_project.html
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ <h2 id="{{ project_key }}">{{ project_value.project }}</h2>
data-filter-control="true">
<thead>
<tr>
<th data-field="actions">{% trans "Actions" %}</th>
<th data-field="id">{% trans "ID" %}</th>
<th data-field="metric">{% trans "Metric" %}</th>
<th data-field="done">{% trans "Done" %}</th>
@@ -42,10 +43,11 @@ <h2 id="{{ project_key }}">{{ project_value.project }}</h2>
</thead>
<tbody>
{% for activity in project_value.project_metrics|dictsort:"activity_id" %}
<tr style="background-color: var(--dark-grey-color);"><td colspan="5" style="color: var(--light-color);">{{ activity.activity }}</td></tr>
<tr style="background-color: var(--dark-grey-color);"><td colspan="6" style="color: var(--light-color);">{{ activity.activity }}</td></tr>
{% for metric_key, metric_value in activity.activity_metrics.items %}
{% for numeric_key, numeric_value in metric_value.metrics.items %}
<tr>
<th scope="row" style="text-align: center;"><a title="{% trans 'View' %}" href="{% url 'metrics:metrics_reports' metric_id=metric_key %}"><button title="{% trans 'View' %}" type="button" class="btn-circle btn-view"><i class="fa-solid fa-eye"></i></button></a></th>
<th scope="row">{{ metric_key }}</th>
<td>{{ metric_value.title }}<br><small>({% translate numeric_key %})</small></td>
<td>{{ numeric_value.done|bool_yesno }}</td>
69 changes: 69 additions & 0 deletions metrics/templates/metrics/list_metrics_reports.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block title %}{{ title }}{% endblock %}
{% load metricstags %}

{% block scripts %}
<script src="https://tools-static.wmflabs.org/cdnjs/ajax/libs/bootstrap-table/1.21.2/bootstrap-table.min.js"></script>
<script src="https://tools-static.wmflabs.org/cdnjs/ajax/libs/bootstrap-table/1.21.2/extensions/mobile/bootstrap-table-mobile.min.js"></script>
<script src="https://tools-static.wmflabs.org/cdnjs/ajax/libs/bootstrap-table/1.21.2/extensions/filter-control/bootstrap-table-filter-control.min.js"></script>
{% endblock %}
{% block styles %}
<link rel="stylesheet" href="https://tools-static.wmflabs.org/cdnjs/ajax/libs/bootstrap/5.2.3/css/bootstrap.min.css">
<link rel="stylesheet" href="https://tools-static.wmflabs.org/cdnjs/ajax/libs/bootstrap-table/1.21.2/bootstrap-table.min.css">
<link rel="stylesheet" type="text/css" href="{% static 'css/forms.css' %}">
{% endblock %}
{% block banner %}{% endblock %}
{% block content %}
<div class="w3-row">
<div class="w3-container userform" style="background-color: var(--light-color); color:black;">
<h1>{{ metric.text }}</h1>
{% for metric_goal in values %}
<h2>{{ metric_goal.text }}</h2>
<div class="w3-container">
<p><b>Goal:</b> {{ metric_goal.goal }}</p>
<p><b>Done:</b> {{ metric_goal.done }}</p>
</div>
<table id="reports_{{ metric.id }}" class="table table-striped table-fixed table-sara metrics_table"
data-mobile-responsive="true"
data-check-on-init="true"
data-filter-control="true">
<thead>
<tr>
<th data-field="id" data-sortable="true">{% trans "ID" %}</th>
<th data-field="report" data-sortable="true">{% trans "Report" %}</th>
<th data-field="done" data-sortable="true">{% trans "Done" %}</th>
<th data-field="goal" data-sortable="true">{% trans "Goal" %}</th>
</tr>
</thead>
<tbody>
{% for report in metric_goal.reports %}
<tr>
<th scope="row">{{ report.id }}</th>
<td><a title="{% trans 'View' %}" href="{% url 'report:detail_report' report_id=report.id %}">{{ report.description }}</a><br><small>({{ report.initial_date }} - {{ report.end_date }})</small></td>
<td>{{ report.done }}</td>
<td>{{ report.done|perc:metric_goal.goal }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endfor %}
</div>
</div>
<script>
$(function () {
$('.metrics_table').bootstrapTable();
})

function datesSorter(a, b) {
if (new Date(a) < new Date(b)) return 1;
if (new Date(a) > new Date(b)) return -1;
return 0;
}

$(function (){
$('[class*="bootstrap-table-filter-control-"]').wrap("<label style='width: 100%'></label>");
})
</script>
{% endblock %}
68 changes: 68 additions & 0 deletions metrics/tests.py
Original file line number Diff line number Diff line change
@@ -253,6 +253,74 @@ def test_update_metrics(self):

self.assertRedirects(response, reverse('metrics:per_project'))

def test_do_not_show_reports_associated_to_a_metric_to_users_without_permission(self):
self.user.user_permissions.remove(self.view_metrics_permission)
self.client.login(username=self.username, password=self.password)

area = Area.objects.create(text="Area")
activity = Activity.objects.create(text="Activity", area=area)
metric = Metric.objects.create(text="Metric 1", activity=activity, number_of_editors=10)

url = reverse("metrics:metrics_reports", args=[metric.id])
response = self.client.get(url)

self.assertEqual(response.status_code, 302)
self.assertRedirects(response, f"{reverse('login')}?next={url}")

def test_show_reports_associated_to_a_metric_only_to_users_with_permission(self):
self.client.login(username=self.username, password=self.password)

area = Area.objects.create(text="Area")
activity = Activity.objects.create(text="Activity", area=area)
metric = Metric.objects.create(text="Metric 1", activity=activity, number_of_editors=10)
team_area = TeamArea.objects.create(text="Area")
report_1 = Report.objects.create(
created_by=self.user_profile,
modified_by=self.user_profile,
activity_associated=activity,
area_responsible=team_area,
initial_date=datetime.now().date(),
end_date=datetime.now().date() + timedelta(days=1),
description="Report 1",
links="https://testlink.com",
learning="Learning" * 60,
)
report_1.metrics_related.add(metric)
report_1.save()

url = reverse("metrics:metrics_reports", args=[metric.id])
response = self.client.get(url)

self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "metrics/list_metrics_reports.html")

def test_redirects_to_list_of_metrics_per_project_if_metric_id_does_not_exist(self):
self.client.login(username=self.username, password=self.password)

area = Area.objects.create(text="Area")
activity = Activity.objects.create(text="Activity", area=area)
metric = Metric.objects.create(text="Metric 1", activity=activity, number_of_editors=10)
team_area = TeamArea.objects.create(text="Area")
report_1 = Report.objects.create(
created_by=self.user_profile,
modified_by=self.user_profile,
activity_associated=activity,
area_responsible=team_area,
initial_date=datetime.now().date(),
end_date=datetime.now().date() + timedelta(days=1),
description="Report 1",
links="https://testlink.com",
learning="Learning" * 60,
)
report_1.metrics_related.add(metric)
report_1.save()

url = reverse("metrics:metrics_reports", args=[123456789])
response = self.client.get(url)

self.assertEqual(response.status_code, 302)
self.assertRedirects(response, f"{reverse('metrics:per_project')}")


class MetricFunctionsTests(TestCase):
def setUp(self):
3 changes: 2 additions & 1 deletion metrics/urls.py
Original file line number Diff line number Diff line change
@@ -10,5 +10,6 @@
path('activities_plan', views.show_activities_plan, name='show_activities'),
path('metrics', views.show_metrics, name='metrics'),
path('metrics_per_project', views.show_metrics_per_project, name='per_project'),
path('update_metrics', views.update_metrics_relations, name='update_metrics')
path('update_metrics', views.update_metrics_relations, name='update_metrics'),
path('metrics_reports/<int:metric_id>', views.metrics_reports, name='metrics_reports'),
]
162 changes: 107 additions & 55 deletions metrics/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import calendar
from django.shortcuts import render, redirect, reverse, HttpResponse
from django.utils.translation import gettext as _
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q, Count, Sum, F
from .models import Activity, Area, Metric
from report.models import Report, Editor, Organizer, Partner, Project, Funding
@@ -70,61 +71,7 @@ def get_metrics_and_aggregate_per_project():
else:
q_filter = Q(project=project)
for metric in Metric.objects.filter(q_filter):
reports = Report.objects.filter(metrics_related__in=[metric])
goal = {
"Wikipedia": metric.wikipedia_created + metric.wikipedia_edited,
"Wikimedia Commons": metric.commons_created + metric.commons_edited,
"Wikidata": metric.wikidata_created + metric.wikidata_edited,
"Wikiversity": metric.wikiversity_created + metric.wikiversity_edited,
"Wikibooks": metric.wikibooks_created + metric.wikibooks_edited,
"Wikisource": metric.wikisource_created + metric.wikisource_edited,
"Wikinews": metric.wikinews_created + metric.wikinews_edited,
"Wikiquote": metric.wikiquote_created + metric.wikiquote_edited,
"Wiktionary": metric.wiktionary_created + metric.wiktionary_edited,
"Wikivoyage": metric.wikivoyage_created + metric.wikivoyage_edited,
"Wikispecies": metric.wikispecies_created + metric.wikispecies_edited,
"MetaWiki": metric.metawiki_created + metric.metawiki_edited,
"MediaWiki": metric.mediawiki_created + metric.mediawiki_edited,
"Number of participants": metric.number_of_participants,
"Number of resources": metric.number_of_resources,
"Number of feedbacks": metric.number_of_feedbacks,
"Number of events": metric.number_of_events,
"Number of editors": metric.number_of_editors,
"Number of editors retained": metric.number_of_editors_retained,
"Number of new editors": metric.number_of_new_editors,
"Number of partnerships": metric.number_of_partnerships,
"Number of organizers": metric.number_of_organizers,
"Number of organizers retained": metric.number_of_organizers_retained,
"Number of people reached through social media": metric.number_of_people_reached_through_social_media,
"Occurence": metric.boolean_type,
}
done = {
"Wikipedia": reports.aggregate(total=Sum(F("wikipedia_created") + F("wikipedia_edited")))["total"] or 0,
"Wikimedia Commons": reports.aggregate(total=Sum(F("commons_created") + F("commons_edited")))["total"] or 0,
"Wikidata": reports.aggregate(total=Sum(F("wikidata_created") + F("wikidata_edited")))["total"] or 0,
"Wikiversity": reports.aggregate(total=Sum(F("wikiversity_created") + F("wikiversity_edited")))["total"] or 0,
"Wikibooks": reports.aggregate(total=Sum(F("wikibooks_created") + F("wikibooks_edited")))["total"] or 0,
"Wikisource": reports.aggregate(total=Sum(F("wikisource_created") + F("wikisource_edited")))["total"] or 0,
"Wikinews": reports.aggregate(total=Sum(F("wikinews_created") + F("wikinews_edited")))["total"] or 0,
"Wikiquote": reports.aggregate(total=Sum(F("wikiquote_created") + F("wikiquote_edited")))["total"] or 0,
"Wiktionary": reports.aggregate(total=Sum(F("wiktionary_created") + F("wiktionary_edited")))["total"] or 0,
"Wikivoyage": reports.aggregate(total=Sum(F("wikivoyage_created") + F("wikivoyage_edited")))["total"] or 0,
"Wikispecies": reports.aggregate(total=Sum(F("wikispecies_created") + F("wikispecies_edited")))["total"] or 0,
"MetaWiki": reports.aggregate(total=Sum(F("metawiki_created") + F("metawiki_edited")))["total"] or 0,
"MediaWiki": reports.aggregate(total=Sum(F("mediawiki_created") + F("mediawiki_edited")))["total"] or 0,
"Number of participants": reports.aggregate(total=Sum("participants"))["total"] or 0,
"Number of resources": reports.aggregate(total=Sum("resources"))["total"] or 0,
"Number of feedbacks": reports.aggregate(total=Sum("feedbacks"))["total"] or 0,
"Number of events": reports.count() or 0,
"Number of editors": Editor.objects.filter(editors__in=reports).distinct().count() or 0,
"Number of editors retained": Editor.objects.filter(retained=True, editors__in=reports).distinct().count() or 0,
"Number of new editors": Editor.objects.filter(editors__in=reports, account_creation_date__gte=F('editors__initial_date')).count() or 0,
"Number of partnerships": Partner.objects.filter(partners__in=reports).distinct().count() or 0,
"Number of organizers": Organizer.objects.filter(organizers__in=reports).distinct().count() or 0,
"Number of organizers retained": Organizer.objects.filter(retained=True, organizers__in=reports).distinct().count() or 0,
"Number of people reached through social media": reports.aggregate(total=Sum(F("number_of_people_reached_through_social_media")))["total"] or 0,
"Occurence": reports.filter(metrics_related__boolean_type=True).exists() or False,
}
goal, done = get_goal_and_done_for_metric(metric)

result_metrics = {key: {"goal": value, "done": done[key]} for key, value in goal.items() if value != 0}
activity_metrics[metric.id] = {
@@ -147,6 +94,74 @@ def get_metrics_and_aggregate_per_project():
return aggregated_metrics_and_results


def get_goal_and_done_for_metric(metric):
reports = Report.objects.filter(metrics_related__in=[metric])
goal = get_goal_for_metric(metric)
done = get_done_for_report(reports)

return goal, done


def get_goal_for_metric(metric):
return {
"Wikipedia": metric.wikipedia_created + metric.wikipedia_edited,
"Wikimedia Commons": metric.commons_created + metric.commons_edited,
"Wikidata": metric.wikidata_created + metric.wikidata_edited,
"Wikiversity": metric.wikiversity_created + metric.wikiversity_edited,
"Wikibooks": metric.wikibooks_created + metric.wikibooks_edited,
"Wikisource": metric.wikisource_created + metric.wikisource_edited,
"Wikinews": metric.wikinews_created + metric.wikinews_edited,
"Wikiquote": metric.wikiquote_created + metric.wikiquote_edited,
"Wiktionary": metric.wiktionary_created + metric.wiktionary_edited,
"Wikivoyage": metric.wikivoyage_created + metric.wikivoyage_edited,
"Wikispecies": metric.wikispecies_created + metric.wikispecies_edited,
"MetaWiki": metric.metawiki_created + metric.metawiki_edited,
"MediaWiki": metric.mediawiki_created + metric.mediawiki_edited,
"Number of participants": metric.number_of_participants,
"Number of resources": metric.number_of_resources,
"Number of feedbacks": metric.number_of_feedbacks,
"Number of events": metric.number_of_events,
"Number of editors": metric.number_of_editors,
"Number of editors retained": metric.number_of_editors_retained,
"Number of new editors": metric.number_of_new_editors,
"Number of partnerships": metric.number_of_partnerships,
"Number of organizers": metric.number_of_organizers,
"Number of organizers retained": metric.number_of_organizers_retained,
"Number of people reached through social media": metric.number_of_people_reached_through_social_media,
"Occurence": metric.boolean_type,
}


def get_done_for_report(reports):
return {
"Wikipedia": reports.aggregate(total=Sum(F("wikipedia_created") + F("wikipedia_edited")))["total"] or 0,
"Wikimedia Commons": reports.aggregate(total=Sum(F("commons_created") + F("commons_edited")))["total"] or 0,
"Wikidata": reports.aggregate(total=Sum(F("wikidata_created") + F("wikidata_edited")))["total"] or 0,
"Wikiversity": reports.aggregate(total=Sum(F("wikiversity_created") + F("wikiversity_edited")))["total"] or 0,
"Wikibooks": reports.aggregate(total=Sum(F("wikibooks_created") + F("wikibooks_edited")))["total"] or 0,
"Wikisource": reports.aggregate(total=Sum(F("wikisource_created") + F("wikisource_edited")))["total"] or 0,
"Wikinews": reports.aggregate(total=Sum(F("wikinews_created") + F("wikinews_edited")))["total"] or 0,
"Wikiquote": reports.aggregate(total=Sum(F("wikiquote_created") + F("wikiquote_edited")))["total"] or 0,
"Wiktionary": reports.aggregate(total=Sum(F("wiktionary_created") + F("wiktionary_edited")))["total"] or 0,
"Wikivoyage": reports.aggregate(total=Sum(F("wikivoyage_created") + F("wikivoyage_edited")))["total"] or 0,
"Wikispecies": reports.aggregate(total=Sum(F("wikispecies_created") + F("wikispecies_edited")))["total"] or 0,
"MetaWiki": reports.aggregate(total=Sum(F("metawiki_created") + F("metawiki_edited")))["total"] or 0,
"MediaWiki": reports.aggregate(total=Sum(F("mediawiki_created") + F("mediawiki_edited")))["total"] or 0,
"Number of participants": reports.aggregate(total=Sum("participants"))["total"] or 0,
"Number of resources": reports.aggregate(total=Sum("resources"))["total"] or 0,
"Number of feedbacks": reports.aggregate(total=Sum("feedbacks"))["total"] or 0,
"Number of events": reports.count() or 0,
"Number of editors": Editor.objects.filter(editors__in=reports).distinct().count() or 0,
"Number of editors retained": Editor.objects.filter(retained=True, editors__in=reports).distinct().count() or 0,
"Number of new editors": Editor.objects.filter(editors__in=reports, account_creation_date__gte=F('editors__initial_date')).count() or 0,
"Number of partnerships": Partner.objects.filter(partners__in=reports).distinct().count() or 0,
"Number of organizers": Organizer.objects.filter(organizers__in=reports).distinct().count() or 0,
"Number of organizers retained": Organizer.objects.filter(retained=True, organizers__in=reports).distinct().count() or 0,
"Number of people reached through social media": reports.aggregate(total=Sum(F("number_of_people_reached_through_social_media")))["total"] or 0,
"Occurence": reports.filter(metrics_related__boolean_type=True).exists() or False,
}


def get_aggregated_metrics_data(project=None):
total_sum = {}

@@ -377,3 +392,40 @@ def update_metrics_relations(request):
report.save()

return redirect(reverse("metrics:per_project"))


@login_required
@permission_required("metrics.view_metric")
def metrics_reports(request, metric_id):
try:
metric = Metric.objects.get(pk=metric_id)
reports = Report.objects.filter(metrics_related=metric_id).order_by("pk")

goals = get_goal_for_metric(metric)
filtered_goals = {key: value for key, value in goals.items() if goals[key] > 0}

values = []
for goal_key, goal_value in filtered_goals.items():
report_values = []
for report in reports:
done = get_done_for_report(Report.objects.filter(pk=report.id))
report_values.append({
"id": report.id,
"description": report.description,
"initial_date": report.initial_date,
"end_date": report.end_date,
"done": done[goal_key],
})
values.append({
"text": goal_key,
"goal": goal_value,
"done": sum([report_aux["done"] for report_aux in report_values]),
"reports": report_values
})

context = {"metric": metric, "values": values}

return render(request, "metrics/list_metrics_reports.html", context)
except ObjectDoesNotExist:
return redirect(reverse('metrics:per_project'))

0 comments on commit b812fd3

Please sign in to comment.