Filter by epics

remotes/origin/issue/4795/notification_even_they_are_disabled
Alejandro Alonso 2016-09-22 12:28:24 +02:00
parent 19cd0c5354
commit ae0bc7ee88
4 changed files with 178 additions and 101 deletions

View File

@ -59,7 +59,6 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
queryset = models.UserStory.objects.all() queryset = models.UserStory.objects.all()
permission_classes = (permissions.UserStoryPermission,) permission_classes = (permissions.UserStoryPermission,)
filter_backends = (base_filters.CanViewUsFilterBackend, filter_backends = (base_filters.CanViewUsFilterBackend,
filters.EpicsFilter,
filters.EpicFilter, filters.EpicFilter,
base_filters.OwnersFilter, base_filters.OwnersFilter,
base_filters.AssignedToFilter, base_filters.AssignedToFilter,
@ -105,6 +104,12 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
include_attachments = "include_attachments" in self.request.QUERY_PARAMS include_attachments = "include_attachments" in self.request.QUERY_PARAMS
include_tasks = "include_tasks" in self.request.QUERY_PARAMS include_tasks = "include_tasks" in self.request.QUERY_PARAMS
epic_id = self.request.QUERY_PARAMS.get("epic", None) epic_id = self.request.QUERY_PARAMS.get("epic", None)
# We can be filtering by more than one epic so epic_id can consist
# of different ids separete by comma. In that situation we will use
# only the first
if epic_id is not None:
epic_id = epic_id.split(",")[0]
qs = attach_extra_info(qs, user=self.request.user, qs = attach_extra_info(qs, user=self.request.user,
include_attachments=include_attachments, include_attachments=include_attachments,
include_tasks=include_tasks, include_tasks=include_tasks,
@ -278,7 +283,7 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
statuses_filter_backends = (f for f in filter_backends if f != base_filters.StatusesFilter) statuses_filter_backends = (f for f in filter_backends if f != base_filters.StatusesFilter)
assigned_to_filter_backends = (f for f in filter_backends if f != base_filters.AssignedToFilter) assigned_to_filter_backends = (f for f in filter_backends if f != base_filters.AssignedToFilter)
owners_filter_backends = (f for f in filter_backends if f != base_filters.OwnersFilter) owners_filter_backends = (f for f in filter_backends if f != base_filters.OwnersFilter)
epics_filter_backends = (f for f in filter_backends if f != filters.EpicsFilter) epics_filter_backends = (f for f in filter_backends if f != filters.EpicFilter)
queryset = self.get_queryset() queryset = self.get_queryset()
querysets = { querysets = {

View File

@ -23,7 +23,3 @@ from taiga.base import filters
class EpicFilter(filters.BaseRelatedFieldsFilter): class EpicFilter(filters.BaseRelatedFieldsFilter):
filter_name = "epics" filter_name = "epics"
param_name = "epic" param_name = "epic"
class EpicsFilter(filters.BaseRelatedFieldsFilter):
filter_name = "epics"

View File

@ -273,16 +273,29 @@ def _get_userstories_statuses(project, queryset):
where_params = queryset_where_tuple[1] where_params = queryset_where_tuple[1]
extra_sql = """ extra_sql = """
WITH "us_counters" AS (
SELECT DISTINCT "userstories_userstory"."status_id" "status_id",
"userstories_userstory"."id" "us_id"
FROM "userstories_userstory"
LEFT JOIN "epics_relateduserstory"
ON "userstories_userstory"."id" = "epics_relateduserstory"."user_story_id"
WHERE {where}
),
"counters" AS (
SELECT "status_id",
COUNT("status_id") "count"
FROM "us_counters"
GROUP BY "status_id"
)
SELECT "projects_userstorystatus"."id", SELECT "projects_userstorystatus"."id",
"projects_userstorystatus"."name", "projects_userstorystatus"."name",
"projects_userstorystatus"."color", "projects_userstorystatus"."color",
"projects_userstorystatus"."order", "projects_userstorystatus"."order",
(SELECT count(*) COALESCE("counters"."count", 0)
FROM "userstories_userstory"
INNER JOIN "projects_project" ON
("userstories_userstory"."project_id" = "projects_project"."id")
WHERE {where} AND "userstories_userstory"."status_id" = "projects_userstorystatus"."id")
FROM "projects_userstorystatus" FROM "projects_userstorystatus"
LEFT JOIN "counters"
ON "counters"."status_id" = "projects_userstorystatus"."id"
WHERE "projects_userstorystatus"."project_id" = %s WHERE "projects_userstorystatus"."project_id" = %s
ORDER BY "projects_userstorystatus"."order"; ORDER BY "projects_userstorystatus"."order";
""".format(where=where) """.format(where=where)
@ -310,31 +323,47 @@ def _get_userstories_assigned_to(project, queryset):
where_params = queryset_where_tuple[1] where_params = queryset_where_tuple[1]
extra_sql = """ extra_sql = """
WITH counters AS ( WITH "us_counters" AS (
SELECT assigned_to_id, count(assigned_to_id) count SELECT DISTINCT "userstories_userstory"."assigned_to_id" "assigned_to_id",
"userstories_userstory"."id" "us_id"
FROM "userstories_userstory" FROM "userstories_userstory"
INNER JOIN "projects_project" ON ("userstories_userstory"."project_id" = "projects_project"."id") LEFT JOIN "epics_relateduserstory"
WHERE {where} AND "userstories_userstory"."assigned_to_id" IS NOT NULL ON "userstories_userstory"."id" = "epics_relateduserstory"."user_story_id"
GROUP BY assigned_to_id WHERE {where}
),
"counters" AS (
SELECT "assigned_to_id",
COUNT("assigned_to_id")
FROM "us_counters"
GROUP BY "assigned_to_id"
) )
SELECT "projects_membership"."user_id" user_id, SELECT "projects_membership"."user_id" "user_id",
"users_user"."full_name", "users_user"."full_name" "full_name",
"users_user"."username", "users_user"."username" "username",
COALESCE("counters".count, 0) count COALESCE("counters".count, 0) "count"
FROM projects_membership FROM "projects_membership"
LEFT OUTER JOIN counters ON ("projects_membership"."user_id" = "counters"."assigned_to_id") LEFT OUTER JOIN "counters"
INNER JOIN "users_user" ON ("projects_membership"."user_id" = "users_user"."id") ON ("projects_membership"."user_id" = "counters"."assigned_to_id")
INNER JOIN "users_user"
ON ("projects_membership"."user_id" = "users_user"."id")
WHERE "projects_membership"."project_id" = %s AND "projects_membership"."user_id" IS NOT NULL WHERE "projects_membership"."project_id" = %s AND "projects_membership"."user_id" IS NOT NULL
-- unassigned userstories -- unassigned userstories
UNION UNION
SELECT NULL user_id, NULL, NULL, count(coalesce(assigned_to_id, -1)) count SELECT NULL "user_id",
NULL "full_name",
NULL "username",
count(coalesce("assigned_to_id", -1)) "count"
FROM "userstories_userstory" FROM "userstories_userstory"
INNER JOIN "projects_project" ON ("userstories_userstory"."project_id" = "projects_project"."id") INNER JOIN "projects_project"
ON ("userstories_userstory"."project_id" = "projects_project"."id")
LEFT JOIN "epics_relateduserstory"
ON ("userstories_userstory"."id" = "epics_relateduserstory"."user_story_id")
WHERE {where} AND "userstories_userstory"."assigned_to_id" IS NULL WHERE {where} AND "userstories_userstory"."assigned_to_id" IS NULL
GROUP BY assigned_to_id GROUP BY "assigned_to_id"
""".format(where=where) """.format(where=where)
with closing(connection.cursor()) as cursor: with closing(connection.cursor()) as cursor:
@ -371,33 +400,43 @@ def _get_userstories_owners(project, queryset):
where_params = queryset_where_tuple[1] where_params = queryset_where_tuple[1]
extra_sql = """ extra_sql = """
WITH counters AS ( WITH "us_counters" AS(
SELECT "userstories_userstory"."owner_id" owner_id, SELECT DISTINCT "userstories_userstory"."owner_id" "owner_id",
count(coalesce("userstories_userstory"."owner_id", -1)) count "userstories_userstory"."id" "us_id"
FROM "userstories_userstory" FROM "userstories_userstory"
INNER JOIN "projects_project" ON ("userstories_userstory"."project_id" = "projects_project"."id") LEFT OUTER JOIN "epics_relateduserstory"
ON ("userstories_userstory"."id" = "epics_relateduserstory"."user_story_id")
WHERE {where} WHERE {where}
GROUP BY "userstories_userstory"."owner_id" ),
"counters" AS (
SELECT "owner_id",
COUNT("owner_id")
FROM "us_counters"
GROUP BY "owner_id"
) )
SELECT "projects_membership"."user_id" id, SELECT "projects_membership"."user_id" "user_id",
"users_user"."full_name", "users_user"."full_name",
"users_user"."username", "users_user"."username",
COALESCE("counters".count, 0) count COALESCE("counters".count, 0) "count"
FROM projects_membership FROM "projects_membership"
LEFT OUTER JOIN counters ON ("projects_membership"."user_id" = "counters"."owner_id") LEFT OUTER JOIN "counters"
INNER JOIN "users_user" ON ("projects_membership"."user_id" = "users_user"."id") ON ("projects_membership"."user_id" = "counters"."owner_id")
INNER JOIN "users_user"
ON ("projects_membership"."user_id" = "users_user"."id")
WHERE "projects_membership"."project_id" = %s AND "projects_membership"."user_id" IS NOT NULL WHERE "projects_membership"."project_id" = %s AND "projects_membership"."user_id" IS NOT NULL
-- System users -- System users
UNION UNION
SELECT "users_user"."id" user_id, SELECT "users_user"."id" "user_id",
"users_user"."full_name" full_name, "users_user"."full_name" "full_name",
"users_user"."username" username, "users_user"."username" "username",
COALESCE("counters".count, 0) count COALESCE("counters"."count", 0) "count"
FROM users_user FROM "users_user"
LEFT OUTER JOIN counters ON ("users_user"."id" = "counters"."owner_id") LEFT OUTER JOIN "counters"
ON ("users_user"."id" = "counters"."owner_id")
WHERE ("users_user"."is_system" IS TRUE) WHERE ("users_user"."is_system" IS TRUE)
""".format(where=where) """.format(where=where)
@ -423,24 +462,31 @@ def _get_userstories_tags(project, queryset):
where_params = queryset_where_tuple[1] where_params = queryset_where_tuple[1]
extra_sql = """ extra_sql = """
WITH userstories_tags AS ( WITH "userstories_tags" AS (
SELECT tag, SELECT "tag",
COUNT(tag) counter FROM ( COUNT("tag") "counter"
SELECT UNNEST(userstories_userstory.tags) tag FROM (
FROM userstories_userstory SELECT DISTINCT "userstories_userstory"."id" "us_id",
INNER JOIN projects_project UNNEST("userstories_userstory"."tags") "tag"
ON (userstories_userstory.project_id = projects_project.id) FROM "userstories_userstory"
WHERE {where}) tags INNER JOIN "projects_project"
GROUP BY tag), ON ("userstories_userstory"."project_id" = "projects_project"."id")
project_tags AS ( LEFT JOIN "epics_relateduserstory"
SELECT reduce_dim(tags_colors) tag_color ON ("userstories_userstory"."id" = "epics_relateduserstory"."user_story_id")
FROM projects_project WHERE {where}
WHERE id=%s) ) "tags"
GROUP BY "tag"),
SELECT tag_color[1] tag, COALESCE(userstories_tags.counter, 0) counter "project_tags" AS (
FROM project_tags SELECT reduce_dim("tags_colors") "tag_color"
LEFT JOIN userstories_tags ON project_tags.tag_color[1] = userstories_tags.tag FROM "projects_project"
ORDER BY tag WHERE "id"=%s)
SELECT "tag_color"[1] "tag", COALESCE("userstories_tags"."counter", 0) "counter"
FROM "project_tags"
LEFT JOIN "userstories_tags"
ON "project_tags"."tag_color"[1] = "userstories_tags"."tag"
ORDER BY "tag"
""".format(where=where) """.format(where=where)
with closing(connection.cursor()) as cursor: with closing(connection.cursor()) as cursor:
@ -461,9 +507,8 @@ def _get_userstories_epics(project, queryset):
queryset_where_tuple = queryset.query.where.as_sql(compiler, connection) queryset_where_tuple = queryset.query.where.as_sql(compiler, connection)
where = queryset_where_tuple[0] where = queryset_where_tuple[0]
where_params = queryset_where_tuple[1] where_params = queryset_where_tuple[1]
extra_sql = """ extra_sql = """
WITH counters AS ( WITH "counters" AS (
SELECT "epics_relateduserstory"."epic_id" AS "epic_id", SELECT "epics_relateduserstory"."epic_id" AS "epic_id",
count("epics_relateduserstory"."id") AS "counter" count("epics_relateduserstory"."id") AS "counter"
FROM "epics_relateduserstory" FROM "epics_relateduserstory"
@ -474,6 +519,7 @@ def _get_userstories_epics(project, queryset):
WHERE {where} WHERE {where}
GROUP BY "epics_relateduserstory"."epic_id" GROUP BY "epics_relateduserstory"."epic_id"
) )
-- User stories with no epics (return results only if there are userstories) -- User stories with no epics (return results only if there are userstories)
SELECT NULL AS "id", SELECT NULL AS "id",
NULL AS "ref", NULL AS "ref",
@ -487,7 +533,9 @@ def _get_userstories_epics(project, queryset):
ON ("userstories_userstory"."project_id" = "projects_project"."id") ON ("userstories_userstory"."project_id" = "projects_project"."id")
WHERE {where} AND "epics_relateduserstory"."epic_id" IS NULL WHERE {where} AND "epics_relateduserstory"."epic_id" IS NULL
GROUP BY "epics_relateduserstory"."epic_id" GROUP BY "epics_relateduserstory"."epic_id"
UNION UNION
SELECT "epics_epic"."id" AS "id", SELECT "epics_epic"."id" AS "id",
"epics_epic"."ref" AS "ref", "epics_epic"."ref" AS "ref",
"epics_epic"."subject" AS "subject", "epics_epic"."subject" AS "subject",
@ -500,7 +548,7 @@ def _get_userstories_epics(project, queryset):
""".format(where=where) """.format(where=where)
with closing(connection.cursor()) as cursor: with closing(connection.cursor()) as cursor:
cursor.execute(extra_sql, where_params + [project.id] + where_params) cursor.execute(extra_sql, where_params + where_params + [project.id])
rows = cursor.fetchall() rows = cursor.fetchall()
result = [] result = []

View File

@ -684,7 +684,7 @@ def test_api_filters_data(client):
# | 6 | status3 | user2 | user1 | tag1 tag2 | epic0 epic2 | # | 6 | status3 | user2 | user1 | tag1 tag2 | epic0 epic2 |
# | 7 | status0 | user1 | user2 | tag3 | None | # | 7 | status0 | user1 | user2 | tag3 | None |
# | 8 | status3 | user3 | user2 | tag1 | epic2 | # | 8 | status3 | user3 | user2 | tag1 | epic2 |
# | 9 | status1 | user2 | user3 | tag0 | none | # | 9 | status1 | user2 | user3 | tag0 | None |
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
us0 = f.UserStoryFactory.create(project=project, owner=user2, assigned_to=None, us0 = f.UserStoryFactory.create(project=project, owner=user2, assigned_to=None,
@ -802,6 +802,34 @@ def test_api_filters_data(client):
assert next(filter(lambda i: i['id'] == epic1.id, response.data["epics"]))["count"] == 0 assert next(filter(lambda i: i['id'] == epic1.id, response.data["epics"]))["count"] == 0
assert next(filter(lambda i: i['id'] == epic2.id, response.data["epics"]))["count"] == 1 assert next(filter(lambda i: i['id'] == epic2.id, response.data["epics"]))["count"] == 1
# Filter (epic0 epic2)
response = client.get(url + "&epic={},{}".format(epic0.id, epic2.id))
assert response.status_code == 200
assert next(filter(lambda i: i['id'] == user1.id, response.data["owners"]))["count"] == 1
assert next(filter(lambda i: i['id'] == user2.id, response.data["owners"]))["count"] == 2
assert next(filter(lambda i: i['id'] == user3.id, response.data["owners"]))["count"] == 1
assert next(filter(lambda i: i['id'] is None, response.data["assigned_to"]))["count"] == 1
assert next(filter(lambda i: i['id'] == user1.id, response.data["assigned_to"]))["count"] == 2
assert next(filter(lambda i: i['id'] == user2.id, response.data["assigned_to"]))["count"] == 1
assert next(filter(lambda i: i['id'] == user3.id, response.data["assigned_to"]))["count"] == 0
assert next(filter(lambda i: i['id'] == status0.id, response.data["statuses"]))["count"] == 1
assert next(filter(lambda i: i['id'] == status1.id, response.data["statuses"]))["count"] == 0
assert next(filter(lambda i: i['id'] == status2.id, response.data["statuses"]))["count"] == 0
assert next(filter(lambda i: i['id'] == status3.id, response.data["statuses"]))["count"] == 3
assert next(filter(lambda i: i['name'] == tag0, response.data["tags"]))["count"] == 0
assert next(filter(lambda i: i['name'] == tag1, response.data["tags"]))["count"] == 4
assert next(filter(lambda i: i['name'] == tag2, response.data["tags"]))["count"] == 2
assert next(filter(lambda i: i['name'] == tag3, response.data["tags"]))["count"] == 1
assert next(filter(lambda i: i['id'] is None, response.data["epics"]))["count"] == 5
assert next(filter(lambda i: i['id'] == epic0.id, response.data["epics"]))["count"] == 3
assert next(filter(lambda i: i['id'] == epic1.id, response.data["epics"]))["count"] == 1
assert next(filter(lambda i: i['id'] == epic2.id, response.data["epics"]))["count"] == 2
def test_get_invalid_csv(client): def test_get_invalid_csv(client):
url = reverse("userstories-csv") url = reverse("userstories-csv")