diff --git a/greenmine/projects/aggregates/filters.py b/greenmine/projects/aggregates/filters.py new file mode 100644 index 00000000..56188c25 --- /dev/null +++ b/greenmine/projects/aggregates/filters.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- + +from contextlib import closing +from django.db import connection + + +def _get_issues_tags(project): + extra_sql = ("select unnest(unpickle(tags)) as tagname, count(unnest(unpickle(tags))) " + "from issues_issue where project_id = %s " + "group by unnest(unpickle(tags)) " + "order by tagname asc") + + with closing(connection.cursor()) as cursor: + cursor.execute(extra_sql, [project.id]) + rows = cursor.fetchall() + + return dict(rows) + +def _get_issues_statuses(project): + extra_sql = ("select status_id, count(status_id) from issues_issue " + "where project_id = %s group by status_id;") + + extra_sql = """ + select id, (select count(*) from issues_issue + where project_id = m.project_id and status_id = m.id) + from projects_issuestatus as m + where project_id = %s; + """ + + with closing(connection.cursor()) as cursor: + cursor.execute(extra_sql, [project.id]) + rows = cursor.fetchall() + + return dict(rows) + + +def _get_issues_priorities(project): + extra_sql = """ + select id, (select count(*) from issues_issue + where project_id = m.project_id and priority_id = m.id) + from projects_priority as m + where project_id = %s; + """ + + with closing(connection.cursor()) as cursor: + cursor.execute(extra_sql, [project.id]) + rows = cursor.fetchall() + + return dict(rows) + + +def _get_issues_types(project): + extra_sql = """ + select id, (select count(*) from issues_issue + where project_id = m.project_id and type_id = m.id) + from projects_issuetype as m + where project_id = %s; + """ + + with closing(connection.cursor()) as cursor: + cursor.execute(extra_sql, [project.id]) + rows = cursor.fetchall() + + return dict(rows) + + +def _get_issues_severities(project): + extra_sql = """ + select id, (select count(*) from issues_issue + where project_id = m.project_id and severity_id = m.id) + from projects_severity as m + where project_id = %s; + """ + + with closing(connection.cursor()) as cursor: + cursor.execute(extra_sql, [project.id]) + rows = cursor.fetchall() + + return dict(rows) + + +def _get_issues_assigned_to(project): + extra_sql = """ + select user_id, (select count(*) from issues_issue + where project_id = pm.project_id and assigned_to_id = pm.user_id) + from projects_membership as pm + where project_id = %s; + """ + + with closing(connection.cursor()) as cursor: + cursor.execute(extra_sql, [project.id]) + rows = cursor.fetchall() + + return dict(rows) + + +def _get_issues_owners(project): + extra_sql = """ + select user_id, (select count(*) from issues_issue + where project_id = pm.project_id and owner_id = pm.user_id) + from projects_membership as pm + where project_id = %s; + """ + + with closing(connection.cursor()) as cursor: + cursor.execute(extra_sql, [project.id]) + rows = cursor.fetchall() + + return dict(rows) + + +def _get_issues_created_by(project): + extra_sql = """ + select user_id, (select count(*) from issues_issue + where project_id = pm.project_id and owner_id = pm.user_id) + from projects_membership as pm + where project_id = %s; + """ + + with closing(connection.cursor()) as cursor: + cursor.execute(extra_sql, [project.id]) + rows = cursor.fetchall() + + return dict(rows) + + +def get_issues_filters_data(project): + data = { + "owners": _get_issues_owners(project), + "tags": _get_issues_tags(project), + "statuses": _get_issues_statuses(project), + "priorities": _get_issues_priorities(project), + "assigned_to": _get_issues_assigned_to(project), + "created_by": _get_issues_created_by(project), + "types": _get_issues_types(project), + "severities": _get_issues_severities(project), + } + return data diff --git a/greenmine/projects/api.py b/greenmine/projects/api.py index ff733f68..b0cffa46 100644 --- a/greenmine/projects/api.py +++ b/greenmine/projects/api.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -from django.db.models import Q, Count -from django.shortcuts import get_object_or_404 +from django.db.models import Q from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @@ -14,7 +13,9 @@ from greenmine.base.notifications.api import NotificationSenderMixin from . import serializers from . import models from . import permissions + from .aggregates import stats +from .aggregates import filters as filters_aggr class ProjectViewSet(ModelCrudViewSet): @@ -33,6 +34,11 @@ class ProjectViewSet(ModelCrudViewSet): project = self.get_object() return Response(stats.get_stats_for_project_issues(project)) + @detail_route(methods=['get']) + def issue_filters_data(self, request, pk=None): + project = self.get_object() + return Response(filters_aggr.get_issues_filters_data(project)) + def get_queryset(self): qs = super(ProjectViewSet, self).get_queryset() qs = qs.filter(Q(owner=self.request.user) | diff --git a/greenmine/projects/issues/api.py b/greenmine/projects/issues/api.py index 854275a8..42879e7b 100644 --- a/greenmine/projects/issues/api.py +++ b/greenmine/projects/issues/api.py @@ -1,8 +1,12 @@ # -*- coding: utf-8 -*- +import reversion from django.contrib.contenttypes.models import ContentType from rest_framework.permissions import IsAuthenticated +from rest_framework.decorators import list_route +from rest_framework.response import Response +from rest_framework import status from greenmine.base import filters from greenmine.base import exceptions as exc @@ -16,8 +20,6 @@ from . import models from . import permissions from . import serializers -import reversion - class IssueAttachmentViewSet(ModelCrudViewSet): model = Attachment @@ -90,3 +92,4 @@ class IssueViewSet(NotificationSenderMixin, ModelCrudViewSet): # Update the comment in the last version reversion.set_comment(self.request.DATA["comment"]) super().post_save(obj, created) + diff --git a/greenmine/projects/models.py b/greenmine/projects/models.py index 6598f9af..2a40a444 100644 --- a/greenmine/projects/models.py +++ b/greenmine/projects/models.py @@ -161,10 +161,6 @@ class Project(models.Model): role_model = get_model("users", "Role") return role_model.objects.all() - # TODO: do not remove this - # return role_model.objects.filter(id__in=list(self.memberships.values_list( - # "role", flat=True))) - def get_users(self): user_model = get_user_model() return user_model.objects.filter( diff --git a/sql/tags.sql b/sql/tags.sql index 368d0b28..4a40a38d 100644 --- a/sql/tags.sql +++ b/sql/tags.sql @@ -1,3 +1,5 @@ +CREATE INDEX issues_unpickle_tags_index ON issues_issue USING btree (unpickle(tags)); + CREATE OR REPLACE FUNCTION unpickle (data text) RETURNS text[] AS $$ @@ -7,18 +9,18 @@ AS $$ return pickle.loads(base64.b64decode(data)) $$ LANGUAGE plpythonu; -CREATE OR REPLACE FUNCTION array_uniq_join (data text[], data2 text[]) - RETURNS text[] -AS $$ - tmp = set(data) - tmp.update(data2) - return tuple(tmp) -$$ LANGUAGE plpythonu; - -DROP AGGREGATE array_uniq_concat (text[]); -CREATE AGGREGATE array_uniq_concat (text[]) -( - sfunc = array_uniq_join, - stype = text[], - initcond = '{}' -); +-- CREATE OR REPLACE FUNCTION array_uniq_join (data text[], data2 text[]) +-- RETURNS text[] +-- AS $$ +-- tmp = set(data) +-- tmp.update(data2) +-- return tuple(tmp) +-- $$ LANGUAGE plpythonu; +-- +-- DROP AGGREGATE array_uniq_concat (text[]); +-- CREATE AGGREGATE array_uniq_concat (text[]) +-- ( +-- sfunc = array_uniq_join, +-- stype = text[], +-- initcond = '{}' +-- );