Skip to content

Commit 94ae575

Browse files
committed
Integrations: allow to pass more data about external versions
This was extracted from #7664
1 parent 8479e72 commit 94ae575

File tree

3 files changed

+83
-58
lines changed

3 files changed

+83
-58
lines changed

readthedocs/api/v2/views/integrations.py

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import json
66
import logging
77
import re
8+
from functools import namedtuple
89

910
from django.shortcuts import get_object_or_404
1011
from rest_framework import permissions, status
@@ -21,14 +22,13 @@
2122
)
2223
from readthedocs.core.views.hooks import (
2324
build_branches,
24-
trigger_sync_versions,
25-
get_or_create_external_version,
26-
delete_external_version,
2725
build_external_version,
26+
delete_external_version,
27+
get_or_create_external_version,
28+
trigger_sync_versions,
2829
)
2930
from readthedocs.integrations.models import HttpExchange, Integration
30-
from readthedocs.projects.models import Project, Feature
31-
31+
from readthedocs.projects.models import Feature, Project
3232

3333
log = logging.getLogger(__name__)
3434

@@ -56,6 +56,12 @@
5656
BITBUCKET_PUSH = 'repo:push'
5757

5858

59+
ExternalVersionData = namedtuple(
60+
'ExternalVersionData',
61+
['id', 'source_branch', 'base_branch', 'commit'],
62+
)
63+
64+
5965
class WebhookMixin:
6066

6167
"""Base class for Webhook mixins."""
@@ -227,20 +233,23 @@ def get_external_version_response(self, project):
227233
:param project: Project instance
228234
:type project: readthedocs.projects.models.Project
229235
"""
230-
identifier, verbose_name = self.get_external_version_data()
236+
version_data = self.get_external_version_data()
231237
# create or get external version object using `verbose_name`.
232238
external_version = get_or_create_external_version(
233-
project, identifier, verbose_name
239+
project=project,
240+
version_data=version_data,
234241
)
235242
# returns external version verbose_name (pull/merge request number)
236243
to_build = build_external_version(
237-
project=project, version=external_version, commit=identifier
244+
project=project,
245+
version=external_version,
246+
version_data=version_data,
238247
)
239248

240249
return {
241-
'build_triggered': True,
250+
'build_triggered': bool(to_build),
242251
'project': project.slug,
243-
'versions': [to_build],
252+
'versions': [to_build] if to_build else [],
244253
}
245254

246255
def get_delete_external_version_response(self, project):
@@ -258,10 +267,10 @@ def get_delete_external_version_response(self, project):
258267
:param project: Project instance
259268
:type project: Project
260269
"""
261-
identifier, verbose_name = self.get_external_version_data()
262-
# Delete external version
270+
version_data = self.get_external_version_data()
263271
deleted_version = delete_external_version(
264-
project, identifier, verbose_name
272+
project=project,
273+
version_data=version_data,
265274
)
266275
return {
267276
'version_deleted': deleted_version is not None,
@@ -320,13 +329,15 @@ def get_data(self):
320329
def get_external_version_data(self):
321330
"""Get Commit Sha and pull request number from payload."""
322331
try:
323-
identifier = self.data['pull_request']['head']['sha']
324-
verbose_name = str(self.data['number'])
325-
326-
return identifier, verbose_name
327-
332+
data = ExternalVersionData(
333+
id=str(self.data['number']),
334+
commit=self.data['pull_request']['head']['sha'],
335+
source_branch=self.data['pull_request']['head']['ref'],
336+
base_branch=self.data['pull_request']['base']['ref'],
337+
)
338+
return data
328339
except KeyError:
329-
raise ParseError('Parameters "sha" and "number" are required')
340+
raise ParseError('Invalid payload')
330341

331342
def is_payload_valid(self):
332343
"""
@@ -530,13 +541,15 @@ def is_payload_valid(self):
530541
def get_external_version_data(self):
531542
"""Get commit SHA and merge request number from payload."""
532543
try:
533-
identifier = self.data['object_attributes']['last_commit']['id']
534-
verbose_name = str(self.data['object_attributes']['iid'])
535-
536-
return identifier, verbose_name
537-
544+
data = ExternalVersionData(
545+
id=str(self.data['object_attributes']['iid']),
546+
commit=self.data['object_attributes']['last_commit']['id'],
547+
source_branch=self.data['object_attributes']['source_branch'],
548+
base_branch=self.data['object_attributes']['target_branch'],
549+
)
550+
return data
538551
except KeyError:
539-
raise ParseError('Parameters "id" and "iid" are required')
552+
raise ParseError('Invalid payload')
540553

541554
def handle_webhook(self):
542555
"""

readthedocs/core/views/hooks.py

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44

55
from readthedocs.builds.constants import EXTERNAL
66
from readthedocs.core.utils import trigger_build
7-
from readthedocs.projects.models import Project, Feature
7+
from readthedocs.projects.models import Feature, Project
88
from readthedocs.projects.tasks import sync_repository_task
99

10-
1110
log = logging.getLogger(__name__)
1211

1312

@@ -119,22 +118,22 @@ def trigger_sync_versions(project):
119118
return None
120119

121120

122-
def get_or_create_external_version(project, identifier, verbose_name):
121+
def get_or_create_external_version(project, version_data):
123122
"""
124-
Get or create external versions using `identifier` and `verbose_name`.
123+
Get or create version using the ``commit`` as identifier, and PR id as ``verbose_name``.
125124
126125
if external version does not exist create an external version
127126
128127
:param project: Project instance
129-
:param identifier: Commit Hash
130-
:param verbose_name: pull/merge request number
131-
:returns: External version.
128+
:param version_data: A :py:class:`readthedocs.api.v2.views.integrations.ExternalVersionData`
129+
instance.
130+
:returns: External version.
132131
:rtype: Version
133132
"""
134133
external_version, created = project.versions.get_or_create(
135-
verbose_name=verbose_name,
134+
verbose_name=version_data.id,
136135
type=EXTERNAL,
137-
defaults={'identifier': identifier, 'active': True},
136+
defaults={'identifier': version_data.commit, 'active': True},
138137
)
139138

140139
if created:
@@ -144,32 +143,37 @@ def get_or_create_external_version(project, identifier, verbose_name):
144143
)
145144
else:
146145
# identifier will change if there is a new commit to the Pull/Merge Request
147-
if external_version.identifier != identifier:
148-
external_version.identifier = identifier
146+
if external_version.identifier != version_data.commit:
147+
external_version.identifier = version_data.commit
149148
external_version.save()
150149

151150
log.info(
152-
'(Update External Version) Updated Version: [%s] ',
153-
external_version.slug
151+
'Commit from external version updated. project=%s version=%s',
152+
project.slug, external_version.slug,
154153
)
155154
return external_version
156155

157156

158-
def delete_external_version(project, identifier, verbose_name):
157+
def delete_external_version(project, version_data):
159158
"""
160-
Delete external versions using `identifier` and `verbose_name`.
159+
Delete external versions using the id and latest commit.
161160
162161
if external version does not exist then returns `None`.
163162
164163
:param project: Project instance
165-
:param identifier: Commit Hash
166-
:param verbose_name: pull/merge request number
167-
:returns: verbose_name (pull/merge request number).
164+
:param version_data: A :py:class:`readthedocs.api.v2.views.integrations.ExternalVersionData`
165+
instance.
166+
:returns: verbose_name (pull/merge request number).
168167
:rtype: str
169168
"""
170-
external_version = project.versions(manager=EXTERNAL).filter(
171-
verbose_name=verbose_name, identifier=identifier
172-
).first()
169+
external_version = (
170+
project.versions(manager=EXTERNAL)
171+
.filter(
172+
verbose_name=version_data.id,
173+
identifier=version_data.commit,
174+
)
175+
.first()
176+
)
173177

174178
if external_version:
175179
# Delete External Version
@@ -183,11 +187,14 @@ def delete_external_version(project, identifier, verbose_name):
183187
return None
184188

185189

186-
def build_external_version(project, version, commit):
190+
def build_external_version(project, version, version_data):
187191
"""
188192
Where we actually trigger builds for external versions.
189193
190194
All pull/merge request webhook logic should route here to call ``trigger_build``.
195+
196+
:param version_data: A :py:class:`readthedocs.api.v2.views.integrations.ExternalVersionData`
197+
instance.
191198
"""
192199
if not project.has_valid_webhook:
193200
project.has_valid_webhook = True
@@ -199,6 +206,6 @@ def build_external_version(project, version, commit):
199206
project.slug,
200207
version.slug,
201208
)
202-
trigger_build(project=project, version=version, commit=commit, force=True)
209+
trigger_build(project=project, version=version, commit=version.identifier, force=True)
203210

204211
return version.verbose_name

readthedocs/rtd_tests/tests/test_api.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import base64
22
import datetime
33
import json
4-
54
from unittest import mock
5+
66
from allauth.socialaccount.models import SocialAccount
77
from django.contrib.auth.models import User
88
from django.http import QueryDict
@@ -16,13 +16,13 @@
1616
GITHUB_CREATE,
1717
GITHUB_DELETE,
1818
GITHUB_EVENT_HEADER,
19-
GITHUB_PUSH,
20-
GITHUB_SIGNATURE_HEADER,
2119
GITHUB_PULL_REQUEST,
20+
GITHUB_PULL_REQUEST_CLOSED,
2221
GITHUB_PULL_REQUEST_OPENED,
2322
GITHUB_PULL_REQUEST_REOPENED,
24-
GITHUB_PULL_REQUEST_CLOSED,
2523
GITHUB_PULL_REQUEST_SYNC,
24+
GITHUB_PUSH,
25+
GITHUB_SIGNATURE_HEADER,
2626
GITLAB_MERGE_REQUEST,
2727
GITLAB_MERGE_REQUEST_CLOSE,
2828
GITLAB_MERGE_REQUEST_MERGE,
@@ -37,7 +37,7 @@
3737
GitLabWebhookView,
3838
)
3939
from readthedocs.api.v2.views.task_views import get_status_data
40-
from readthedocs.builds.constants import LATEST, EXTERNAL
40+
from readthedocs.builds.constants import EXTERNAL, LATEST
4141
from readthedocs.builds.models import Build, BuildCommandResult, Version
4242
from readthedocs.integrations.models import Integration
4343
from readthedocs.oauth.models import RemoteOrganization, RemoteRepository
@@ -48,7 +48,6 @@
4848
Project,
4949
)
5050

51-
5251
super_auth = base64.b64encode(b'super:test').decode('utf-8')
5352
eric_auth = base64.b64encode(b'eric:test').decode('utf-8')
5453

@@ -840,9 +839,13 @@ def setUp(self):
840839
"number": 2,
841840
"pull_request": {
842841
"head": {
843-
"sha": self.commit
844-
}
845-
}
842+
"sha": self.commit,
843+
"ref": 'source_branch',
844+
},
845+
"base": {
846+
"ref": "master",
847+
},
848+
},
846849
}
847850
self.gitlab_merge_request_payload = {
848851
"object_kind": GITLAB_MERGE_REQUEST,
@@ -851,7 +854,9 @@ def setUp(self):
851854
"last_commit": {
852855
"id": self.commit
853856
},
854-
"action": "open"
857+
"action": "open",
858+
"source_branch": "source_branch",
859+
"target_branch": "master",
855860
},
856861
}
857862
self.gitlab_payload = {

0 commit comments

Comments
 (0)