Skip to content

Support for generic webhooks #8522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 50 commits into from
Nov 9, 2021
Merged

Support for generic webhooks #8522

merged 50 commits into from
Nov 9, 2021

Conversation

stsewd
Copy link
Member

@stsewd stsewd commented Sep 22, 2021

Currently we support webhooks to notify external services when a build starts/fails. The problem with this is that most of the time users want a quick integration with slack or whatever, but those services require you to send a payload with a determined format (usually just a json body). So, in order to have that, users need to implement a proxy between our webhooks and the service they want to use. In order to provide more flexibility and easy to integrate webhooks I'm reworking our current implementation like so:

  • Users can define the format of the payload we are going to send on each event (we do this by providing some placeholders)
  • Users can select to what events their webhook is subscribed to.
  • We provide a signature of the payload for users to verify the precedence of the webhook.
  • Webhooks responses (aka http exchanges) are saved for each webhook, so is more easy to debug.

To make it more easy, we could document or suggest some "quick templates" for users to use, like a nice notification for slack or discord and some step by step tutorials https://api.slack.com/messaging/webhooks https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks

Moving webhooks to their own page

Currently, we have webhooks under the "Notifications" tab,
there they are mixed with email notifications.

In order to have a better UI, I have moved the webhooks to its own page instead,
and renamed the other section as "Email notifications".

And to avoid having two pages to manage the webhooks,
the old and new webhooks can be accessed from the same page.

I have added some checks to see if the user has old webhooks and show a note
that those have been moved to the other page.

Old webhooks don't have a custom payload, but instead will be using the defined payload from https://docs.readthedocs.io/en/stable/guides/build-notifications.html#using-webhook.
Users can migrate to a custom payload by just editing the webhook,
we don't support creating webhooks from the old implementation.

Screenshots

Screenshot 2021-09-28 at 14-34-43 Webhooks Read the Docs
Screenshot 2021-09-28 at 14-35-32 Webhooks Read the Docs
Screenshot 2021-09-28 at 14-35-15 Webhooks Read the Docs

Some confusing documentation...

So, in our docs we have a webhooks page https://docs.readthedocs.io/en/stable/webhooks.html,
but they are about the webhook we provide in order for vcs services to notify us when a new branch/tag is created or when new changes are pushed so we can build the docs for those. We actually refer to those as "integrations" rather than webhooks in our codebase/docs.

The actual webhooks docs are in a page called "build notifications"
https://docs.readthedocs.io/en/stable/guides/build-notifications.html.
Which make sense, but we could expand our webhooks to listen to more events outside builds.

Future improvements

  • Allow to mark a webhook as active/inactive, this is useful to test things
    without having to delete the webhook in order to stop sending events to it.
  • Allow to hit the webhooks when building from an external version (aka from pull requests).
    Right now we don't send notifications on builds from external versions,
    doing this by default could be too spammy.
  • Allow filtering by version name/privacy-level before sending a webhook.
    Currently, we send webhooks for any version on the subscribed events
    (but I do see having people only want to be notified about changes on master for example).
    Users could implement a proxy to filter those, or we could implement a filter in our side so they don't need to maintain that.
  • More events: when a version is created/deleted on rtd?

Closes #6942

Copy link
Member

@humitos humitos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a good first approach to me. I did a non-complete review for now and I left some comments.

Comment on lines 1316 to +1317
if not self.version or self.version.type != EXTERNAL:
send_notifications.delay(version_pk, build_pk=build_pk, email=email)
build_tasks.send_build_notifications.delay(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we may want to add this as a config for the Webhook. Like a checkbox for Send notifications on PRs so the user can decide whether or not to send them instead of us deciding by them.

@stsewd stsewd force-pushed the webhooks branch 6 times, most recently from c93065a to fd27634 Compare September 28, 2021 01:24
@astrojuanlu
Copy link
Contributor

Does this solve #8137 and #8364?

@stsewd
Copy link
Member Author

stsewd commented Sep 28, 2021

Does this solve #8137 and #8364?

Nope, those are vcs integrations. Integrations send a payload to our webhook (rtd.org/webhook/), meantime in webhooks, we send a payload to the given url.

@stsewd stsewd force-pushed the webhooks branch 3 times, most recently from 2d05db6 to 9843252 Compare September 28, 2021 19:59
Comment on lines +623 to +626
HttpExchange.objects.from_requests_exchange(
response=response,
related_object=webhook,
)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could create this object in two steps, before sending the request and updating it after the request was made. This is so we save the exchange even if the request failed (like timeout).

model = WebHook
fields = ['project', 'url', 'events', 'payload', 'secret']
widgets = {
'events': forms.CheckboxSelectMultiple,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is better than the list, and we don't have that many events. I wanted to show something similar to what github does (the name and the description)
Screenshot 2021-09-28 at 15-07-07 Webhook · https requires io github web-hook · readthedocs readthedocs org

but looks like we would need to just manually do that, there isn't a widget that does it for us :/

Comment on lines 1555 to 1563
'${event}': event,
'${build.id}': build.id,
'${build.commit}': build.commit or '',
'${organization.name}': organization_name,
'${organization.slug}': organization_slug,
'${project.slug}': project.slug,
'${project.name}': project.name,
'${version.slug}': version.slug,
'${version.name}': version.verbose_name,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't know what others substitutions would be useful

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URLs: project, version, build on Read the Docs and, maybe on VCS

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added some urls, but most of that information can be built using the pk or slug of the models.

</p>

{% if has_old_webhooks %}
<p class="help_text">
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could have a "warning" class or something more prominent, but don't think we are using a class like that in somewhere else.

@@ -0,0 +1,39 @@
{% extends "projects/project_edit_base.html" %}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to share the same file instead of copy/paste?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The body could be moved to another file. But I do see this having more options (like retry the webhook), so don't know.


{# If the webhook was created from the old implementation, it doesn't have a custom payload. #}
{% if object.pk and not object.payload %}
<p class="help_text">
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, could be a warning class or something

@stsewd
Copy link
Member Author

stsewd commented Sep 28, 2021

Think this should be ready for review, only docs are missing.

@stsewd stsewd marked this pull request as ready for review September 28, 2021 21:10
substitutions = {
'${event}': event,
'${build.id}': build.id,
'${build.commit}': build.commit or '',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was thinking we could have ${build.commit.short} to display only the 7/8 first chars of the commit.

@stsewd stsewd requested a review from a team September 29, 2021 20:54
Comment on lines +485 to +488
def __init__(self, version, build, event):
self.version = version
self.build = build
self.project = version.project
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this requires version and build? It should be enough just with the build.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old code was sending the build and version pk separately, don't know if there is a case where a build doesn't have the version attached.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it still required with the new code after the refactor? Can we remove it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea why the old code required it, so not sure about changing that on this PR.

Copy link
Member

@humitos humitos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, the PR's description looks awesome! 💯 --It helped me a lot to understand all the changes.

I'd say that all the future improvements could be an issue/issues. Besides, together with those improvements, I think we will also want a way to send extra HTTP headers together with a custom body.

The PR looks great! The only thing I'd like to change before merging, is the substitution syntax (${variable}) to use the Django Template syntax instead ({{ variable }}) which is more known.

@@ -0,0 +1,39 @@
{% extends "projects/project_edit_base.html" %}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to share the same file instead of copy/paste?

Comment on lines 1555 to 1563
'${event}': event,
'${build.id}': build.id,
'${build.commit}': build.commit or '',
'${organization.name}': organization_name,
'${organization.slug}': organization_slug,
'${project.slug}': project.slug,
'${project.name}': project.name,
'${version.slug}': version.slug,
'${version.name}': version.verbose_name,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URLs: project, version, build on Read the Docs and, maybe on VCS

@stsewd
Copy link
Member Author

stsewd commented Oct 26, 2021

Yep, I struggled with GitHub notifications on this one. In any case, wdyt of #8522 (comment) ?

that can be done with some duplication, this is creating a webhook for each event just changing the payload. But, I think the most common case for slack notifications is failed builds.

@ericholscher
Copy link
Member

I feel like we've been talking about this for a long time. I'd like to propose the following path forward:

  • Use {{}} for the variables, so we can close that conversation. It's still replacement only, and we move on from that conversation
  • We ship this with the slack & discord examples in docs
  • We work on another PR that adds better support for common extensions (probably Slack to start), which we control the format of

I feel like this is the path forward, but this PR has gotten overwhelming, and we should just get this baseline feature merged.

@stsewd
Copy link
Member Author

stsewd commented Oct 28, 2021

Use {{}} for the variables, so we can close that conversation. It's still replacement only, and we move on from that conversation

Is that with one space around the name? Or is it using the template engine from django to support all variations?

@ericholscher
Copy link
Member

Is that with one space around the name? Or is it using the template engine from django to support all variations?

1 or 0 spaces. Should be simple enough to implement (eg. just loop over the list of replacements twice).

@stsewd
Copy link
Member Author

stsewd commented Nov 2, 2021

Changed to allow {{ foo }} or {{foo}} (I still think if we go for {{ foo }} we should support just one variation), start_date is still iso-formated without microseconds and tzinfo (2021-10-25T16:02:25)

Copy link
Member

@ericholscher ericholscher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this forward 👍

@astrojuanlu astrojuanlu requested a review from a team as a code owner November 8, 2021 22:23
@astrojuanlu astrojuanlu removed the request for review from a team November 8, 2021 22:25
@stsewd stsewd merged commit d4f6873 into master Nov 9, 2021
@stsewd stsewd deleted the webhooks branch November 9, 2021 21:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

users cannot customize notification message's format
4 participants