Skip to content
Merged
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
25 changes: 25 additions & 0 deletions readthedocs/api/v2/views/integrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
from readthedocs.core.views.hooks import trigger_sync_versions
from readthedocs.integrations.models import HttpExchange
from readthedocs.integrations.models import Integration
from readthedocs.notifications.models import Notification
from readthedocs.projects.models import Project
from readthedocs.projects.notifications import MESSAGE_PROJECT_DEPRECATED_WEBHOOK
from readthedocs.vcs_support.backends.git import parse_version_from_ref


Expand Down Expand Up @@ -448,6 +450,29 @@ def handle_webhook(self):
See https://developer.github.com/v3/activity/events/types/

"""
if self.project.is_github_app_project:
Notification.objects.add(
message_id=MESSAGE_PROJECT_DEPRECATED_WEBHOOK,
attached_to=self.project,
dismissable=True,
)
return Response(
{
"detail": " ".join(
dedent(
"""
This project is connected to our GitHub App and doesn't require a separate webhook, ignoring webhook event.
Remove the deprecated webhook from your repository to avoid duplicate events,
see https://docs.readthedocs.com/platform/stable/reference/git-integration.html#manually-migrating-a-project.
"""
)
.strip()
.splitlines()
)
},
status=status.HTTP_400_BAD_REQUEST,
)

# Get event and trigger other webhook events
action = self.data.get("action", None)
created = self.data.get("created", False)
Expand Down
15 changes: 15 additions & 0 deletions readthedocs/projects/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
MESSAGE_PROJECT_SKIP_BUILDS = "project:invalid:skip-builds"
MESSAGE_PROJECT_ADDONS_BY_DEFAULT = "project:addons:by-default"
MESSAGE_PROJECT_SSH_KEY_WITH_WRITE_ACCESS = "project:ssh-key-with-write-access"
MESSAGE_PROJECT_DEPRECATED_WEBHOOK = "project:webhooks:deprecated"

messages = [
Message(
Expand Down Expand Up @@ -193,5 +194,19 @@
),
type=WARNING,
),
Message(
id=MESSAGE_PROJECT_DEPRECATED_WEBHOOK,
header=_("Remove deprecated webhook"),
body=_(
textwrap.dedent(
"""
This project is connected to our GitHub App and doesn't require a separate webhook.
<a href="https://docs.readthedocs.com/platform/stable/reference/git-integration.html#manually-migrating-a-project">Remove the deprecated webhook from your repository</a>
to avoid duplicate events.
"""
).strip(),
),
type=INFO,
),
]
registry.add(messages)
17 changes: 17 additions & 0 deletions readthedocs/projects/views/private.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
from readthedocs.projects.models import Project
from readthedocs.projects.models import ProjectRelationship
from readthedocs.projects.models import WebHook
from readthedocs.projects.notifications import MESSAGE_PROJECT_DEPRECATED_WEBHOOK
from readthedocs.projects.tasks.utils import clean_project_resources
from readthedocs.projects.utils import get_csv_file
from readthedocs.projects.views.base import ProjectAdminMixin
Expand Down Expand Up @@ -967,6 +968,22 @@ class IntegrationDelete(IntegrationMixin, DeleteViewWithMessage):
success_message = _("Integration deleted")
http_method_names = ["post"]

def post(self, request, *args, **kwargs):
resp = super().post(request, *args, **kwargs)
# Dismiss notification about removing the GitHub webhook.
project = self.get_project()
if (
project.is_github_app_project
and not project.integrations.filter(
integration_type=Integration.GITHUB_WEBHOOK
).exists()
):
Notification.objects.cancel(
attached_to=project,
message_id=MESSAGE_PROJECT_DEPRECATED_WEBHOOK,
)
return resp


class IntegrationExchangeDetail(IntegrationMixin, DetailView):
model = HttpExchange
Expand Down
45 changes: 45 additions & 0 deletions readthedocs/rtd_tests/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
Project,
)
from readthedocs.aws.security_token_service import AWSS3TemporaryCredentials
from readthedocs.projects.notifications import MESSAGE_PROJECT_DEPRECATED_WEBHOOK
from readthedocs.subscriptions.constants import TYPE_CONCURRENT_BUILDS
from readthedocs.subscriptions.products import RTDProductFeature
from readthedocs.vcs_support.backends.git import parse_version_from_ref
Expand Down Expand Up @@ -2628,6 +2629,50 @@ def test_github_get_external_version_data(self, trigger_build):
self.assertEqual(version_data.source_branch, "source_branch")
self.assertEqual(version_data.base_branch, "master")

def test_github_skip_githubapp_projects(self, trigger_build):
installation = get(
GitHubAppInstallation,
installation_id=1111,
target_id=1111,
target_type=GitHubAccountType.USER,
)
remote_repository = get(
RemoteRepository,
remote_id="1234",
name="repo",
full_name="user/repo",
vcs_provider=GitHubAppProvider.id,
github_app_installation=installation,
)
self.project.remote_repository = remote_repository
self.project.save()

assert self.project.is_github_app_project
assert self.project.notifications.count() == 0

client = APIClient()
payload = '{"ref":"refs/heads/master"}'
signature = get_signature(
self.github_integration,
payload,
)
headers = {
GITHUB_EVENT_HEADER: GITHUB_PUSH,
GITHUB_SIGNATURE_HEADER: signature,
}
resp = client.post(
reverse("api_webhook_github", kwargs={"project_slug": self.project.slug}),
json.loads(payload),
format="json",
headers=headers,
)
assert resp.status_code == 400
assert "This project is connected to our GitHub App" in resp.data["detail"]

notification = self.project.notifications.first()
assert notification is not None
assert notification.message_id == MESSAGE_PROJECT_DEPRECATED_WEBHOOK

def test_gitlab_webhook_for_branches(self, trigger_build):
"""GitLab webhook API."""
client = APIClient()
Expand Down