Repository: zulip/zulip

Issue:
Integration of open source project management tool Wekan
We are using Wekan as open source alternative for trello at work and I'm looking for a way to integrate notifications into zulip - just like the trello integration. 

- Has anyone experiences in doing so and can help?

If this is not the right place for this question please tell me where to ask.


Discussion:
Hello @zulip/server-integrations members, this issue was labeled with the "area: integrations" label, so you may want to check it out!

<!-- areaLabelAddition -->
@kratsching I'm not aware of an existing integration.  It looks like Wekan does support webhooks: 
https://github.com/wekan/wekan/wiki/Webhook-data

So I expect it should be possible to write a Zulip integration following this guide: https://zulipchat.com/api/incoming-webhooks-overview

If you can do the fixture collection part, possibly someone from the community would be up for doing the formatting pieces.
@timabbott thank you for your answer - I will check that out in october. If I find a solution I will come back here.


Resolving patch:
diff --git a/zerver/lib/integrations.py b/zerver/lib/integrations.py
--- a/zerver/lib/integrations.py
+++ b/zerver/lib/integrations.py
@@ -463,6 +463,7 @@ def __init__(self, name: str, *args: Any, **kwargs: Any) -> None:
         function="zerver.webhooks.yo.view.api_yo_app_webhook",
         display_name="Yo",
     ),
+    WebhookIntegration("wekan", ["productivity"], display_name="Wekan"),
     WebhookIntegration("wordpress", ["marketing"], display_name="WordPress"),
     WebhookIntegration("zapier", ["meta-integration"]),
     WebhookIntegration("zendesk", ["customer-support"]),
@@ -805,6 +806,7 @@ def __init__(self, name: str, *args: Any, **kwargs: Any) -> None:
     "trello": [ScreenshotConfig("adding_comment_to_card.json")],
     "updown": [ScreenshotConfig("check_multiple_events.json")],
     "uptimerobot": [ScreenshotConfig("uptimerobot_monitor_up.json")],
+    "wekan": [ScreenshotConfig("add_comment.json")],
     "wordpress": [ScreenshotConfig("publish_post.txt", "wordpress_post_created.png")],
     "yo": [
         ScreenshotConfig(
diff --git a/zerver/webhooks/wekan/__init__.py b/zerver/webhooks/wekan/__init__.py
new file mode 100644
diff --git a/zerver/webhooks/wekan/view.py b/zerver/webhooks/wekan/view.py
new file mode 100644
--- /dev/null
+++ b/zerver/webhooks/wekan/view.py
@@ -0,0 +1,50 @@
+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
+
+LINK_TEMPLATE = "[See in Wekan]({url})"
+MESSAGE_TEMPLATE = "{body}\n\n{footer}"
+
+
+def get_url(text: str) -> str:
+    return text.split("\n")[-1]
+
+
+def get_hyperlinked_url(text: str) -> str:
+    url = get_url(text)
+    return LINK_TEMPLATE.format(url=url)
+
+
+def clean_payload_text(text: str) -> str:
+    url = get_url(text)
+    return text.replace(url, "").replace("\n", "")
+
+
+def get_message_body(payload: Dict[str, Any], action: str) -> str:
+    footer = get_hyperlinked_url(payload["text"])
+    body = process_message_data(payload, action)
+    return MESSAGE_TEMPLATE.format(body=body, footer=footer)
+
+
+def process_message_data(payload: Dict[str, Any], action: str) -> str:
+    payload["text"] = clean_payload_text(payload["text"])
+    return "{text}.".format(**payload)
+
+
+@webhook_view("Wekan")
+@has_request_variables
+def api_wekan_webhook(
+    request: HttpRequest,
+    user_profile: UserProfile,
+    payload: Dict[str, Any] = REQ(argument_type="body"),
+) -> HttpResponse:
+    topic = "Wekan Notification"
+    body = get_message_body(payload, payload["description"])
+    check_send_webhook_message(request, user_profile, topic, body)
+    return json_success(request)


Test patch:
diff --git a/zerver/webhooks/wekan/tests.py b/zerver/webhooks/wekan/tests.py
new file mode 100644
--- /dev/null
+++ b/zerver/webhooks/wekan/tests.py
@@ -0,0 +1,217 @@
+from zerver.lib.test_classes import WebhookTestCase
+
+
+class WekanHookTests(WebhookTestCase):
+    STREAM_NAME = "wekan"
+    URL_TEMPLATE = "/api/v1/external/wekan?stream={stream}&api_key={api_key}"
+    FIXTURE_DIR_NAME = "wekan"
+
+    def test_add_attachment_message(self) -> None:
+        expected_message = 'JohnFish added attachment "hGfm5ksud8k" to card "Markdown and emoji\'s" at list "Design" at swimlane "Default" at board "Bucket List".\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list/pMtu7kPZvMuhhC4hL)'
+        self.check_webhook(
+            "add_attachment",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_add_checklist_item_message(self) -> None:
+        expected_message = 'JohnFish added checklist item "merge commit 9dfe" to checklist "To do" at card "Markdown and emoji\'s" at list "Design" at swimlane "Default" at board "Bucket List".\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list/pMtu7kPZvMuhhC4hL)'
+        self.check_webhook(
+            "add_checklist_item",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_add_checklist_message(self) -> None:
+        expected_message = 'JohnFish added checklist "To do" to card "Markdown and emoji\'s" at list "Design" at swimlane "Default" at board "bucked-list".\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list/pMtu7kPZvMuhhC4hL)'
+        self.check_webhook(
+            "add_checklist",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_add_label_message(self) -> None:
+        expected_message = 'JohnFish Added label Language to card "Markdown & emojis" at list "Design" at swimlane "Default" at board "Bucket List".\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list/TMmjFnQGuZPsbjXzS)'
+        self.check_webhook(
+            "add_label",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_archived_swimlane_message(self) -> None:
+        expected_message = 'JohnFish Swimlane "Default" at board "Bucket List" moved to Archive.\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list)'
+        self.check_webhook(
+            "archived_swimlane",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_archived_card_message(self) -> None:
+        expected_message = 'JohnFish Card "Markdown and emoji\'s" at list "Design" at swimlane "Default" at board "Bucket List" moved to Archive.\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list/pMtu7kPZvMuhhC4hL)'
+        self.check_webhook(
+            "archived_card",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_archived_list_message(self) -> None:
+        expected_message = 'JohnFish List "Design" at swimlane "Default" at board "Bucket List" moved to Archive.\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list)'
+        self.check_webhook(
+            "archived_list",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_checked_item_message(self) -> None:
+        expected_message = 'JohnFish checked To do of checklist "To do" at card "Markdown and emoji\'s" at list "Design" at swimlane "Default" at board "bucket-list".\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list/pMtu7kPZvMuhhC4hL)'
+        self.check_webhook(
+            "checked_item",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_add_comment_message(self) -> None:
+        expected_message = 'JohnFish commented on card "Markdown and emoji\'s": "This feature is important" at list "Design" at swimlane "Default" at board "Bucket List".\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list/pMtu7kPZvMuhhC4hL)'
+        self.check_webhook(
+            "add_comment",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_create_card_message(self) -> None:
+        expected_message = 'JohnFish created card "Markdown and emoji\'s" to list "Development & Implementation" at swimlane "Default" at board "Bucket List".\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list/pMtu7kPZvMuhhC4hL)'
+        self.check_webhook(
+            "create_card",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_create_custom_field_message(self) -> None:
+        expected_message = 'JohnFish created custom field Language at board "Bucket List".\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list)'
+        self.check_webhook(
+            "create_custom_field",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_create_list_message(self) -> None:
+        expected_message = 'JohnFish added list "Testing & Maintenance" to board "Bucket List".\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list)'
+        self.check_webhook(
+            "create_list",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_create_swimlane_message(self) -> None:
+        expected_message = 'JohnFish created swimlane "Jasper" to board "Bucket List".\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list)'
+        self.check_webhook(
+            "create_swimlane",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_delete_attachment_message(self) -> None:
+        expected_message = 'JohnFish deleted attachment "hGfm5ksud8k.jpg" at card "Markdown and emoji\'s" at list "Design" at swimlane "Default" at board "Bucket List".\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list/pMtu7kPZvMuhhC4hL)'
+        self.check_webhook(
+            "delete_attachment",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_join_member_message(self) -> None:
+        expected_message = 'JohnFish added member kokoboss to card "Markdown & emojis" at list "Design" at swimlane "Default" at board "Bucket List".\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list/TMmjFnQGuZPsbjXzS)'
+        self.check_webhook(
+            "join_member",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_move_card_message(self) -> None:
+        expected_message = 'JohnFish moved card "Markdown and emoji\'s" at board "Bucket List" from list "Development & Implementation" at swimlane "Default" to list "Design" at swimlane "Default".\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list/pMtu7kPZvMuhhC4hL)'
+        self.check_webhook(
+            "move_card",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_remove_list_message(self) -> None:
+        expected_message = "JohnFish act-removeList.\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list)"
+        self.check_webhook(
+            "remove_list",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_remove_swimlane_message(self) -> None:
+        expected_message = "JohnFish act-removeSwimlane.\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list)"
+        self.check_webhook(
+            "remove_swimlane",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_removed_checklist_item_message(self) -> None:
+        expected_message = "JohnFish act-removedChecklistItem.\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list/pMtu7kPZvMuhhC4hL)"
+        self.check_webhook(
+            "removed_checklist_item",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_removed_checklist_message(self) -> None:
+        expected_message = 'JohnFish removed checklist "To do" from card "Markdown and emoji\'s" at list "Design" at swimlane "Default" at board "Bucket List".\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list/pMtu7kPZvMuhhC4hL)'
+        self.check_webhook(
+            "removed_checklist",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_restored_card_message(self) -> None:
+        expected_message = 'JohnFish restored card "Markdown and emoji\'s" to list "Design" at swimlane "Default" at board "Bucket List".\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list/pMtu7kPZvMuhhC4hL)'
+        self.check_webhook(
+            "restored_card",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_set_custom_field_message(self) -> None:
+        expected_message = "JohnFish act-setCustomField.\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list/pMtu7kPZvMuhhC4hL)"
+        self.check_webhook(
+            "set_custom_field",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def test_uncomplete_checklist_message(self) -> None:
+        expected_message = 'JohnFish uncompleted checklist To do at card "Markdown and emoji\'s" at list "Design" at swimlane "Default" at board "Bucket List".\n\n[See in Wekan](http://127.0.0.1/b/Jinj4Xj7qnHLRmrTY/bucket-list/pMtu7kPZvMuhhC4hL)'
+        self.check_webhook(
+            "uncomplete_checklist",
+            "Wekan Notification",
+            expected_message,
+            content_type="application/x-www-form-urlencoded",
+        )
+
+    def get_body(self, fixture_name: str) -> str:
+        return self.webhook_fixture_data("wekan", fixture_name, file_type="json")