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,
@ -104,7 +103,13 @@ 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,18 +273,31 @@ def _get_userstories_statuses(project, queryset):
where_params = queryset_where_tuple[1] where_params = queryset_where_tuple[1]
extra_sql = """ extra_sql = """
SELECT "projects_userstorystatus"."id", WITH "us_counters" AS (
"projects_userstorystatus"."name", SELECT DISTINCT "userstories_userstory"."status_id" "status_id",
"projects_userstorystatus"."color", "userstories_userstory"."id" "us_id"
"projects_userstorystatus"."order", FROM "userstories_userstory"
(SELECT count(*) LEFT JOIN "epics_relateduserstory"
FROM "userstories_userstory" ON "userstories_userstory"."id" = "epics_relateduserstory"."user_story_id"
INNER JOIN "projects_project" ON WHERE {where}
("userstories_userstory"."project_id" = "projects_project"."id") ),
WHERE {where} AND "userstories_userstory"."status_id" = "projects_userstorystatus"."id") "counters" AS (
FROM "projects_userstorystatus" SELECT "status_id",
WHERE "projects_userstorystatus"."project_id" = %s COUNT("status_id") "count"
ORDER BY "projects_userstorystatus"."order"; FROM "us_counters"
GROUP BY "status_id"
)
SELECT "projects_userstorystatus"."id",
"projects_userstorystatus"."name",
"projects_userstorystatus"."color",
"projects_userstorystatus"."order",
COALESCE("counters"."count", 0)
FROM "projects_userstorystatus"
LEFT JOIN "counters"
ON "counters"."status_id" = "projects_userstorystatus"."id"
WHERE "projects_userstorystatus"."project_id" = %s
ORDER BY "projects_userstorystatus"."order";
""".format(where=where) """.format(where=where)
with closing(connection.cursor()) as cursor: with closing(connection.cursor()) as cursor:
@ -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",
FROM "userstories_userstory" "userstories_userstory"."id" "us_id"
INNER JOIN "projects_project" ON ("userstories_userstory"."project_id" = "projects_project"."id") FROM "userstories_userstory"
WHERE {where} AND "userstories_userstory"."assigned_to_id" IS NOT NULL LEFT JOIN "epics_relateduserstory"
GROUP BY assigned_to_id ON "userstories_userstory"."id" = "epics_relateduserstory"."user_story_id"
) WHERE {where}
),
SELECT "projects_membership"."user_id" user_id, "counters" AS (
"users_user"."full_name", SELECT "assigned_to_id",
"users_user"."username", COUNT("assigned_to_id")
COALESCE("counters".count, 0) count FROM "us_counters"
FROM projects_membership GROUP BY "assigned_to_id"
LEFT OUTER JOIN counters ON ("projects_membership"."user_id" = "counters"."assigned_to_id") )
INNER JOIN "users_user" ON ("projects_membership"."user_id" = "users_user"."id")
SELECT "projects_membership"."user_id" "user_id",
"users_user"."full_name" "full_name",
"users_user"."username" "username",
COALESCE("counters".count, 0) "count"
FROM "projects_membership"
LEFT OUTER JOIN "counters"
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"
WHERE {where} ON ("userstories_userstory"."id" = "epics_relateduserstory"."user_story_id")
GROUP BY "userstories_userstory"."owner_id" WHERE {where}
) ),
SELECT "projects_membership"."user_id" id, "counters" AS (
SELECT "owner_id",
COUNT("owner_id")
FROM "us_counters"
GROUP BY "owner_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,33 +507,35 @@ 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"
INNER JOIN "userstories_userstory" INNER JOIN "userstories_userstory"
ON ("userstories_userstory"."id" = "epics_relateduserstory"."user_story_id") ON ("userstories_userstory"."id" = "epics_relateduserstory"."user_story_id")
INNER JOIN "projects_project" INNER JOIN "projects_project"
ON ("userstories_userstory"."project_id" = "projects_project"."id") ON ("userstories_userstory"."project_id" = "projects_project"."id")
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",
NULL AS "subject", NULL AS "subject",
0 AS "order", 0 AS "order",
count(COALESCE("epics_relateduserstory"."epic_id", -1)) AS "counter" count(COALESCE("epics_relateduserstory"."epic_id", -1)) AS "counter"
FROM "userstories_userstory" FROM "userstories_userstory"
LEFT OUTER JOIN "epics_relateduserstory" LEFT OUTER JOIN "epics_relateduserstory"
ON ("epics_relateduserstory"."user_story_id" = "userstories_userstory"."id") ON ("epics_relateduserstory"."user_story_id" = "userstories_userstory"."id")
INNER JOIN "projects_project" INNER JOIN "projects_project"
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",
@ -498,9 +546,9 @@ def _get_userstories_epics(project, queryset):
ON ("counters"."epic_id" = "epics_epic"."id") ON ("counters"."epic_id" = "epics_epic"."id")
WHERE "epics_epic"."project_id" = %s WHERE "epics_epic"."project_id" = %s
""".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")