diff --git a/static/images/integrations/bot_avatars/opencollective.png b/static/images/integrations/bot_avatars/opencollective.png new file mode 100644 index 0000000000..839e43b315 Binary files /dev/null and b/static/images/integrations/bot_avatars/opencollective.png differ diff --git a/static/images/integrations/logos/opencollective.svg b/static/images/integrations/logos/opencollective.svg new file mode 100644 index 0000000000..dcb6f06bcf --- /dev/null +++ b/static/images/integrations/logos/opencollective.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/images/integrations/opencollective/001.png b/static/images/integrations/opencollective/001.png new file mode 100644 index 0000000000..6805c86eb3 Binary files /dev/null and b/static/images/integrations/opencollective/001.png differ diff --git a/zerver/lib/integrations.py b/zerver/lib/integrations.py index a3d28e7eb2..ed09408d23 100644 --- a/zerver/lib/integrations.py +++ b/zerver/lib/integrations.py @@ -423,6 +423,9 @@ WEBHOOK_INTEGRATIONS: List[WebhookIntegration] = [ stream_name="opbeat", function="zerver.webhooks.opbeat.view.api_opbeat_webhook", ), + WebhookIntegration( + "opencollective", ["communication"], display_name="Open Collective Incoming Webhook" + ), WebhookIntegration("opsgenie", ["meta-integration", "monitoring"]), WebhookIntegration("pagerduty", ["monitoring"], display_name="PagerDuty"), WebhookIntegration("papertrail", ["monitoring"]), @@ -761,6 +764,7 @@ DOC_SCREENSHOT_CONFIG: Dict[str, List[BaseScreenshotConfig]] = { ScreenshotConfig("incident_closed.json", "003.png"), ], "opbeat": [ScreenshotConfig("error_reopen.json")], + "opencollective": [ScreenshotConfig("one_time_donation.json")], "opsgenie": [ScreenshotConfig("addrecipient.json", image_name="000.png")], "pagerduty": [ScreenshotConfig("trigger_v2.json")], "papertrail": [ScreenshotConfig("short_post.json", payload_as_query_param=True)], diff --git a/zerver/webhooks/opencollective/__init__.py b/zerver/webhooks/opencollective/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/zerver/webhooks/opencollective/doc.md b/zerver/webhooks/opencollective/doc.md new file mode 100644 index 0000000000..4e7bb05217 --- /dev/null +++ b/zerver/webhooks/opencollective/doc.md @@ -0,0 +1,17 @@ +This integration currently supports getting notifications to a stream of your Zulip instance, +when a new member signs-up on an **Open Collective** page. + +1. {!create-stream.md!} + +1. {!create-bot-construct-url-indented.md!} + +1. Go to [Open Collective Website](https://opencollective.com/), find +your desired collective page, then go to *Settings* -> *Webhooks*, paste the +bot URL and choose *Activity* -> *New Member*. + +{!congrats.md!} + +![](/static/images/integrations/opencollective/001.png) + +In the future, this integration can be developed in order to +support other types of *Activity* such as *New Transaction*, *Subscription Canceled* etc. diff --git a/zerver/webhooks/opencollective/fixtures/one_time_donation.json b/zerver/webhooks/opencollective/fixtures/one_time_donation.json new file mode 100644 index 0000000000..5ae5fa2fa2 --- /dev/null +++ b/zerver/webhooks/opencollective/fixtures/one_time_donation.json @@ -0,0 +1,36 @@ +{ + "createdAt": "2021-06-21T17:25:24.344Z", + "id": 1242511, + "type": "collective.member.created", + "CollectiveId": 281290, + "data": { + "member": { + "role": "BACKER", + "description": null, + "since": "2021-06-21T17:25:24.332Z", + "memberCollective": { + "id": 280138, + "type": "USER", + "slug": "leyteris-kyriazanos", + "name": "Λευτέρης Κυριαζάνος", + "company": null, + "website": null, + "twitterHandle": null, + "githubHandle": null, + "description": null, + "previewImage": null + } + }, + "order": { + "id": 183773, + "totalAmount": 100, + "currency": "EUR", + "description": "Financial contribution to Test-Webhooks", + "interval": null, + "createdAt": "2021-06-21T17:25:17.968Z", + "quantity": 1, + "formattedAmount": "€1.00", + "formattedAmountWithInterval": "€1.00" + } + } +} diff --git a/zerver/webhooks/opencollective/fixtures/one_time_incognito_donation.json b/zerver/webhooks/opencollective/fixtures/one_time_incognito_donation.json new file mode 100644 index 0000000000..db71957aac --- /dev/null +++ b/zerver/webhooks/opencollective/fixtures/one_time_incognito_donation.json @@ -0,0 +1,30 @@ +{ + "createdAt": "2021-04-30T19:20:04.346Z", + "id": 1121199, + "type": "collective.member.created", + "CollectiveId": 30312, + "data": { + "member": { + "role": "BACKER", + "description": null, + "since": "2021-04-30T19:20:04.314Z", + "memberCollective": { + "type": "USER", + "name": "Incognito", + "previewImage": null + } + }, + "order": { + "id": 168629, + "totalAmount": 100, + "currency": "USD", + "description": "Monthly financial contribution to Zulip", + "interval": "month", + "createdAt": "2021-04-30T19:19:58.647Z", + "quantity": 1, + "formattedAmount": "$1.00", + "formattedAmountWithInterval": "$1.00 / month" + } + } + } + diff --git a/zerver/webhooks/opencollective/tests.py b/zerver/webhooks/opencollective/tests.py new file mode 100644 index 0000000000..5bd9511741 --- /dev/null +++ b/zerver/webhooks/opencollective/tests.py @@ -0,0 +1,31 @@ +from zerver.lib.test_classes import WebhookTestCase + + +class OpenCollectiveHookTests(WebhookTestCase): + STREAM_NAME = "test" + URL_TEMPLATE = "/api/v1/external/opencollective?&api_key={api_key}&stream={stream}" + WEBHOOK_DIR_NAME = "opencollective" + + # Note: Include a test function per each distinct message condition your integration supports + def test_one_time_donation(self) -> None: # test one time donation + expected_topic = "New Member" + expected_message = "@_**Λευτέρης Κυριαζάνος** donated **€1.00**! :tada:" + + self.check_webhook( + "one_time_donation", + expected_topic, + expected_message, + content_type="application/x-www-form-urlencoded", + ) + + def test_one_time_incognito_donation(self) -> None: # test one time incognito donation + expected_topic = "New Member" + expected_message = "An **Incognito** member donated **$1.00**! :tada:" + + # use fixture named helloworld_hello + self.check_webhook( + "one_time_incognito_donation", + expected_topic, + expected_message, + content_type="application/x-www-form-urlencoded", + ) diff --git a/zerver/webhooks/opencollective/view.py b/zerver/webhooks/opencollective/view.py new file mode 100644 index 0000000000..08619806a3 --- /dev/null +++ b/zerver/webhooks/opencollective/view.py @@ -0,0 +1,47 @@ +from typing import Any, Dict + +from django.http import HttpRequest, HttpResponse + +from zerver.decorator import webhook_view +from zerver.lib.request import REQ, has_request_variables +from zerver.lib.response import json_success +from zerver.lib.webhooks.common import check_send_webhook_message +from zerver.models import UserProfile + +MEMBER_NAME_TEMPLATE = "{name}" +AMOUNT_TEMPLATE = "{amount}" + + +@webhook_view("OpenCollective") +@has_request_variables +def api_opencollective_webhook( + request: HttpRequest, + user_profile: UserProfile, + payload: Dict[str, Any] = REQ(argument_type="body"), +) -> HttpResponse: + + name = get_name(payload) + amount = get_amount(payload) + + # construct the body of the message + body = "" + + if name == "Incognito": # Incognito donation + body = f"An **Incognito** member donated **{amount}**! :tada:" + else: # non - Incognito donation + body = f"@_**{name}** donated **{amount}**! :tada:" + + topic = "New Member" + + # send the message + check_send_webhook_message(request, user_profile, topic, body) + + return json_success() + + +def get_name(payload: Dict[str, Any]) -> str: + return MEMBER_NAME_TEMPLATE.format(name=payload["data"]["member"]["memberCollective"]["name"]) + + +def get_amount(payload: Dict[str, Any]) -> str: + return AMOUNT_TEMPLATE.format(amount=payload["data"]["order"]["formattedAmount"])