Skip to content

Commit 12801aa

Browse files
committed
[api] Add API endpoints to manage repositories
This commit adds two new API endpoints for managing repositories: - /scheduler/add_repository: This endpoint allows users to add a new repository for analysis. - `/scheduler/repositories`: This endpoint provides a list of available repositories in GrimoireLab along with task information. It includes filtering options for backend, category, and URI. Signed-off-by: Jose Javier Merchante <[email protected]>
1 parent 199b235 commit 12801aa

File tree

4 files changed

+211
-0
lines changed

4 files changed

+211
-0
lines changed

src/grimoirelab/core/scheduler/api.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
get_registered_task_model
3131
)
3232
from .tasks.models import EventizerTask
33+
from .datasources.models import Repository
3334

3435

3536
class EventizerPaginator(pagination.PageNumberPagination):
@@ -176,3 +177,33 @@ class EventizerJobLogs(generics.RetrieveAPIView):
176177
def get_queryset(self):
177178
task_id = self.kwargs['task_id']
178179
return get_registered_task_model('eventizer')[1].objects.filter(task__uuid=task_id)
180+
181+
182+
class EventizerRepositoryListSerializer(serializers.ModelSerializer):
183+
task = EventizerTaskListSerializer()
184+
185+
class Meta:
186+
model = Repository
187+
fields = [
188+
'uri', 'datasource_type', 'datasource_category', 'task',
189+
]
190+
191+
192+
class RepositoryList(generics.ListAPIView):
193+
serializer_class = EventizerRepositoryListSerializer
194+
pagination_class = EventizerPaginator
195+
196+
def get_queryset(self):
197+
datasource = self.request.query_params.get('datasource')
198+
category = self.request.query_params.get('category')
199+
uri = self.request.query_params.get('uri')
200+
201+
queryset = Repository.objects.select_related('task')
202+
if datasource is not None:
203+
queryset = queryset.filter(datasource_type=datasource)
204+
if category is not None:
205+
queryset = queryset.filter(datasource_category=category)
206+
if uri is not None:
207+
queryset = queryset.filter(uri=uri)
208+
209+
return queryset

src/grimoirelab/core/scheduler/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
urlpatterns = [
2626
re_path(r'^add_task', views.add_task),
27+
re_path(r'^add_repository', views.add_repository, name='add_repository'),
28+
path('repositories/', api.RepositoryList.as_view()),
2729
path('tasks/', api.EventizerTaskList.as_view()),
2830
path('tasks/<str:uuid>/', api.EventizerTaskDetail.as_view()),
2931
path('tasks/<str:task_id>/jobs/', api.EventizerJobList.as_view()),

src/grimoirelab/core/scheduler/views.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
import json
2020

2121
from django.conf import settings
22+
from django.db import IntegrityError
2223
from django.http import JsonResponse
2324
from django.views.decorators.csrf import csrf_exempt
2425
from django.views.decorators.http import require_http_methods
2526

27+
from .datasources.models import Repository
2628
from .scheduler import (
2729
schedule_task
2830
)
@@ -78,3 +80,66 @@ def add_task(request):
7880
'message': f"Task {task.id} added correctly"
7981
}
8082
return JsonResponse(response, safe=False)
83+
84+
85+
@require_http_methods(["POST"])
86+
@csrf_exempt
87+
def add_repository(request):
88+
"""Create a Repository and start a Task to fetch items
89+
90+
The body should contain a JSON similar to:
91+
{
92+
'uri': "<repository_uri>",
93+
'datasource_type': 'git',
94+
'datasource_category': 'commit',
95+
'scheduler': {
96+
'job_interval': 86400,
97+
'job_max_retries': 3,
98+
}
99+
}
100+
"""
101+
try:
102+
data = json.loads(request.body)
103+
except json.JSONDecodeError:
104+
return JsonResponse({"error": "Invalid JSON format."}, status=400)
105+
106+
# Get POST data
107+
job_interval = settings.GRIMOIRELAB_JOB_INTERVAL
108+
job_max_retries = settings.GRIMOIRELAB_JOB_MAX_RETRIES
109+
if 'scheduler' in data:
110+
job_interval = data['scheduler'].get('job_interval', job_interval)
111+
job_max_retries = data['scheduler'].get('job_max_retries', job_max_retries)
112+
113+
uri = data.get('uri')
114+
datasource_type = data.get('datasource_type')
115+
datasource_category = data.get('datasource_category')
116+
if not uri or not datasource_type or not datasource_category:
117+
return JsonResponse({"error": "Missing parameters"}, status=400)
118+
119+
# Create the task and the repository
120+
try:
121+
repository = Repository.objects.create(uri=uri,
122+
datasource_type=datasource_type,
123+
datasource_category=datasource_category)
124+
except IntegrityError:
125+
return JsonResponse({"error": "Repository already exists"}, status=405)
126+
127+
task_args = {
128+
'uri': data['uri']
129+
}
130+
task = schedule_task(
131+
'eventizer', task_args,
132+
datasource_type=datasource_type,
133+
datasource_category=datasource_category,
134+
job_interval=job_interval,
135+
job_max_retries=job_max_retries
136+
)
137+
repository.task = task
138+
repository.save()
139+
140+
response = {
141+
'status': 'ok',
142+
'task_id': repository.task.uuid,
143+
'message': f"Repository {uri} added correctly"
144+
}
145+
return JsonResponse(response, safe=False)

tests/scheduler/test_views.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (C) GrimoireLab Contributors
4+
#
5+
# This program is free software; you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation; either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
#
18+
19+
from django.test import TestCase, Client
20+
from django.urls import reverse
21+
from unittest.mock import patch
22+
import json
23+
24+
from grimoirelab.core.scheduler.tasks.models import EventizerTask
25+
from grimoirelab.core.scheduler.datasources.models import Repository
26+
27+
28+
class TestAddRepository(TestCase):
29+
def setUp(self):
30+
self.client = Client()
31+
self.url = reverse('add_repository')
32+
self.valid_data = {
33+
'uri': "https://example.com/repo.git",
34+
'datasource_type': 'git',
35+
'datasource_category': 'commit',
36+
'scheduler': {
37+
'job_interval': 86400,
38+
'job_max_retries': 3,
39+
'force_run': False
40+
}
41+
}
42+
self.task = EventizerTask.create_task(
43+
task_args={'uri': 'uri'},
44+
job_interval=86400,
45+
job_max_retries=3,
46+
datasource_type='git',
47+
datasource_category='commit'
48+
)
49+
50+
@patch('grimoirelab.core.scheduler.views.schedule_task')
51+
def test_add_repository_valid(self, mock_schedule_task):
52+
"""Test adding a repository with valid data"""
53+
54+
mock_schedule_task.return_value = self.task
55+
56+
response = self.client.post(
57+
self.url,
58+
data=json.dumps(self.valid_data),
59+
content_type='application/json'
60+
)
61+
62+
self.assertEqual(response.status_code, 200)
63+
self.assertEqual(response.json(), {
64+
'status': 'ok',
65+
'task_id': self.task.uuid,
66+
'message': f"Repository {self.valid_data['uri']} added correctly"
67+
})
68+
69+
def test_add_repository_invalid_json(self):
70+
"""Test adding a repository with invalid JSON"""
71+
72+
response = self.client.post(
73+
self.url,
74+
data="invalid json",
75+
content_type='application/json'
76+
)
77+
78+
self.assertEqual(response.status_code, 400)
79+
self.assertEqual(response.json(), {"error": "Invalid JSON format."})
80+
81+
def test_add_repository_missing_parameters(self):
82+
"""Test adding a repository with missing parameters"""
83+
84+
invalid_data = self.valid_data.copy()
85+
del invalid_data['uri']
86+
87+
response = self.client.post(
88+
self.url,
89+
data=json.dumps(invalid_data),
90+
content_type='application/json'
91+
)
92+
93+
self.assertEqual(response.status_code, 400)
94+
self.assertEqual(response.json(), {"error": "Missing parameters"})
95+
96+
def test_add_repository_already_exists(self):
97+
"""Test adding a repository that already exists"""
98+
99+
# Make the repository already existing
100+
existing_repository = Repository.objects.create(
101+
uri="https://example.com/repo.git",
102+
datasource_type="git",
103+
datasource_category="commit",
104+
task=self.task
105+
)
106+
response = self.client.post(
107+
self.url,
108+
data=json.dumps(self.valid_data),
109+
content_type='application/json'
110+
)
111+
112+
self.assertEqual(response.status_code, 405)
113+
self.assertEqual(response.json(), {"error": "Repository already exists"})

0 commit comments

Comments
 (0)