Adding member stats to the API
parent
fa1e0c6bf6
commit
e7b963a674
|
@ -80,6 +80,12 @@ class ProjectViewSet(ModelCrudViewSet):
|
|||
self.check_permissions(request, 'stats', project)
|
||||
return Response(services.get_stats_for_project(project))
|
||||
|
||||
@detail_route(methods=['get'])
|
||||
def member_stats(self, request, pk=None):
|
||||
project = self.get_object()
|
||||
self.check_permissions(request, 'member_stats', project)
|
||||
return Response(services.get_member_stats_for_project(project))
|
||||
|
||||
@detail_route(methods=['get'])
|
||||
def issues_stats(self, request, pk=None):
|
||||
project = self.get_object()
|
||||
|
|
|
@ -28,6 +28,7 @@ class ProjectPermission(TaigaResourcePermission):
|
|||
modules_perms = IsProjectOwner()
|
||||
list_perms = AllowAny()
|
||||
stats_perms = AllowAny()
|
||||
member_stats_perms = HasProjectPerm('view_project')
|
||||
star_perms = IsAuthenticated()
|
||||
unstar_perms = IsAuthenticated()
|
||||
issues_stats_perms = AllowAny()
|
||||
|
|
|
@ -30,6 +30,7 @@ from .filters import get_issues_filters_data
|
|||
|
||||
from .stats import get_stats_for_project_issues
|
||||
from .stats import get_stats_for_project
|
||||
from .stats import get_member_stats_for_project
|
||||
|
||||
from .members import create_members_in_bulk
|
||||
from .members import get_members_from_bulk
|
||||
|
|
|
@ -18,6 +18,8 @@ from django.db.models import Q, Count
|
|||
import datetime
|
||||
import copy
|
||||
|
||||
from taiga.projects.history.models import HistoryEntry
|
||||
|
||||
|
||||
def _get_milestones_stats_for_backlog(project):
|
||||
"""
|
||||
|
@ -212,3 +214,77 @@ def get_stats_for_project(project):
|
|||
'speed': speed,
|
||||
}
|
||||
return project_stats
|
||||
|
||||
|
||||
def _get_closed_bugs_per_member_stats(project):
|
||||
# Closed bugs per user
|
||||
closed_bugs = project.issues.filter(status__is_closed=True)\
|
||||
.values('assigned_to')\
|
||||
.annotate(count=Count('assigned_to'))\
|
||||
.order_by()
|
||||
closed_bugs = { p["assigned_to"]: p["count"] for p in closed_bugs}
|
||||
return closed_bugs
|
||||
|
||||
|
||||
def _get_iocaine_tasks_per_member_stats(project):
|
||||
# Iocaine tasks assigned per user
|
||||
iocaine_tasks = project.tasks.filter(is_iocaine=True)\
|
||||
.values('assigned_to')\
|
||||
.annotate(count=Count('assigned_to'))\
|
||||
.order_by()
|
||||
iocaine_tasks = { t["assigned_to"]: t["count"] for t in iocaine_tasks}
|
||||
return iocaine_tasks
|
||||
|
||||
|
||||
def _get_wiki_changes_per_member_stats(project):
|
||||
# Wiki changes
|
||||
wiki_changes = {}
|
||||
wiki_page_keys = ["wiki.wikipage:%s"%id for id in project.wiki_pages.values_list("id", flat=True)]
|
||||
history_entries = HistoryEntry.objects.filter(key__in=wiki_page_keys).values('user')
|
||||
for entry in history_entries:
|
||||
editions = wiki_changes.get(entry["user"]["pk"], 0)
|
||||
wiki_changes[entry["user"]["pk"]] = editions + 1
|
||||
|
||||
return wiki_changes
|
||||
|
||||
|
||||
def _get_created_bugs_per_member_stats(project):
|
||||
# Created_bugs
|
||||
created_bugs = project.issues\
|
||||
.values('owner')\
|
||||
.annotate(count=Count('owner'))\
|
||||
.order_by()
|
||||
created_bugs = { p["owner"]: p["count"] for p in created_bugs }
|
||||
return created_bugs
|
||||
|
||||
|
||||
def _get_closed_tasks_per_member_stats(project):
|
||||
# Closed tasks
|
||||
closed_tasks = project.tasks.filter(status__is_closed=True)\
|
||||
.values('assigned_to')\
|
||||
.annotate(count=Count('assigned_to'))\
|
||||
.order_by()
|
||||
closed_tasks = {p["assigned_to"]: p["count"] for p in closed_tasks}
|
||||
return closed_tasks
|
||||
|
||||
def get_member_stats_for_project(project):
|
||||
base_counters = {id: 0 for id in project.members.values_list("id", flat=True)}
|
||||
closed_bugs = base_counters.copy()
|
||||
closed_bugs.update(_get_closed_bugs_per_member_stats(project))
|
||||
iocaine_tasks = base_counters.copy()
|
||||
iocaine_tasks.update(_get_iocaine_tasks_per_member_stats(project))
|
||||
wiki_changes = base_counters.copy()
|
||||
wiki_changes.update(_get_wiki_changes_per_member_stats(project))
|
||||
created_bugs = base_counters.copy()
|
||||
created_bugs.update(_get_created_bugs_per_member_stats(project))
|
||||
closed_tasks = base_counters.copy()
|
||||
closed_tasks.update(_get_closed_tasks_per_member_stats(project))
|
||||
|
||||
member_stats = {
|
||||
"closed_bugs": closed_bugs,
|
||||
"iocaine_tasks": iocaine_tasks,
|
||||
"wiki_changes": wiki_changes,
|
||||
"created_bugs": created_bugs,
|
||||
"closed_tasks": closed_tasks,
|
||||
}
|
||||
return member_stats
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from django.core.urlresolvers import reverse
|
||||
from taiga.base.utils import json
|
||||
from taiga.projects.services import stats as stats_services
|
||||
from taiga.projects.history.services import take_snapshot
|
||||
|
||||
from .. import factories as f
|
||||
|
||||
|
@ -84,6 +86,7 @@ def test_issue_status_slug_generation(client):
|
|||
assert response.status_code == 200
|
||||
assert response.data["slug"] == "new-status"
|
||||
|
||||
|
||||
def test_points_name_duplicated(client):
|
||||
point_1 = f.PointsFactory()
|
||||
point_2 = f.PointsFactory(project=point_1.project)
|
||||
|
@ -96,9 +99,64 @@ def test_points_name_duplicated(client):
|
|||
assert response.status_code == 400
|
||||
assert response.data["name"][0] == "Name duplicated for the project"
|
||||
|
||||
|
||||
def test_update_points_when_not_null_values_for_points(client):
|
||||
points = f.PointsFactory(name="?", value="6")
|
||||
role = f.RoleFactory(project=points.project, computable=True)
|
||||
assert points.project.points.filter(value__isnull=True).count() == 0
|
||||
points.project.update_role_points()
|
||||
assert points.project.points.filter(value__isnull=True).count() == 1
|
||||
|
||||
|
||||
def test_get_closed_bugs_per_member_stats():
|
||||
project = f.ProjectFactory()
|
||||
membership_1 = f.MembershipFactory(project=project)
|
||||
membership_2 = f.MembershipFactory(project=project)
|
||||
issue_closed_status = f.IssueStatusFactory(is_closed=True, project=project)
|
||||
issue_open_status = f.IssueStatusFactory(is_closed=False, project=project)
|
||||
issue_closed = f.IssueFactory(project=project,
|
||||
status=issue_closed_status,
|
||||
owner=membership_1.user,
|
||||
assigned_to=membership_1.user)
|
||||
issue_open = f.IssueFactory(project=project,
|
||||
status=issue_open_status,
|
||||
owner=membership_2.user,
|
||||
assigned_to=membership_2.user)
|
||||
task_closed_status = f.TaskStatusFactory(is_closed=True, project=project)
|
||||
task_open_status = f.TaskStatusFactory(is_closed=False, project=project)
|
||||
task_closed = f.TaskFactory(project=project,
|
||||
status=task_closed_status,
|
||||
owner=membership_1.user,
|
||||
assigned_to=membership_1.user)
|
||||
task_open = f.TaskFactory(project=project,
|
||||
status=task_open_status,
|
||||
owner=membership_2.user,
|
||||
assigned_to=membership_2.user)
|
||||
task_iocaine = f.TaskFactory(project=project,
|
||||
status=task_open_status,
|
||||
owner=membership_2.user,
|
||||
assigned_to=membership_2.user,
|
||||
is_iocaine=True)
|
||||
|
||||
wiki_page = f.WikiPageFactory.create(project=project, owner=membership_1.user)
|
||||
take_snapshot(wiki_page, user=membership_1.user)
|
||||
wiki_page.content="Frontend, future"
|
||||
wiki_page.save()
|
||||
take_snapshot(wiki_page, user=membership_1.user)
|
||||
|
||||
stats = stats_services.get_member_stats_for_project(project)
|
||||
|
||||
assert stats["closed_bugs"][membership_1.user.id] == 1
|
||||
assert stats["closed_bugs"][membership_2.user.id] == 0
|
||||
|
||||
assert stats["iocaine_tasks"][membership_1.user.id] == 0
|
||||
assert stats["iocaine_tasks"][membership_2.user.id] == 1
|
||||
|
||||
assert stats["wiki_changes"][membership_1.user.id] == 2
|
||||
assert stats["wiki_changes"][membership_2.user.id] == 0
|
||||
|
||||
assert stats["created_bugs"][membership_1.user.id] == 1
|
||||
assert stats["created_bugs"][membership_2.user.id] == 1
|
||||
|
||||
assert stats["closed_tasks"][membership_1.user.id] == 1
|
||||
assert stats["closed_tasks"][membership_2.user.id] == 0
|
||||
|
|
Loading…
Reference in New Issue