Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions apps/api/plane/api/views/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@
ISSUE_ORDER_BY_ALLOWLIST,
sanitize_order_by,
)
from plane.utils.grouper import issue_group_values, issue_on_results, issue_queryset_grouper
from plane.utils.paginator import GroupedOffsetPaginator, SubGroupedOffsetPaginator
from plane.bgtasks.storage_metadata_task import get_asset_object_metadata
from .base import BaseAPIView
from plane.utils.host import base_host
Expand Down Expand Up @@ -420,6 +422,67 @@ def get(self, request, slug, project_id):
else:
issue_queryset = issue_queryset.order_by(order_by_param)

group_by = {"state": "state_id"}.get(request.GET.get("group_by"), request.GET.get("group_by"))
sub_group_by = {"state": "state_id"}.get(
request.GET.get("sub_group_by"), request.GET.get("sub_group_by")
)

if group_by:
issue_queryset = issue_queryset_grouper(
queryset=issue_queryset,
group_by=group_by,
sub_group_by=sub_group_by,
)
grouping_kwargs = {
"request": request,
"order_by": order_by_param,
"queryset": issue_queryset,
"total_count_queryset": total_issue_queryset,
"on_results": lambda issues: issue_on_results(
group_by=group_by,
issues=issues,
sub_group_by=sub_group_by,
),
"group_by_fields": issue_group_values(
field=group_by,
slug=slug,
project_id=project_id,
queryset=issue_queryset,
),
"group_by_field_name": group_by,
"count_filter": Q(
Q(issue_intake__status=1)
| Q(issue_intake__status=-1)
| Q(issue_intake__status=2)
| Q(issue_intake__isnull=True),
archived_at__isnull=True,
is_draft=False,
),
}

if sub_group_by:
if group_by == sub_group_by:
return Response(
{"error": "Group by and sub group by cannot have same parameters"},
status=status.HTTP_400_BAD_REQUEST,
)
grouping_kwargs.update(
{
"paginator_cls": SubGroupedOffsetPaginator,
"sub_group_by_fields": issue_group_values(
field=sub_group_by,
slug=slug,
project_id=project_id,
queryset=issue_queryset,
),
"sub_group_by_field_name": sub_group_by,
}
)
else:
grouping_kwargs["paginator_cls"] = GroupedOffsetPaginator

return self.paginate(**grouping_kwargs)

return self.paginate(
request=request,
queryset=(issue_queryset),
Expand Down
54 changes: 54 additions & 0 deletions apps/api/plane/tests/contract/api/test_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,57 @@ def test_legitimate_order_by_still_works(self, api_key_client, workspace, projec
assert response.status_code == status.HTTP_200_OK, (
f"order_by={value!r} got {response.status_code}: {response.data!r}"
)


@pytest.mark.contract
class TestIssueListGrouping:
def get_url(self, workspace_slug, project_id):
return f"/api/v1/workspaces/{workspace_slug}/projects/{project_id}/issues/"

@pytest.mark.django_db
def test_group_by_state_alias_returns_grouped_results(self, api_key_client, workspace, project, state, issue):
response = api_key_client.get(self.get_url(workspace.slug, project.id), {"group_by": "state"})

assert response.status_code == status.HTTP_200_OK, f"Got {response.status_code}: {response.data!r}"
assert response.data["grouped_by"] == "state_id"
assert response.data["sub_grouped_by"] is None
assert str(state.id) in response.data["results"]
assert response.data["results"][str(state.id)]["results"][0]["id"] == issue.id

@pytest.mark.django_db
def test_group_by_state_id_and_sub_group_by_priority_are_supported(
self, api_key_client, workspace, project, state, issue
):
response = api_key_client.get(
self.get_url(workspace.slug, project.id),
{"group_by": "state_id", "sub_group_by": "priority"},
)

assert response.status_code == status.HTTP_200_OK, f"Got {response.status_code}: {response.data!r}"
assert response.data["grouped_by"] == "state_id"
assert response.data["sub_grouped_by"] == "priority"
assert str(state.id) in response.data["results"]

@pytest.mark.django_db
def test_without_group_by_returns_flat_results(self, api_key_client, workspace, project, issue):
response = api_key_client.get(self.get_url(workspace.slug, project.id))

assert response.status_code == status.HTTP_200_OK, f"Got {response.status_code}: {response.data!r}"
assert response.data["grouped_by"] is None
assert response.data["sub_grouped_by"] is None
assert response.data["results"][0]["id"] == issue.id

@pytest.mark.django_db
@pytest.mark.parametrize(
"query_params",
[
{"group_by": "not_a_field"},
{"group_by": "state", "sub_group_by": "state_id"},
],
)
def test_invalid_grouping_parameters_return_bad_request(
self, api_key_client, workspace, project, issue, query_params
):
response = api_key_client.get(self.get_url(workspace.slug, project.id), query_params)

assert response.status_code == status.HTTP_400_BAD_REQUEST, f"Got {response.status_code}: {response.data!r}"