From 3c732c9dbf1a874c7e4c97a4f984c0596a5cdb1f Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Thu, 27 Dec 2018 11:28:20 -0700 Subject: [PATCH 01/10] Updated copy on webhooks * Drop "deprecated webhook endpoint" copy, this is core team nomenclature, not user nomenclature. * Add small amount of docs to point to --- docs/webhooks.rst | 42 +++++++++++++++++++ readthedocs/projects/notifications.py | 2 +- .../deprecated_webhook_endpoint_email.html | 9 ++-- .../deprecated_webhook_endpoint_site.html | 2 +- 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/docs/webhooks.rst b/docs/webhooks.rst index ecf18611b50..31137f36f61 100644 --- a/docs/webhooks.rst +++ b/docs/webhooks.rst @@ -36,6 +36,8 @@ As an example, the URL pattern looks like this: *readthedocs.org/api/v2/webhook/ Use this URL when setting up a new webhook with your provider -- these steps vary depending on the provider: +.. _webhook-integration-github: + GitHub ~~~~~~ @@ -53,6 +55,8 @@ For a 403 error, it's likely that the Payload URL is incorrect. .. note:: The webhook token, intended for the GitHub **Secret** field, is not yet implemented. +.. _webhook-integration-bitbucket: + Bitbucket ~~~~~~~~~ @@ -63,6 +67,8 @@ Bitbucket * Under **Triggers**, **Repository push** should be selected * Finish by clicking **Save** +.. _webhook-integration-gitlab: + GitLab ~~~~~~ @@ -73,6 +79,8 @@ GitLab * Leave the default **Push events** selected and mark **Tag push events** also * Finish by clicking **Add Webhook** +.. _webhook-integration-generic: + Using the generic API integration --------------------------------- @@ -136,3 +144,37 @@ Resyncing webhooks It might be necessary to re-establish a webhook if you are noticing problems. To resync a webhook from Read the Docs, visit the integration detail page and follow the directions for re-syncing your repository webhook. + +Troubleshooting +--------------- + +My project isn't automatically building +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If your project isn't automatically building, you can check your integration on +Read the Docs to see the payload sent to our servers. If there is no recent +activity on your Read the Docs project webhook integration, then it's likely +that your VCS provider is not configured correctly. If there is payload +information on your Read the Docs project, you might need to verify that your +versions are configured to build correctly. + +Either way, it may help to either resync your webhook intergration (see +`Resyncing webhooks`_ for information on this process), or set up an entirely +new webhook intergration. + +.. _webhook-github-services: + +Should I use GitHub Services? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Effective Jan 31st, GitHub Services will stop sending notifications to Read the +Docs. Your project has been configured on Read the Docs for a long time, you are +most likely using a GitHub Service to automatically build your project on Read +the Docs. + +In order for your project to continue automatically building, you will need to +configure your GitHub repository with a new webhook. You can use either a +connected GitHub account and a :ref:`GitHub webhook integration ` +on your Read the Docs project, or you can use a +:ref:`generic webhook integraiton ` without a connected +account. diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py index c1fd9985aa6..d3b4af2e272 100644 --- a/readthedocs/projects/notifications.py +++ b/readthedocs/projects/notifications.py @@ -37,7 +37,7 @@ class DeprecatedWebhookEndpointNotification(Notification): name = 'deprecated_webhook_endpoint' context_object_name = 'project' - subject = 'Project {{ project.name }} is using a deprecated webhook' + subject = '{{ project.name }} project webhook needs to be updated' send_email = False email_period = timedelta(days=7) level = REQUIREMENT diff --git a/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_email.html b/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_email.html index c51862ea7fc..6ab3247fc21 100644 --- a/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_email.html +++ b/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_email.html @@ -1,7 +1,8 @@ -

Just a heads up, your project {{ project.name }} has configured a DEPRECATED webhook to trigger new builds and should be upgraded. Projects hitting these deprecated webhook will stop building on Jan 1, 2019. Please, update it soon!

+

Your project, {{ project.name }}, is currently using GitHub Services to trigger builds on Read the Docs. Effective January 31, 2019, GitHub will no longer process requests using the Services feature, and so Read the Docs will not receive notifications on updates to your repository.

-

To update the webhook your project is hitting, you need to go to the project's settings under your VCS service (GitHub, Bitbucket or GitLab) and remove the Read the Docs webhook from there.

+

To continue building your Read the Docs project on changes to your repository, you will need to add a new webhook on your GitHub repository. You can either connect your GitHub account and configure a GitHub webhook integration, or you can add a generic webhook integration.

-

Once you have done that, you need to go to your project's Integrations under Read the Docs project's Admin, click integration and then in "Resync webhook" button.

+

You can find more information on our webhook intergrations in our documentation, at:

-

Thanks!

+{% comment %}Plain text link because of text version of email{% endcomment %} +

https://docs.readthedocs.io/en/latest/webhooks.html

diff --git a/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_site.html b/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_site.html index 7d7276fb260..7588aa51bc6 100644 --- a/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_site.html +++ b/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_site.html @@ -1 +1 @@ -Your project {{ project.name }} has configured a DEPRECATED webhook to trigger new builds and should be upgraded. Projects hitting these deprecated webhooks will stop building on Jan 1, 2019. Please, update it soon! +Your project, {{ project.name }}, needs to be reconfigured in order to continue building automatically after Jan 31st. For more information, see our documentation on webhook integrations. From b50af5485355f1bec668dcbe1c654c376b53085f Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Wed, 2 Jan 2019 18:13:02 -0700 Subject: [PATCH 02/10] Update docs and point to docs in notification message --- docs/webhooks.rst | 15 +++++++++------ .../deprecated_webhook_endpoint_site.html | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/webhooks.rst b/docs/webhooks.rst index 31137f36f61..8ab637a0340 100644 --- a/docs/webhooks.rst +++ b/docs/webhooks.rst @@ -164,13 +164,14 @@ new webhook intergration. .. _webhook-github-services: -Should I use GitHub Services? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +I was warned I shouldn't use GitHub Services +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Effective Jan 31st, GitHub Services will stop sending notifications to Read the -Docs. Your project has been configured on Read the Docs for a long time, you are -most likely using a GitHub Service to automatically build your project on Read -the Docs. +Last year, GitHub announced that effective Jan 31st, GitHub Services will stop +working [1]_. This means GitHub will stop sending notifications to Read the Docs +for projects configured with the ``ReadTheDocs`` GitHub Service. If your project +has been configured on Read the Docs for a long time, you are most likely still +using this service to automatically build your project on Read the Docs. In order for your project to continue automatically building, you will need to configure your GitHub repository with a new webhook. You can use either a @@ -178,3 +179,5 @@ connected GitHub account and a :ref:`GitHub webhook integration ` without a connected account. + +.. [1] https://developer.github.com/changes/2018-04-25-github-services-deprecation/ diff --git a/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_site.html b/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_site.html index 7588aa51bc6..5f1ada22295 100644 --- a/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_site.html +++ b/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_site.html @@ -1 +1 @@ -Your project, {{ project.name }}, needs to be reconfigured in order to continue building automatically after Jan 31st. For more information, see our documentation on webhook integrations. +Your project, {{ project.name }}, needs to be reconfigured in order to continue building automatically after Jan 31st. For more information, see our documentation on webhook integrations. From f4663075204ba6d71838bc97da75770665746dfb Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Wed, 9 Jan 2019 10:00:10 -0700 Subject: [PATCH 03/10] Split up deprecated view notification to GitHub and other webhook endpoints This sets a date for deprecated of these endpoints as Mar 1st 2019. Too soon? * Reduce complexity and drop decorator pattern for Notification classmethod pattern used in other notifications * Add notifications for non-GitHub incoming webhooks * Add docs as well --- docs/webhooks.rst | 31 +++++++ readthedocs/core/views/hooks.py | 24 +++++- readthedocs/notifications/decorators.py | 51 ------------ readthedocs/projects/notifications.py | 83 +++++++++++++++---- .../rtd_tests/tests/test_notifications.py | 16 ++-- .../deprecated_build_webhook_email.html | 6 ++ .../deprecated_build_webhook_site.html | 1 + ...l => deprecated_github_webhook_email.html} | 2 +- .../deprecated_github_webhook_site.html | 1 + .../deprecated_webhook_endpoint_site.html | 1 - 10 files changed, 134 insertions(+), 82 deletions(-) delete mode 100644 readthedocs/notifications/decorators.py create mode 100644 readthedocs/templates/projects/notifications/deprecated_build_webhook_email.html create mode 100644 readthedocs/templates/projects/notifications/deprecated_build_webhook_site.html rename readthedocs/templates/projects/notifications/{deprecated_webhook_endpoint_email.html => deprecated_github_webhook_email.html} (81%) create mode 100644 readthedocs/templates/projects/notifications/deprecated_github_webhook_site.html delete mode 100644 readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_site.html diff --git a/docs/webhooks.rst b/docs/webhooks.rst index 584548d88ed..e637e4085c0 100644 --- a/docs/webhooks.rst +++ b/docs/webhooks.rst @@ -20,6 +20,8 @@ details and a list of HTTP exchanges that have taken place for the integration. You need this information for the URL, webhook, or Payload URL needed by the repository provider such as GitHub, GitLab, or Bitbucket. +.. _webhook-creation: + Webhook Creation ---------------- @@ -181,3 +183,32 @@ on your Read the Docs project, or you can use a account. .. [1] https://developer.github.com/changes/2018-04-25-github-services-deprecation/ + +.. _webhook-deprecated-endpoints: + +I was warned that my project won't automatically build after Mar 1st +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In addition to :ref:`no longer supporting GitHub Services `, +we have decided to no longer support several other legacy incoming webhook +endpoints that were used before we introduced project webhook integrations. When +we introduced our webhook integrations, we added several features and improved +security for incoming webhooks and these features were not added to our leagcy +incoming webhooks. New projects have not been able to use our legacy incoming +webhooks since, however if you have a project that has been established for a +while, you may still be using these endpoints. + +After March 1st, 2019, we will stop accepting incoming webhook notifications for +these legacy incoming webhooks. Your project will need to be reconfigured and +have a webhook integration configured, pointing to a new webhook with your VCS +provider. + +In particular, the incoming webhook URLs that will be removed are: + +* ``https://readthedocs.org/build`` +* ``https://readthedocs.org/bitbucket`` +* ``https://readthedocs.org/github`` (as noted :ref:`above `) +* ``https://readthedocs.org/gitlab`` + +In order to establish a new project webhook integration, :ref:`follow +the directions for your VCS provider ` diff --git a/readthedocs/core/views/hooks.py b/readthedocs/core/views/hooks.py index bd3ca775e03..3c1802ffdef 100644 --- a/readthedocs/core/views/hooks.py +++ b/readthedocs/core/views/hooks.py @@ -17,9 +17,12 @@ from readthedocs.builds.constants import LATEST from readthedocs.core.utils import trigger_build -from readthedocs.notifications.decorators import notify_deprecated_endpoint from readthedocs.projects import constants from readthedocs.projects.models import Feature, Project +from readthedocs.projects.notifications import ( + DeprecatedBuildWebhookNotification, + DeprecatedGitHubWebhookNotification, +) from readthedocs.projects.tasks import sync_repository_task log = logging.getLogger(__name__) @@ -127,7 +130,6 @@ def log_info(project, msg): msg=msg)) -@notify_deprecated_endpoint def _build_url(url, projects, branches): """ Map a URL onto specific projects to build that are linked to that URL. @@ -223,6 +225,9 @@ def github_build(request): # noqa: D205 branches ) projects = repo_projects | ssh_projects + # TODO remove _build_url call and replace with a 4xx response after + # Jan 31st. + DeprecatedGitHubWebhookNotification.for_project_users(projects) return _build_url(http_search_url, projects, branches) except NoProjectException: log.exception('Project match not found: url=%s', http_search_url) @@ -259,6 +264,9 @@ def gitlab_build(request): # noqa: D205 ) projects = get_project_from_url(search_url) if projects: + # TODO remove _build_url call and replace with a 4xx response after + # Mar 1st. + DeprecatedBuildWebhookNotification.for_project_users(projects) return _build_url(search_url, projects, branches) log.info('Project match not found: url=%s', search_url) @@ -327,6 +335,9 @@ def bitbucket_build(request): projects = get_project_from_url(search_url) if projects and branches: + # TODO remove _build_url call and replace with a 4xx response after + # Mar 1st. + DeprecatedBuildWebhookNotification.for_project_users(projects) return _build_url(search_url, projects, branches) if not branches: @@ -343,7 +354,6 @@ def bitbucket_build(request): @csrf_exempt -@notify_deprecated_endpoint def generic_build(request, project_id_or_slug=None): """ Generic webhook build endpoint. @@ -373,7 +383,13 @@ def generic_build(request, project_id_or_slug=None): if request.method == 'POST': slug = request.POST.get('version_slug', project.default_version) log.info( - "(Incoming Generic Build) %s [%s]", project.slug, slug) + "(Incoming Generic Build) %s [%s]", + project.slug, + slug, + ) + # TODO remove _build_url call and replace with a 4xx response after + # Mar 1st. + DeprecatedBuildWebhookNotification.for_project_users([project]) _build_version(project, slug) else: return HttpResponse("You must POST to this resource.") diff --git a/readthedocs/notifications/decorators.py b/readthedocs/notifications/decorators.py deleted file mode 100644 index 7817c50fea6..00000000000 --- a/readthedocs/notifications/decorators.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- - -import logging - -from django.db.models import Q -from django.http import HttpRequest - -from readthedocs.projects.models import Project -from readthedocs.projects.notifications import DeprecatedWebhookEndpointNotification - -log = logging.getLogger(__name__) - - -def notify_deprecated_endpoint(function): - """ - Decorator to notify owners that the endpoint is DEPRECATED. - - .. note:: - - See the class ``DeprecatedWebhookEndpointNotification`` which contains - all the logic to send notification for this messages properly without - spamming. - """ - def wrap(*args, **kwargs): - # Called from ``generic_build`` - project_id_or_slug = kwargs.get('project_id_or_slug') - - if project_id_or_slug: - projects = Project.objects.filter( - Q(pk=project_id_or_slug) | Q(slug=project_id_or_slug), - ) - else: - # Called from ``_build_url`` - projects = args[1] # ``projects`` argument - - if projects: - for project in projects: - # Send one notification to each owner of the project - for user in project.users.all(): - notification = DeprecatedWebhookEndpointNotification( - project, - HttpRequest(), - user, - ) - notification.send() - else: - log.info('Projects not found when hitting deprecated webhook') - - return function(*args, **kwargs) - - return wrap diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py index d3b4af2e272..0ea74b360a0 100644 --- a/readthedocs/projects/notifications.py +++ b/readthedocs/projects/notifications.py @@ -17,25 +17,38 @@ class ResourceUsageNotification(Notification): level = REQUIREMENT -class DeprecatedWebhookEndpointNotification(Notification): +class DeprecatedViewNotification(Notification): """ - Notification for the usage of deprecated webhook endpoints. - - Each time that a view decorated with ``notify_deprecated_endpoint`` is hit, - a new instance of this class is created. Then, ``.send`` is called and the - ``SiteBackend`` will create (avoiding duplication) a site notification and - the ``EmailBackend`` will do nothing (because of ``send_email=False``). - - Besides, a ``message_extends.models.Message`` object is created to track - sending an email if this endpoint is hit again after ``email_period``. When, - ``.send`` is call and the ``email_period`` was reach from the - ``Message.created`` time we mark ``send_email=True`` in this instance and - call the super ``.send`` method that will effectively send the email and - mark the message as ``extra_tags='email_sent'``. + Notification to alert user of a view that is going away. + + This notification is used for cases where we want to alert the project + users that a view that they are using is going to be going away. + + .. warning:: + This is currently used primarily for deprecated webhook endpoints, which + aren't even hit by a user. There are likely some mechanics to this + class that expect a webhook endpoint and not a generic view. + + The first time that a notification is sent to a user, ``SiteBackend`` will + create (avoiding duplication) a site notification and the ``EmailBackend`` + will send an email notification. The :py:cls:`Message` object will now have + ``extra_tags`` of ``email_delayed``. This means that the first email was + sent and that we won't send the second email for + ``DeprecatedViewNotification.email_period``. + + The second time that a notification is sent to a user, + :py:cls:`message_extends.models.Message` will deduplicate the site message + and we rely on message meta data, stored in ``extra_tags`` on the model, to + determine if an email was already sent. We will send a second email to a + :py:cls:`Message` that has ``email_delayed`` in extra_tags, at which point + we'll set ``extra_tags`` to have ``email_sent``. Any further attempts to + send a message won't work as the ``extra_tags`` is no longer + ``email_delayed``. """ - name = 'deprecated_webhook_endpoint' + # This is an abstract class, we won't set a name yet. + name = None context_object_name = 'project' subject = '{{ project.name }} project webhook needs to be updated' send_email = False @@ -43,18 +56,44 @@ class DeprecatedWebhookEndpointNotification(Notification): level = REQUIREMENT def __init__(self, context_object, request, user=None): - super(DeprecatedWebhookEndpointNotification, self).__init__( + super(DeprecatedViewNotification, self).__init__( context_object, request, user, ) self.message, created = self._create_message() + if self.name is None: + raise ValueError('{} is an abstract class.'.format( + self.__class__.__name__, + )) + # Mark this notification to be sent as email the first time that it's # created (user hits this endpoint for the first time) if created: self.send_email = True + @classmethod + def for_project_users(cls, projects): + """ + Notify project users of deprecated view. + + This is primarily used for deprecated webhook endpoints, though is not + particular to this usage. + + :param projects: List of project instances + :type projects: [:py:class:`Project`] + """ + for project in projects: + # Send one notification to each owner of the project + for user in project.users.all(): + notification = cls( + context_object=project, + request=HttpRequest(), + user=user, + ) + notification.send() + def _create_message(self): # Each time this class is instantiated we create a new Message (it's # de-duplicated by using the ``message``, ``user`` and ``extra_tags`` @@ -80,4 +119,14 @@ def send(self, *args, **kwargs): # noqa # de-duplicate the following one self._create_message() - super(DeprecatedWebhookEndpointNotification, self).send(*args, **kwargs) + super(DeprecatedViewNotification, self).send(*args, **kwargs) + + +class DeprecatedGitHubWebhookNotification(DeprecatedViewNotification): + + name = 'deprecated_github_webhook' + + +class DeprecatedBuildWebhookNotification(DeprecatedViewNotification): + + name = 'deprecated_build_webhook' diff --git a/readthedocs/rtd_tests/tests/test_notifications.py b/readthedocs/rtd_tests/tests/test_notifications.py index 2c36023f90c..32d50dff288 100644 --- a/readthedocs/rtd_tests/tests/test_notifications.py +++ b/readthedocs/rtd_tests/tests/test_notifications.py @@ -17,7 +17,7 @@ from readthedocs.notifications.backends import EmailBackend, SiteBackend from readthedocs.notifications.constants import ERROR, INFO_NON_PERSISTENT, WARNING_NON_PERSISTENT from readthedocs.projects.models import Project -from readthedocs.projects.notifications import DeprecatedWebhookEndpointNotification +from readthedocs.projects.notifications import DeprecatedViewNotification from readthedocs.builds.models import Build @@ -239,7 +239,7 @@ def setUp(self): self.user = fixture.get(User) self.request = HttpRequest() - self.notification = DeprecatedWebhookEndpointNotification( + self.notification = DeprecatedViewNotification( self.project, self.request, self.user, @@ -248,13 +248,13 @@ def setUp(self): def test_deduplication(self): self.assertEqual(PersistentMessage.objects.filter(user=self.user).count(), 1) for x in range(5): - DeprecatedWebhookEndpointNotification( + DeprecatedViewNotification( self.project, self.request, self.user, ) self.assertEqual(PersistentMessage.objects.filter(user=self.user).count(), 1) - DeprecatedWebhookEndpointNotification( + DeprecatedViewNotification( self.project, self.request, fixture.get(User), @@ -262,7 +262,7 @@ def test_deduplication(self): self.assertEqual(PersistentMessage.objects.count(), 2) self.notification.message.extra_tags = 'email_sent' self.notification.message.save() - DeprecatedWebhookEndpointNotification( + DeprecatedViewNotification( self.project, self.request, self.user, @@ -285,7 +285,7 @@ def test_send_email(self, send_email): # Hit the endpoint twice for x in range(2): - notification = DeprecatedWebhookEndpointNotification( + notification = DeprecatedViewNotification( self.project, self.request, self.user, @@ -294,7 +294,7 @@ def test_send_email(self, send_email): # the message also to be sure that no new notification was created self.assertEqual(PersistentMessage.objects.filter( user=self.user, - message__startswith=DeprecatedWebhookEndpointNotification.name).count(), + message__startswith=DeprecatedViewNotification.name).count(), 1, ) self.assertFalse(notification.send_email) # second notification @@ -313,7 +313,7 @@ def test_send_email(self, send_email): # A new Message object is created self.assertEqual(PersistentMessage.objects.filter( user=self.user, - message__startswith=DeprecatedWebhookEndpointNotification.name).count(), + message__startswith=DeprecatedViewNotification.name).count(), 2, ) self.assertEqual(PersistentMessage.objects.last().extra_tags, 'email_delayed') diff --git a/readthedocs/templates/projects/notifications/deprecated_build_webhook_email.html b/readthedocs/templates/projects/notifications/deprecated_build_webhook_email.html new file mode 100644 index 00000000000..503cf612eb5 --- /dev/null +++ b/readthedocs/templates/projects/notifications/deprecated_build_webhook_email.html @@ -0,0 +1,6 @@ +

Your project, {{ project.name }}, is currently using a legacy incoming webhook to trigger builds on Read the Docs. Effective March 1st, 2019, Read the Docs will no longer accept incoming webhooks through these endpoints.

+ +

To continue building your Read the Docs project on changes to your repository, you will need to configure a new webhook with your VCS provider. You can find more information on how to configure a new webhook in our documentation, at:

+ +{% comment %}Plain text link because of text version of email{% endcomment %} +

https://docs.readthedocs.io/en/latest/webhooks.html#webhook-deprecated-endpoints

diff --git a/readthedocs/templates/projects/notifications/deprecated_build_webhook_site.html b/readthedocs/templates/projects/notifications/deprecated_build_webhook_site.html new file mode 100644 index 00000000000..8c2f84973cb --- /dev/null +++ b/readthedocs/templates/projects/notifications/deprecated_build_webhook_site.html @@ -0,0 +1 @@ +Your project, {{ project.name }}, needs to be reconfigured in order to continue building automatically after March 1st, 2019. For more information, see our documentation on webhook integrations. diff --git a/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_email.html b/readthedocs/templates/projects/notifications/deprecated_github_webhook_email.html similarity index 81% rename from readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_email.html rename to readthedocs/templates/projects/notifications/deprecated_github_webhook_email.html index 6ab3247fc21..7d352390d42 100644 --- a/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_email.html +++ b/readthedocs/templates/projects/notifications/deprecated_github_webhook_email.html @@ -5,4 +5,4 @@

You can find more information on our webhook intergrations in our documentation, at:

{% comment %}Plain text link because of text version of email{% endcomment %} -

https://docs.readthedocs.io/en/latest/webhooks.html

+

https://docs.readthedocs.io/en/latest/webhooks.html#webhook-github-services

diff --git a/readthedocs/templates/projects/notifications/deprecated_github_webhook_site.html b/readthedocs/templates/projects/notifications/deprecated_github_webhook_site.html new file mode 100644 index 00000000000..c41290c4b09 --- /dev/null +++ b/readthedocs/templates/projects/notifications/deprecated_github_webhook_site.html @@ -0,0 +1 @@ +Your project, {{ project.name }}, needs to be reconfigured in order to continue building automatically after Jan 31st, 2019. For more information, see our documentation on webhook integrations. diff --git a/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_site.html b/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_site.html deleted file mode 100644 index 5f1ada22295..00000000000 --- a/readthedocs/templates/projects/notifications/deprecated_webhook_endpoint_site.html +++ /dev/null @@ -1 +0,0 @@ -Your project, {{ project.name }}, needs to be reconfigured in order to continue building automatically after Jan 31st. For more information, see our documentation on webhook integrations. From 721137b09af99668e08214b794f845bf9bfc9439 Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Thu, 10 Jan 2019 01:43:58 -0700 Subject: [PATCH 04/10] More renaming and slight refactor Found out 2x messages are being generated, so this stops the automated mechanism for triggering these messages. --- readthedocs/core/views/hooks.py | 16 --------- readthedocs/notifications/backends.py | 10 +++--- readthedocs/notifications/notification.py | 1 + readthedocs/projects/notifications.py | 3 +- .../rtd_tests/tests/test_notifications.py | 33 ++++++++++++++----- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/readthedocs/core/views/hooks.py b/readthedocs/core/views/hooks.py index 3c1802ffdef..c6d4bc91188 100644 --- a/readthedocs/core/views/hooks.py +++ b/readthedocs/core/views/hooks.py @@ -19,10 +19,6 @@ from readthedocs.core.utils import trigger_build from readthedocs.projects import constants from readthedocs.projects.models import Feature, Project -from readthedocs.projects.notifications import ( - DeprecatedBuildWebhookNotification, - DeprecatedGitHubWebhookNotification, -) from readthedocs.projects.tasks import sync_repository_task log = logging.getLogger(__name__) @@ -225,9 +221,6 @@ def github_build(request): # noqa: D205 branches ) projects = repo_projects | ssh_projects - # TODO remove _build_url call and replace with a 4xx response after - # Jan 31st. - DeprecatedGitHubWebhookNotification.for_project_users(projects) return _build_url(http_search_url, projects, branches) except NoProjectException: log.exception('Project match not found: url=%s', http_search_url) @@ -264,9 +257,6 @@ def gitlab_build(request): # noqa: D205 ) projects = get_project_from_url(search_url) if projects: - # TODO remove _build_url call and replace with a 4xx response after - # Mar 1st. - DeprecatedBuildWebhookNotification.for_project_users(projects) return _build_url(search_url, projects, branches) log.info('Project match not found: url=%s', search_url) @@ -335,9 +325,6 @@ def bitbucket_build(request): projects = get_project_from_url(search_url) if projects and branches: - # TODO remove _build_url call and replace with a 4xx response after - # Mar 1st. - DeprecatedBuildWebhookNotification.for_project_users(projects) return _build_url(search_url, projects, branches) if not branches: @@ -387,9 +374,6 @@ def generic_build(request, project_id_or_slug=None): project.slug, slug, ) - # TODO remove _build_url call and replace with a 4xx response after - # Mar 1st. - DeprecatedBuildWebhookNotification.for_project_users([project]) _build_version(project, slug) else: return HttpResponse("You must POST to this resource.") diff --git a/readthedocs/notifications/backends.py b/readthedocs/notifications/backends.py index 28529f1f1a6..fada113272d 100644 --- a/readthedocs/notifications/backends.py +++ b/readthedocs/notifications/backends.py @@ -32,11 +32,7 @@ def send_notification(request, notification): backends = getattr(settings, 'NOTIFICATION_BACKENDS', []) for cls_name in backends: backend = import_string(cls_name)(request) - # Do not send email notification if defined explicitly - if backend.name == EmailBackend.name and not notification.send_email: - pass - else: - backend.send(notification) + backend.send(notification) class Backend(object): @@ -60,6 +56,8 @@ class EmailBackend(Backend): name = 'email' def send(self, notification): + if not notification.send_email: + return # FIXME: if the level is an ERROR an email is received and sometimes # it's not necessary. This behavior should be clearly documented in the # code @@ -114,6 +112,6 @@ def send(self, notification): backend_name=self.name, source_format=HTML, ), - extra_tags='', + extra_tags=notification.extra_tags, user=notification.user, ) diff --git a/readthedocs/notifications/notification.py b/readthedocs/notifications/notification.py index d2300e1f4f0..c4d1ca4de77 100644 --- a/readthedocs/notifications/notification.py +++ b/readthedocs/notifications/notification.py @@ -35,6 +35,7 @@ class Notification(object): subject = None user = None send_email = True + extra_tags = '' def __init__(self, context_object, request, user=None): self.object = context_object diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py index 0ea74b360a0..4e86da1b28f 100644 --- a/readthedocs/projects/notifications.py +++ b/readthedocs/projects/notifications.py @@ -4,6 +4,7 @@ from __future__ import absolute_import from datetime import timedelta from django.utils import timezone +from django.http import HttpRequest from messages_extends.models import Message from readthedocs.notifications import Notification from readthedocs.notifications.constants import REQUIREMENT @@ -74,7 +75,7 @@ def __init__(self, context_object, request, user=None): self.send_email = True @classmethod - def for_project_users(cls, projects): + def notify_project_users(cls, projects): """ Notify project users of deprecated view. diff --git a/readthedocs/rtd_tests/tests/test_notifications.py b/readthedocs/rtd_tests/tests/test_notifications.py index 32d50dff288..c82d81865c0 100644 --- a/readthedocs/rtd_tests/tests/test_notifications.py +++ b/readthedocs/rtd_tests/tests/test_notifications.py @@ -17,7 +17,10 @@ from readthedocs.notifications.backends import EmailBackend, SiteBackend from readthedocs.notifications.constants import ERROR, INFO_NON_PERSISTENT, WARNING_NON_PERSISTENT from readthedocs.projects.models import Project -from readthedocs.projects.notifications import DeprecatedViewNotification +from readthedocs.projects.notifications import ( + DeprecatedGitHubWebhookNotification, + DeprecatedBuildWebhookNotification, +) from readthedocs.builds.models import Build @@ -235,26 +238,38 @@ class DeprecatedWebhookEndpointNotificationTests(TestCase): def setUp(self): PersistentMessage.objects.all().delete() - self.project = fixture.get(Project) self.user = fixture.get(User) + self.project = fixture.get(Project, users=[self.user]) self.request = HttpRequest() - self.notification = DeprecatedViewNotification( + self.notification = DeprecatedBuildWebhookNotification( self.project, self.request, self.user, ) + def test_classmethod_for_project_users(self): + user = fixture.get(User) + project = fixture.get(Project, main_language_project=None) + project.users.add(user) + project.refresh_from_db() + self.assertEqual(project.users.count(), 1) + self.assertEqual(PersistentMessage.objects.filter(user=user).count(), 0) + DeprecatedGitHubWebhookNotification.notify_project_users([project]) + self.assertEqual(PersistentMessage.objects.filter(user=user).count(), 1) + DeprecatedGitHubWebhookNotification.notify_project_users([project]) + self.assertEqual(PersistentMessage.objects.filter(user=user).count(), 1) + def test_deduplication(self): self.assertEqual(PersistentMessage.objects.filter(user=self.user).count(), 1) for x in range(5): - DeprecatedViewNotification( + DeprecatedBuildWebhookNotification( self.project, self.request, self.user, ) self.assertEqual(PersistentMessage.objects.filter(user=self.user).count(), 1) - DeprecatedViewNotification( + DeprecatedBuildWebhookNotification( self.project, self.request, fixture.get(User), @@ -262,7 +277,7 @@ def test_deduplication(self): self.assertEqual(PersistentMessage.objects.count(), 2) self.notification.message.extra_tags = 'email_sent' self.notification.message.save() - DeprecatedViewNotification( + DeprecatedBuildWebhookNotification( self.project, self.request, self.user, @@ -285,7 +300,7 @@ def test_send_email(self, send_email): # Hit the endpoint twice for x in range(2): - notification = DeprecatedViewNotification( + notification = DeprecatedBuildWebhookNotification( self.project, self.request, self.user, @@ -294,7 +309,7 @@ def test_send_email(self, send_email): # the message also to be sure that no new notification was created self.assertEqual(PersistentMessage.objects.filter( user=self.user, - message__startswith=DeprecatedViewNotification.name).count(), + message__startswith=DeprecatedBuildWebhookNotification.name).count(), 1, ) self.assertFalse(notification.send_email) # second notification @@ -313,7 +328,7 @@ def test_send_email(self, send_email): # A new Message object is created self.assertEqual(PersistentMessage.objects.filter( user=self.user, - message__startswith=DeprecatedViewNotification.name).count(), + message__startswith=DeprecatedBuildWebhookNotification.name).count(), 2, ) self.assertEqual(PersistentMessage.objects.last().extra_tags, 'email_delayed') From 4aa1409303e01d14384143a47507cace9be6e35a Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Thu, 10 Jan 2019 01:47:34 -0700 Subject: [PATCH 05/10] Update dates --- .../projects/notifications/deprecated_build_webhook_email.html | 2 +- .../projects/notifications/deprecated_build_webhook_site.html | 2 +- .../projects/notifications/deprecated_github_webhook_site.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/readthedocs/templates/projects/notifications/deprecated_build_webhook_email.html b/readthedocs/templates/projects/notifications/deprecated_build_webhook_email.html index 503cf612eb5..fb19f176532 100644 --- a/readthedocs/templates/projects/notifications/deprecated_build_webhook_email.html +++ b/readthedocs/templates/projects/notifications/deprecated_build_webhook_email.html @@ -1,4 +1,4 @@ -

Your project, {{ project.name }}, is currently using a legacy incoming webhook to trigger builds on Read the Docs. Effective March 1st, 2019, Read the Docs will no longer accept incoming webhooks through these endpoints.

+

Your project, {{ project.name }}, is currently using a legacy incoming webhook to trigger builds on Read the Docs. Effective April 1st, 2019, Read the Docs will no longer accept incoming webhooks through these endpoints.

To continue building your Read the Docs project on changes to your repository, you will need to configure a new webhook with your VCS provider. You can find more information on how to configure a new webhook in our documentation, at:

diff --git a/readthedocs/templates/projects/notifications/deprecated_build_webhook_site.html b/readthedocs/templates/projects/notifications/deprecated_build_webhook_site.html index 8c2f84973cb..33c5e27e616 100644 --- a/readthedocs/templates/projects/notifications/deprecated_build_webhook_site.html +++ b/readthedocs/templates/projects/notifications/deprecated_build_webhook_site.html @@ -1 +1 @@ -Your project, {{ project.name }}, needs to be reconfigured in order to continue building automatically after March 1st, 2019. For more information, see our documentation on webhook integrations. +Your project, {{ project.name }}, needs to be reconfigured in order to continue building automatically after April 1st, 2019. For more information, see our documentation on webhook integrations. diff --git a/readthedocs/templates/projects/notifications/deprecated_github_webhook_site.html b/readthedocs/templates/projects/notifications/deprecated_github_webhook_site.html index c41290c4b09..0832efaf793 100644 --- a/readthedocs/templates/projects/notifications/deprecated_github_webhook_site.html +++ b/readthedocs/templates/projects/notifications/deprecated_github_webhook_site.html @@ -1 +1 @@ -Your project, {{ project.name }}, needs to be reconfigured in order to continue building automatically after Jan 31st, 2019. For more information, see our documentation on webhook integrations. +Your project, {{ project.name }}, needs to be reconfigured in order to continue building automatically after January 31st, 2019. For more information, see our documentation on webhook integrations. From 21005fe9a0d2cbc5a3d79c55d3cfeba084a25f8e Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Thu, 10 Jan 2019 01:48:26 -0700 Subject: [PATCH 06/10] Also update docs --- docs/webhooks.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/webhooks.rst b/docs/webhooks.rst index e637e4085c0..68c873a86d1 100644 --- a/docs/webhooks.rst +++ b/docs/webhooks.rst @@ -186,8 +186,8 @@ account. .. _webhook-deprecated-endpoints: -I was warned that my project won't automatically build after Mar 1st -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +I was warned that my project won't automatically build after March 1st +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In addition to :ref:`no longer supporting GitHub Services `, we have decided to no longer support several other legacy incoming webhook From 62de090875446e1f589a6b29857299babf9e5d5e Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Thu, 10 Jan 2019 02:34:04 -0700 Subject: [PATCH 07/10] Typo on date --- docs/webhooks.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/webhooks.rst b/docs/webhooks.rst index 68c873a86d1..7e6e109bf3e 100644 --- a/docs/webhooks.rst +++ b/docs/webhooks.rst @@ -186,7 +186,7 @@ account. .. _webhook-deprecated-endpoints: -I was warned that my project won't automatically build after March 1st +I was warned that my project won't automatically build after April 1st ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In addition to :ref:`no longer supporting GitHub Services `, From db716254ca9543fbee70dc4ecdb646896411accd Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Thu, 10 Jan 2019 12:02:05 -0700 Subject: [PATCH 08/10] Back out some more of the changes to notifications to make them operable without automation --- readthedocs/notifications/backends.py | 3 + readthedocs/projects/notifications.py | 82 +------------------ .../rtd_tests/tests/test_notifications.py | 82 +++---------------- requirements/testing.txt | 2 - 4 files changed, 15 insertions(+), 154 deletions(-) diff --git a/readthedocs/notifications/backends.py b/readthedocs/notifications/backends.py index fada113272d..6cc794ddbbf 100644 --- a/readthedocs/notifications/backends.py +++ b/readthedocs/notifications/backends.py @@ -51,6 +51,9 @@ class EmailBackend(Backend): The content body is first rendered from an on-disk template, then passed into the standard email templates as a string. + + If the notification is set to ``send_email=False``, this backend will exit + early from :py:meth:`send`. """ name = 'email' diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py index 4e86da1b28f..db7838bc80e 100644 --- a/readthedocs/projects/notifications.py +++ b/readthedocs/projects/notifications.py @@ -20,68 +20,17 @@ class ResourceUsageNotification(Notification): class DeprecatedViewNotification(Notification): - """ - Notification to alert user of a view that is going away. - - This notification is used for cases where we want to alert the project - users that a view that they are using is going to be going away. - - .. warning:: - This is currently used primarily for deprecated webhook endpoints, which - aren't even hit by a user. There are likely some mechanics to this - class that expect a webhook endpoint and not a generic view. - - The first time that a notification is sent to a user, ``SiteBackend`` will - create (avoiding duplication) a site notification and the ``EmailBackend`` - will send an email notification. The :py:cls:`Message` object will now have - ``extra_tags`` of ``email_delayed``. This means that the first email was - sent and that we won't send the second email for - ``DeprecatedViewNotification.email_period``. - - The second time that a notification is sent to a user, - :py:cls:`message_extends.models.Message` will deduplicate the site message - and we rely on message meta data, stored in ``extra_tags`` on the model, to - determine if an email was already sent. We will send a second email to a - :py:cls:`Message` that has ``email_delayed`` in extra_tags, at which point - we'll set ``extra_tags`` to have ``email_sent``. Any further attempts to - send a message won't work as the ``extra_tags`` is no longer - ``email_delayed``. - """ - - # This is an abstract class, we won't set a name yet. - name = None + """Notification to alert user of a view that is going away.""" + context_object_name = 'project' subject = '{{ project.name }} project webhook needs to be updated' - send_email = False - email_period = timedelta(days=7) level = REQUIREMENT - def __init__(self, context_object, request, user=None): - super(DeprecatedViewNotification, self).__init__( - context_object, - request, - user, - ) - self.message, created = self._create_message() - - if self.name is None: - raise ValueError('{} is an abstract class.'.format( - self.__class__.__name__, - )) - - # Mark this notification to be sent as email the first time that it's - # created (user hits this endpoint for the first time) - if created: - self.send_email = True - @classmethod def notify_project_users(cls, projects): """ Notify project users of deprecated view. - This is primarily used for deprecated webhook endpoints, though is not - particular to this usage. - :param projects: List of project instances :type projects: [:py:class:`Project`] """ @@ -95,33 +44,6 @@ def notify_project_users(cls, projects): ) notification.send() - def _create_message(self): - # Each time this class is instantiated we create a new Message (it's - # de-duplicated by using the ``message``, ``user`` and ``extra_tags`` - # status) - return Message.objects.get_or_create( - message='{}: {}'.format(self.name, self.get_subject()), - level=self.level, - user=self.user, - extra_tags='email_delayed', - ) - - def send(self, *args, **kwargs): # noqa - if self.message.created + self.email_period < timezone.now(): - # Mark this instance to really send the email and rely on the - # EmailBackend to effectively send the email - self.send_email = True - - # Mark the message as sent and send the email - self.message.extra_tags = 'email_sent' - self.message.save() - - # Create a new Message with ``email_delayed`` so we are prepared to - # de-duplicate the following one - self._create_message() - - super(DeprecatedViewNotification, self).send(*args, **kwargs) - class DeprecatedGitHubWebhookNotification(DeprecatedViewNotification): diff --git a/readthedocs/rtd_tests/tests/test_notifications.py b/readthedocs/rtd_tests/tests/test_notifications.py index c82d81865c0..1fa16323df4 100644 --- a/readthedocs/rtd_tests/tests/test_notifications.py +++ b/readthedocs/rtd_tests/tests/test_notifications.py @@ -10,7 +10,6 @@ from django.test.utils import override_settings from django.contrib.auth.models import User, AnonymousUser from django.utils import timezone -from freezegun import freeze_time from messages_extends.models import Message as PersistentMessage from readthedocs.notifications import Notification, SiteNotification @@ -248,87 +247,26 @@ def setUp(self): self.user, ) - def test_classmethod_for_project_users(self): + @mock.patch('readthedocs.notifications.backends.send_email') + def test_dedupliation(self, send_email): user = fixture.get(User) project = fixture.get(Project, main_language_project=None) project.users.add(user) project.refresh_from_db() self.assertEqual(project.users.count(), 1) + self.assertEqual(PersistentMessage.objects.filter(user=user).count(), 0) DeprecatedGitHubWebhookNotification.notify_project_users([project]) - self.assertEqual(PersistentMessage.objects.filter(user=user).count(), 1) - DeprecatedGitHubWebhookNotification.notify_project_users([project]) - self.assertEqual(PersistentMessage.objects.filter(user=user).count(), 1) - - def test_deduplication(self): - self.assertEqual(PersistentMessage.objects.filter(user=self.user).count(), 1) - for x in range(5): - DeprecatedBuildWebhookNotification( - self.project, - self.request, - self.user, - ) - self.assertEqual(PersistentMessage.objects.filter(user=self.user).count(), 1) - DeprecatedBuildWebhookNotification( - self.project, - self.request, - fixture.get(User), - ) - self.assertEqual(PersistentMessage.objects.count(), 2) - self.notification.message.extra_tags = 'email_sent' - self.notification.message.save() - DeprecatedBuildWebhookNotification( - self.project, - self.request, - self.user, - ) - self.assertEqual(PersistentMessage.objects.filter(user=self.user).count(), 2) - self.assertEqual(PersistentMessage.objects.count(), 3) - @mock.patch('readthedocs.notifications.backends.send_email') - def test_send_email(self, send_email): - # After creating the notification object, ``send_email=True`` - self.assertTrue(self.notification.send_email) - - # Calling ``.send`` on this instance will send the email and the - # ``Message`` will stay in ``email_delayed`` status. - self.notification.send() - self.notification.message.refresh_from_db() - self.assertEqual(self.notification.message.extra_tags, 'email_delayed') + # Site and email notification will go out, site message doesn't have + # any reason to deduplicate yet + self.assertEqual(PersistentMessage.objects.filter(user=user).count(), 1) self.assertTrue(send_email.called) send_email.reset_mock() - - # Hit the endpoint twice - for x in range(2): - notification = DeprecatedBuildWebhookNotification( - self.project, - self.request, - self.user, - ) - # A SiteNotification is created after calling ``.send`` so we filter for - # the message also to be sure that no new notification was created - self.assertEqual(PersistentMessage.objects.filter( - user=self.user, - message__startswith=DeprecatedBuildWebhookNotification.name).count(), - 1, - ) - self.assertFalse(notification.send_email) # second notification - notification.send() self.assertFalse(send_email.called) - self.assertEqual(notification.message.extra_tags, 'email_delayed') - - # In 7 days, when the ``.send`` method is called from a new created - # instance, this method will send the email and mark this ``Message`` as - # ``email_sent`` - with freeze_time(timezone.now() + timedelta(days=7)): - notification.send() + # Expect the site message to deduplicate, the email won't + DeprecatedGitHubWebhookNotification.notify_project_users([project]) + self.assertEqual(PersistentMessage.objects.filter(user=user).count(), 1) self.assertTrue(send_email.called) - self.assertEqual(notification.message.extra_tags, 'email_sent') - # A new Message object is created - self.assertEqual(PersistentMessage.objects.filter( - user=self.user, - message__startswith=DeprecatedBuildWebhookNotification.name).count(), - 2, - ) - self.assertEqual(PersistentMessage.objects.last().extra_tags, 'email_delayed') + send_email.reset_mock() diff --git a/requirements/testing.txt b/requirements/testing.txt index 01edf9a67d2..dae26eb67af 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -19,8 +19,6 @@ Mercurial==4.4.2 yamale==1.8.0 pytest-mock==1.10.0 -freezegun==0.3.11 - # local debugging tools datadiff ipdb From 7284884101b605aa891a7c18bd9f5c124ea986c6 Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Thu, 10 Jan 2019 12:45:21 -0700 Subject: [PATCH 09/10] Add admin method for notification --- readthedocs/projects/admin.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/readthedocs/projects/admin.py b/readthedocs/projects/admin.py index d55954fdbcf..09b3ba9faca 100644 --- a/readthedocs/projects/admin.py +++ b/readthedocs/projects/admin.py @@ -30,12 +30,20 @@ ProjectRelationship, WebHook, ) -from .notifications import ResourceUsageNotification +from .notifications import ( + ResourceUsageNotification, + DeprecatedBuildWebhookNotification, + DeprecatedGitHubWebhookNotification, +) from .tasks import remove_dir class ProjectSendNotificationView(SendNotificationView): - notification_classes = [ResourceUsageNotification] + notification_classes = [ + ResourceUsageNotification, + DeprecatedBuildWebhookNotification, + DeprecatedGitHubWebhookNotification, + ] def get_object_recipients(self, obj): for owner in obj.users.all(): From 107356df6ab276f6d61796ce3175e291ed93c9f8 Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Thu, 10 Jan 2019 13:19:14 -0700 Subject: [PATCH 10/10] Add admin filter for project features --- readthedocs/projects/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/projects/admin.py b/readthedocs/projects/admin.py index 09b3ba9faca..4640a7abc73 100644 --- a/readthedocs/projects/admin.py +++ b/readthedocs/projects/admin.py @@ -127,7 +127,7 @@ class ProjectAdmin(GuardedModelAdmin): list_display = ('name', 'slug', 'repo', 'repo_type', 'featured') list_filter = ('repo_type', 'featured', 'privacy_level', 'documentation_type', 'programming_language', - ProjectOwnerBannedFilter) + 'feature__feature_id', ProjectOwnerBannedFilter) list_editable = ('featured',) search_fields = ('slug', 'repo') inlines = [ProjectRelationshipInline, RedirectInline,