From 2d6e6369f3e9a180d93f7483d8fa10a300c0ba34 Mon Sep 17 00:00:00 2001 From: sbansal1999 Date: Thu, 13 Apr 2023 00:04:01 +0530 Subject: [PATCH] integrations: Add Linear webhook integration. Fixes part of #23118. --- .../integrations/bot_avatars/linear.png | Bin 0 -> 2116 bytes static/images/integrations/linear/001.png | Bin 0 -> 45196 bytes static/images/integrations/logos/linear.svg | Bin 0 -> 448 bytes zerver/lib/integrations.py | 2 + zerver/webhooks/linear/__init__.py | 0 zerver/webhooks/linear/doc.md | 18 ++ .../linear/fixtures/comment_create.json | 25 +++ .../linear/fixtures/comment_remove.json | 25 +++ .../linear/fixtures/comment_update.json | 31 ++++ .../linear/fixtures/issue_create_complex.json | 73 ++++++++ .../linear/fixtures/issue_create_simple.json | 41 +++++ ...sue_create_simple_without_description.json | 39 ++++ .../linear/fixtures/issue_remove.json | 81 +++++++++ .../fixtures/issue_sub_issue_create.json | 43 +++++ .../fixtures/issue_sub_issue_remove.json | 44 +++++ .../fixtures/issue_sub_issue_update.json | 47 +++++ .../linear/fixtures/issue_update.json | 84 +++++++++ .../linear/fixtures/project_create.json | 35 ++++ zerver/webhooks/linear/tests.py | 85 +++++++++ zerver/webhooks/linear/view.py | 168 ++++++++++++++++++ 20 files changed, 841 insertions(+) create mode 100644 static/images/integrations/bot_avatars/linear.png create mode 100644 static/images/integrations/linear/001.png create mode 100644 static/images/integrations/logos/linear.svg create mode 100644 zerver/webhooks/linear/__init__.py create mode 100644 zerver/webhooks/linear/doc.md create mode 100644 zerver/webhooks/linear/fixtures/comment_create.json create mode 100644 zerver/webhooks/linear/fixtures/comment_remove.json create mode 100644 zerver/webhooks/linear/fixtures/comment_update.json create mode 100644 zerver/webhooks/linear/fixtures/issue_create_complex.json create mode 100644 zerver/webhooks/linear/fixtures/issue_create_simple.json create mode 100644 zerver/webhooks/linear/fixtures/issue_create_simple_without_description.json create mode 100644 zerver/webhooks/linear/fixtures/issue_remove.json create mode 100644 zerver/webhooks/linear/fixtures/issue_sub_issue_create.json create mode 100644 zerver/webhooks/linear/fixtures/issue_sub_issue_remove.json create mode 100644 zerver/webhooks/linear/fixtures/issue_sub_issue_update.json create mode 100644 zerver/webhooks/linear/fixtures/issue_update.json create mode 100644 zerver/webhooks/linear/fixtures/project_create.json create mode 100644 zerver/webhooks/linear/tests.py create mode 100644 zerver/webhooks/linear/view.py diff --git a/static/images/integrations/bot_avatars/linear.png b/static/images/integrations/bot_avatars/linear.png new file mode 100644 index 0000000000000000000000000000000000000000..d79b9c06acc6315e1d64d226cc8b12bb7d4a4eb6 GIT binary patch literal 2116 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 literal 0 HcmV?d00001 diff --git a/static/images/integrations/logos/linear.svg b/static/images/integrations/logos/linear.svg new file mode 100644 index 0000000000000000000000000000000000000000..02a5df7184baaf8062c6ed2e749eea739fb0a293 GIT binary patch literal 448 zcmZXR-HL-S5QSf5$X<7x$$yF#y6oQR3wS5AtAcCGR%#!A6SZt%A)HAvUrtU)e!I8u z_#VbvA-ZY0?z3#3XPgyYe%eeDQ5FdSbAOt;LUC3OE&_Qb@`GOAJ~y9)ne|YC0T`qwx+5YRTLFYd7t$ z$guNB None: + expected_topic = "21e12515-fe5e-4923-88a1-e9ace5056473" + expected_message = "Issue [#42 Drop-down overflow in the select menu.](https://linear.app/webhooks/issue/WEB-42/drop-down-overflow-in-the-select-menu) was created in team Webhooks.\nPriority: High, Status: Todo." + self.check_webhook( + "issue_create_simple_without_description", + expected_topic, + expected_message, + ) + + def test_issue_create_simple_without_description_with_custom_topic_in_url(self) -> None: + self.url = self.build_webhook_url(topic="notifications") + expected_topic = "notifications" + expected_message = "Issue [#42 Drop-down overflow in the select menu.](https://linear.app/webhooks/issue/WEB-42/drop-down-overflow-in-the-select-menu) was created in team Webhooks.\nPriority: High, Status: Todo." + self.check_webhook( + "issue_create_simple_without_description", + expected_topic, + expected_message, + ) + + def test_issue_create_simple(self) -> None: + expected_topic = "a4344dc7-7d8d-4b28-a93c-553ac9aba41a" + expected_message = 'Issue [#43 Very small font in tooltip](https://linear.app/webhooks/issue/WEB-43/very-small-font-in-tooltip) was created in team Webhooks:\n~~~ quote\nThe tooltips at the "Select Drawing" and "Edit Drawing" buttons have a very small font and therefore are not very legible. Apart from this, the wording of the text has to be changed to fit better with the overall design pattern.\n~~~\n\nStatus: Todo.' + self.check_webhook("issue_create_simple", expected_topic, expected_message) + + def test_issue_create_complex(self) -> None: + expected_topic = "3443a709-f2b5-46f2-a136-a0445fd432be" + expected_message = "Issue [#44 This is regarding the outage that we faced during 11/12/22 from 2000 to 2200.](https://linear.app/webhooks/issue/WEB-44/this-is-regarding-the-outage-that-we-faced-during-111222-from-2000-to) was created in team Webhooks:\n~~~ quote\nThe outage that occurred on the above-mentioned date is a cause for concern as it could have significant implications for the organization and its users. A prolonged outage can result in lost revenue, productivity, and customer confidence. Therefore, it is essential to conduct a detailed assessment and analysis to identify the root cause of the outage and take appropriate measures to prevent its recurrence.\n\nThe analysis process may involve the use of specialized tools and techniques to help pinpoint the exact cause of the outage. Once the root cause has been identified, the organization can take steps to implement effective solutions that can mitigate the risk of a similar outage happening in the future. The assessment and analysis process will help the organization to develop a more robust and reliable IT infrastructure that can provide uninterrupted services to its users.\n~~~\n\nPriority: Urgent, Assignee: Satyam Bansal, Status: In Progress." + self.check_webhook("issue_create_complex", expected_topic, expected_message) + + def test_comment_create(self) -> None: + expected_topic = "f9a37fcf-eb52-44be-a52c-0477f70e9952" + expected_message = "Satyam Bansal [commented](https://linear.app/webhooks/issue/WEB-46#comment-c7cafc52) on issue **Thorough Impact Analysis and Cost Realization**:\n~~~ quote\nPerforming a thorough impact analysis and cost realization is a crucial step in responding to any system outage or incident. By examining the extent of the outage, the affected systems or services, the number of users impacted, and any error messages or logs generated during the incident, we can gain a comprehensive understanding of the scope of the incident.\n\nThis information can then be used to prioritize the resolution efforts and minimize the impact on our organization's operations. Additionally, cost realization allows us to evaluate the financial impact of the outage on our organization and make informed decisions regarding resource allocation for future incidents.\n\nOverall, conducting a thorough impact analysis and cost realization can help us effectively manage incidents and prevent similar issues from occurring in the future.\n~~~" + self.check_webhook("comment_create", expected_topic, expected_message) + + def test_comment_remove(self) -> None: + expected_topic = "f9a37fcf-eb52-44be-a52c-0477f70e9952" + expected_message = "Satyam Bansal has removed a comment." + self.check_webhook("comment_remove", expected_topic, expected_message) + + def test_comment_update(self) -> None: + expected_topic = "f9a37fcf-eb52-44be-a52c-0477f70e9952" + expected_message = "Satyam Bansal [updated comment](https://linear.app/webhooks/issue/WEB-46#comment-c7cafc52) on issue **Thorough Impact Analysis and Cost Realization**:\n~~~ quote\nInvalid response to any system outage or incident, it is essential to perform a comprehensive impact analysis and cost evaluation. By examining factors such as the extent of the outage, the affected systems or services, the number of users affected, and any error messages or logs generated during the incident, we can gain a detailed understanding of the incident's scope.\n\nThis information is then critical in prioritizing resolution efforts and reducing the impact on our organization's operations. Additionally, conducting cost realization allows us to assess the financial implications of the outage and make informed decisions about allocating resources for future incidents.\n\nOverall, performing a thorough impact analysis and cost realization is an effective way to manage incidents and prevent similar issues from recurring.\n~~~" + self.check_webhook("comment_update", expected_topic, expected_message) + + def test_issue_remove(self) -> None: + expected_topic = "3443a709-f2b5-46f2-a136-a0445fd432be" + expected_message = "Issue **#44 This is regarding the outage that we faced on 11/12/22 from 2000 to 2200 and also on 25/12/22 from 0000 to 0230** has been removed from team Webhooks." + self.check_webhook("issue_remove", expected_topic, expected_message) + + def test_issue_sub_issue_create(self) -> None: + expected_topic = "3443a709-f2b5-46f2-a136-a0445fd432be" + expected_message = "Sub-Issue [#46 Impact Analysis](https://linear.app/webhooks/issue/WEB-46/impact-analysis) was created in team Webhooks:\n~~~ quote\nExamining the extent of the outage, the affected systems or services, the number of users impacted, and any error messages or logs generated during the incident.\n~~~\n\nStatus: Todo." + self.check_webhook("issue_sub_issue_create", expected_topic, expected_message) + + def test_issue_sub_issue_remove(self) -> None: + expected_topic = "3443a709-f2b5-46f2-a136-a0445fd432be" + expected_message = "Sub-Issue **#46 Thorough Impact Analysis and Cost Realization** has been removed from team Webhooks." + self.check_webhook("issue_sub_issue_remove", expected_topic, expected_message) + + def test_issue_sub_issue_update(self) -> None: + expected_topic = "3443a709-f2b5-46f2-a136-a0445fd432be" + expected_message = "Sub-Issue [#46 Thorough Impact Analysis and Cost Realization](https://linear.app/webhooks/issue/WEB-46/thorough-impact-analysis-and-cost-realization) was updated in team Webhooks:\n~~~ quote\nExamining the extent of the outage, the affected systems or services, the number of users impacted, and any error messages or logs generated during the incident.\n~~~\n\nStatus: Todo." + self.check_webhook("issue_sub_issue_update", expected_topic, expected_message) + + def test_issue_update(self) -> None: + expected_topic = "3443a709-f2b5-46f2-a136-a0445fd432be" + expected_message = "Issue [#44 This is regarding the outage that we faced on 11/12/22 from 2000 to 2200 and also on 25/12/22 from 0000 to 0230](https://linear.app/webhooks/issue/WEB-44/this-is-regarding-the-outage-that-we-faced-on-111222-from-2000-to-2200) was updated in team Webhooks:\n~~~ quote\nThe outage that occurred on the above-mentioned date is a cause for concern as it could have significant implications for the organization and its users. A prolonged outage can result in lost revenue, productivity, and customer confidence. Therefore, it is essential to conduct a detailed assessment and analysis to identify the root cause of the outage and take appropriate measures to prevent its recurrence.\n\nThe analysis process may involve the use of specialized tools and techniques to help pinpoint the exact cause of the outage. Once the root cause has been identified, the organization can take steps to implement effective solutions that can mitigate the risk of a similar outage happening in the future. The assessment and analysis process will help the organization to develop a more robust and reliable IT infrastructure that can provide uninterrupted services to its users.\n~~~\n\nPriority: Urgent, Assignee: Satyam Bansal, Status: In Progress." + self.check_webhook("issue_update", expected_topic, expected_message) + + def test_project_create(self) -> None: + payload = self.get_body("project_create") + result = self.client_post( + self.url, + payload, + content_type="application/json", + ) + self.assert_json_success(result) diff --git a/zerver/webhooks/linear/view.py b/zerver/webhooks/linear/view.py new file mode 100644 index 0000000000..363cd4fc6f --- /dev/null +++ b/zerver/webhooks/linear/view.py @@ -0,0 +1,168 @@ +from typing import Callable, Dict, Optional + +from django.http import HttpRequest, HttpResponse + +from zerver.decorator import webhook_view +from zerver.lib.exceptions import UnsupportedWebhookEventTypeError +from zerver.lib.request import REQ, has_request_variables +from zerver.lib.response import json_success +from zerver.lib.validator import WildValue, check_int, check_string, to_wild_value +from zerver.lib.webhooks.common import check_send_webhook_message +from zerver.models import UserProfile + +CONTENT_MESSAGE_TEMPLATE = "\n~~~ quote\n{message}\n~~~\n" + +ISSUE_CREATE_OR_UPDATE_TEMPLATE = ( + "{type} [#{number} {title}]({url}) was {action} in team {team_name}" +) + +ISSUE_REMOVE_TEMPLATE = "{type} **#{number} {title}** has been removed from team {team_name}." +COMMENT_CREATE_OR_UPDATE_TEMPLATE = "{user} [{action}]({url}) on issue **{issue_title}**:" +COMMENT_REMOVE_TEMPLATE = "{user} has removed a comment." + + +def get_issue_created_or_updated_message(event: str, payload: WildValue, action: str) -> str: + message = ISSUE_CREATE_OR_UPDATE_TEMPLATE.format( + type="Issue" if event == "issue" else "Sub-Issue", + number=payload["data"]["number"].tame(check_int), + title=payload["data"]["title"].tame(check_string), + url=payload["url"].tame(check_string), + action=action, + team_name=payload["data"]["team"]["name"].tame(check_string), + ) + + has_description = "description" in payload["data"] + if has_description: + message += f":{CONTENT_MESSAGE_TEMPLATE.format(message=payload['data']['description'].tame(check_string))}" + else: + message += "." + + to_add = [] + + priority_label = payload["data"]["priorityLabel"].tame(check_string) + if priority_label != "No priority": + to_add.append(f"Priority: {priority_label}") + + has_assignee = "assignee" in payload["data"] + if has_assignee: + to_add.append(f"Assignee: {payload['data']['assignee']['name'].tame(check_string)}") + + status = payload["data"]["state"]["name"].tame(check_string) + to_add.append(f"Status: {status}") + + message += f"\n{', '.join(to_add)}." + + return message + + +def get_issue_remove_body(payload: WildValue, event: str) -> str: + return ISSUE_REMOVE_TEMPLATE.format( + type="Issue" if event == "issue" else "Sub-Issue", + number=payload["data"]["number"].tame(check_int), + title=payload["data"]["title"].tame(check_string), + team_name=payload["data"]["team"]["name"].tame(check_string), + ) + + +def get_comment_create_or_update_body(payload: WildValue, event: str, action: str) -> str: + message = COMMENT_CREATE_OR_UPDATE_TEMPLATE.format( + user=payload["data"]["user"]["name"].tame(check_string), + action=action, + url=payload["url"].tame(check_string), + issue_title=payload["data"]["issue"]["title"].tame(check_string), + ) + message += CONTENT_MESSAGE_TEMPLATE.format(message=payload["data"]["body"].tame(check_string)) + return message + + +def get_comment_remove_body(payload: WildValue, event: str) -> str: + return COMMENT_REMOVE_TEMPLATE.format(user=payload["data"]["user"]["name"].tame(check_string)) + + +def get_issue_or_sub_issue_message(payload: WildValue, event: str) -> str: + action = payload["action"].tame(check_string) + if action == "remove": + return get_issue_remove_body(payload, event) + + return get_issue_created_or_updated_message( + event, payload, action="created" if action == "create" else "updated" + ) + + +def get_comment_message(payload: WildValue, event: str) -> str: + action = payload["action"].tame(check_string) + if action == "remove": + return get_comment_remove_body(payload, event) + + return get_comment_create_or_update_body( + payload, event, "commented" if action == "create" else "updated comment" + ) + + +EVENT_FUNCTION_MAPPER: Dict[str, Callable[[WildValue, str], str]] = { + "issue": get_issue_or_sub_issue_message, + "sub_issue": get_issue_or_sub_issue_message, + "comment": get_comment_message, +} + +IGNORED_EVENTS = ["IssueLabel", "Project", "ProjectUpdate", "Cycle", "Reaction"] + +ALL_EVENT_TYPES = list(EVENT_FUNCTION_MAPPER.keys()) + + +@webhook_view("Linear", notify_bot_owner_on_invalid_json=True, all_event_types=ALL_EVENT_TYPES) +@has_request_variables +def api_linear_webhook( + request: HttpRequest, + user_profile: UserProfile, + payload: WildValue = REQ(argument_type="body", converter=to_wild_value), + user_specified_topic: Optional[str] = REQ("topic", default=None), +) -> HttpResponse: + event_type = get_event_type(payload) + if event_type is None: + return json_success(request) + + topic = get_topic(user_specified_topic, event_type, payload) + + body_function = EVENT_FUNCTION_MAPPER[event_type] + body = body_function(payload, event_type) + + check_send_webhook_message(request, user_profile, topic, body) + + return json_success(request) + + +def get_topic(user_specified_topic: Optional[str], event: str, payload: WildValue) -> str: + if user_specified_topic is not None: + return user_specified_topic + elif event == "comment": + issue_id = payload["data"]["issueId"].tame(check_string) + return issue_id + elif event == "sub_issue": + parent = payload["data"]["parentId"].tame(check_string) + return parent + elif event == "issue": + issue_id = payload["data"]["id"].tame(check_string) + return issue_id + + raise UnsupportedWebhookEventTypeError("unknown event type") + + +def get_event_type(payload: WildValue) -> Optional[str]: + event_type = payload["type"].tame(check_string) + + if event_type == "Issue": + has_parent_id = "parentId" in payload["data"] + return "issue" if not has_parent_id else "sub_issue" + elif event_type == "Comment": + return "comment" + elif event_type in IGNORED_EVENTS: + return None + + # This happens when a new event type is added to Linear and we + # haven't updated the integration yet. + complete_event = "{}:{}".format( + event_type, payload.get("action", "???").tame(check_string) + ) # nocoverage + + raise UnsupportedWebhookEventTypeError(complete_event)