Skip to content

Commit 285bfdf

Browse files
Merge pull request #623 from weni-ai/feature/endpoint-get-flows
Endpoint to get flows
2 parents 920d068 + 941161e commit 285bfdf

File tree

4 files changed

+114
-0
lines changed

4 files changed

+114
-0
lines changed

temba/api/v2/flows/__init__.py

Whitespace-only changes.

temba/api/v2/flows/tests.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from unittest.mock import patch
2+
from uuid import uuid4
3+
4+
from django.urls import reverse
5+
6+
from temba.flows.models import Flow
7+
from temba.tests.base import TembaTest
8+
9+
10+
class TestInternalFlowsAPIView(TembaTest):
11+
def setUp(self):
12+
super().setUp()
13+
# ensure project has a proj_uuid for lookups
14+
self.org.proj_uuid = uuid4()
15+
self.org.save(update_fields=["proj_uuid"])
16+
17+
@patch("temba.api.v2.flows.views.InternalFlowsAPIView.authentication_classes", [])
18+
@patch("temba.api.v2.flows.views.InternalFlowsAPIView.permission_classes", [])
19+
def test_list_flows_success(self):
20+
# create some flows
21+
flow1 = Flow.create(self.org, self.user, name="Flow One")
22+
Flow.create(self.org, self.user, name="Flow Two")
23+
Flow.create(self.org, self.user, name="Flow Three")
24+
25+
url = f"{reverse('api.v2.internal_flows')}?project_uuid={self.org.proj_uuid}&limit=2"
26+
resp = self.client.get(url)
27+
self.assertEqual(resp.status_code, 200)
28+
data = resp.json()
29+
30+
# paginated response shape
31+
self.assertIn("results", data)
32+
self.assertIn("next", data)
33+
self.assertIn("previous", data)
34+
35+
# only uuid and name returned
36+
self.assertEqual(len(data["results"][0].keys()), 2)
37+
self.assertIn("uuid", data["results"][0])
38+
self.assertIn("name", data["results"][0])
39+
40+
# ordering should be newest first (flow3, flow2, ...)
41+
self.assertEqual({r["name"] for r in data["results"]}, {"Flow Three", "Flow Two"})
42+
self.assertIsNotNone(data["next"]) # has more pages
43+
44+
# follow next page
45+
next_url = data["next"]
46+
resp = self.client.get(next_url)
47+
self.assertEqual(resp.status_code, 200)
48+
data2 = resp.json()
49+
self.assertEqual(len(data2["results"]), 1)
50+
self.assertEqual(data2["results"][0]["name"], flow1.name)
51+
52+
@patch("temba.api.v2.flows.views.InternalFlowsAPIView.authentication_classes", [])
53+
@patch("temba.api.v2.flows.views.InternalFlowsAPIView.permission_classes", [])
54+
def test_missing_project_uuid(self):
55+
resp = self.client.get(reverse("api.v2.internal_flows"))
56+
self.assertEqual(resp.status_code, 400)
57+
self.assertEqual(resp.json(), {"error": "project_uuid is required"})
58+
59+
@patch("temba.api.v2.flows.views.InternalFlowsAPIView.authentication_classes", [])
60+
@patch("temba.api.v2.flows.views.InternalFlowsAPIView.permission_classes", [])
61+
def test_invalid_project_uuid(self):
62+
url = f"{reverse('api.v2.internal_flows')}?project_uuid={uuid4()}&limit=1"
63+
resp = self.client.get(url)
64+
self.assertEqual(resp.status_code, 404)
65+
self.assertEqual(resp.json(), {"error": "Project not found"})
66+
67+
@patch("temba.api.v2.flows.views.InternalFlowsAPIView.authentication_classes", [])
68+
@patch("temba.api.v2.flows.views.InternalFlowsAPIView.permission_classes", [])
69+
def test_pagination_limit(self):
70+
# create more flows than the limit
71+
for i in range(12):
72+
Flow.create(self.org, self.user, name=f"Flow {i}")
73+
74+
url = f"{reverse('api.v2.internal_flows')}?project_uuid={self.org.proj_uuid}&limit=5"
75+
resp = self.client.get(url)
76+
self.assertEqual(resp.status_code, 200)
77+
data = resp.json()
78+
self.assertEqual(len(data["results"]), 5)
79+
self.assertIsNotNone(data.get("next"))

temba/api/v2/flows/views.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@
44
from rest_framework.exceptions import NotFound, PermissionDenied
55
from rest_framework.permissions import IsAuthenticated
66
from rest_framework.renderers import JSONRenderer
7+
from rest_framework.request import Request
78
from rest_framework.response import Response
89
from rest_framework.views import APIView
10+
from weni.internal.authenticators import InternalOIDCAuthentication
911

1012
from django.conf import settings
1113

1214
from temba import mailroom
15+
from temba.api.v2.internals.views import APIViewMixin
16+
from temba.api.v2.permissions import IsUserInOrg
17+
from temba.api.v2.views_base import CreatedOnCursorPagination
1318
from temba.flows.models import Flow
19+
from temba.orgs.models import Org
1420
from temba.utils import analytics
1521

1622

@@ -99,3 +105,30 @@ def post(self, request, flow_uuid, *args, **kwargs):
99105
return Response(client.sim_resume(payload))
100106
except mailroom.MailroomException:
101107
return Response(dict(status="error", description="mailroom error"), status=500)
108+
109+
110+
class InternalFlowsAPIView(APIViewMixin, APIView):
111+
authentication_classes = [InternalOIDCAuthentication]
112+
permission_classes = [IsAuthenticated, IsUserInOrg]
113+
114+
class Pagination(CreatedOnCursorPagination):
115+
page_size_query_param = "limit"
116+
117+
def get(self, request: Request):
118+
params = request.query_params
119+
project_uuid = params.get("project_uuid")
120+
121+
if project_uuid is None:
122+
return Response(status=400, data={"error": "project_uuid is required"})
123+
124+
try:
125+
org = Org.objects.get(proj_uuid=project_uuid)
126+
except Org.DoesNotExist:
127+
return Response(status=404, data={"error": "Project not found"})
128+
129+
queryset = Flow.objects.filter(org=org, is_active=True)
130+
131+
paginator = self.Pagination()
132+
page = paginator.paginate_queryset(queryset, request, view=self)
133+
results = [{"uuid": str(flow.uuid), "name": flow.name} for flow in page]
134+
return paginator.get_paginated_response(results)

temba/api/v2/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from django.urls import include, path
55

66
from temba.api.v2.elasticsearch.views import ContactsElasticSearchEndpoint
7+
from temba.api.v2.flows.views import InternalFlowsAPIView
78
from temba.api.v2.projects.views import GetProjectView
89

910
from .flows.urls import urlpatterns as flows_urlpatterns
@@ -83,6 +84,7 @@
8384
url(r"^events$", EventsEndpoint.as_view(), name="api.v2.events"),
8485
url(r"^filter_templates$", FilterTemplatesEndpoint.as_view(), name="api.v2.filter_templates"),
8586
url(r"^filter_templates_new$", FilterTemplatesEndpointNew.as_view(), name="api.v2.filter_templates_new"),
87+
url(r"^internal_flows$", InternalFlowsAPIView.as_view(), name="api.v2.internal_flows"),
8688
url(r"^definitions$", DefinitionsEndpoint.as_view(), name="api.v2.definitions"),
8789
url(r"^fields$", FieldsEndpoint.as_view(), name="api.v2.fields"),
8890
url(r"^flow_starts$", FlowStartsEndpoint.as_view(), name="api.v2.flow_starts"),

0 commit comments

Comments
 (0)