mirror of
https://github.com/zulip/zulip.git
synced 2025-11-11 17:36:27 +00:00
integrations: Add support for GitLab design comments.
Fixes #26199. Co-authored-by: Barış <barisunsalhn@users.noreply.github.com> Co-authored-by: Satyam Bansal <sbansal1999@gmail.com>
This commit is contained in:
94
zerver/webhooks/gitlab/fixtures/note_hook__design_note.json
Normal file
94
zerver/webhooks/gitlab/fixtures/note_hook__design_note.json
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
{
|
||||||
|
"object_kind": "note",
|
||||||
|
"event_type": "note",
|
||||||
|
"user": {
|
||||||
|
"id": 14613894,
|
||||||
|
"name": "Satyam Bansal",
|
||||||
|
"username": "sbansal1999",
|
||||||
|
"avatar_url": "https://gitlab.com/uploads/-/system/user/avatar/14613894/avatar.png",
|
||||||
|
"email": "[REDACTED]"
|
||||||
|
},
|
||||||
|
"project_id": 46186758,
|
||||||
|
"project": {
|
||||||
|
"id": 46186758,
|
||||||
|
"name": "testing-zulip-gitlab-integration",
|
||||||
|
"description": null,
|
||||||
|
"web_url": "https://gitlab.com/sbansal1999/testing-zulip-gitlab-integration",
|
||||||
|
"avatar_url": null,
|
||||||
|
"git_ssh_url": "git@gitlab.com:sbansal1999/testing-zulip-gitlab-integration.git",
|
||||||
|
"git_http_url": "https://gitlab.com/sbansal1999/testing-zulip-gitlab-integration.git",
|
||||||
|
"namespace": "Satyam Bansal",
|
||||||
|
"visibility_level": 0,
|
||||||
|
"path_with_namespace": "sbansal1999/testing-zulip-gitlab-integration",
|
||||||
|
"default_branch": "main",
|
||||||
|
"ci_config_path": "",
|
||||||
|
"homepage": "https://gitlab.com/sbansal1999/testing-zulip-gitlab-integration",
|
||||||
|
"url": "git@gitlab.com:sbansal1999/testing-zulip-gitlab-integration.git",
|
||||||
|
"ssh_url": "git@gitlab.com:sbansal1999/testing-zulip-gitlab-integration.git",
|
||||||
|
"http_url": "https://gitlab.com/sbansal1999/testing-zulip-gitlab-integration.git"
|
||||||
|
},
|
||||||
|
"object_attributes": {
|
||||||
|
"attachment": null,
|
||||||
|
"author_id": 14613894,
|
||||||
|
"change_position": {
|
||||||
|
"base_sha": null,
|
||||||
|
"start_sha": null,
|
||||||
|
"head_sha": null,
|
||||||
|
"old_path": null,
|
||||||
|
"new_path": null,
|
||||||
|
"position_type": "text",
|
||||||
|
"old_line": null,
|
||||||
|
"new_line": null,
|
||||||
|
"line_range": null
|
||||||
|
},
|
||||||
|
"commit_id": null,
|
||||||
|
"created_at": "2023-07-05 16:38:53 UTC",
|
||||||
|
"discussion_id": "90c3bd62a51e132229eb2433a42af0707c1b7091",
|
||||||
|
"id": 1458583152,
|
||||||
|
"line_code": null,
|
||||||
|
"note": "hello",
|
||||||
|
"noteable_id": 601586,
|
||||||
|
"noteable_type": "DesignManagement::Design",
|
||||||
|
"original_position": {
|
||||||
|
"base_sha": "0000000000000000000000000000000000000000",
|
||||||
|
"start_sha": "0000000000000000000000000000000000000000",
|
||||||
|
"head_sha": "3e8137cae02e78675cd220ae1dd2ced26feba62f",
|
||||||
|
"old_path": null,
|
||||||
|
"new_path": "designs/issue-1/Screenshot.png",
|
||||||
|
"position_type": "image",
|
||||||
|
"width": 740,
|
||||||
|
"height": 499,
|
||||||
|
"x": 413,
|
||||||
|
"y": 315
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"base_sha": "0000000000000000000000000000000000000000",
|
||||||
|
"start_sha": "0000000000000000000000000000000000000000",
|
||||||
|
"head_sha": "3e8137cae02e78675cd220ae1dd2ced26feba62f",
|
||||||
|
"old_path": null,
|
||||||
|
"new_path": "designs/issue-1/Screenshot.png",
|
||||||
|
"position_type": "image",
|
||||||
|
"width": 740,
|
||||||
|
"height": 499,
|
||||||
|
"x": 413,
|
||||||
|
"y": 315
|
||||||
|
},
|
||||||
|
"project_id": 46186758,
|
||||||
|
"resolved_at": null,
|
||||||
|
"resolved_by_id": null,
|
||||||
|
"resolved_by_push": null,
|
||||||
|
"st_diff": null,
|
||||||
|
"system": false,
|
||||||
|
"type": "DiffNote",
|
||||||
|
"updated_at": "2023-07-05 16:38:53 UTC",
|
||||||
|
"updated_by_id": null,
|
||||||
|
"description": "hello",
|
||||||
|
"url": null
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"name": "testing-zulip-gitlab-integration",
|
||||||
|
"url": "git@gitlab.com:sbansal1999/testing-zulip-gitlab-integration.git",
|
||||||
|
"description": null,
|
||||||
|
"homepage": "https://gitlab.com/sbansal1999/testing-zulip-gitlab-integration"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -257,6 +257,12 @@ class GitlabHookTests(WebhookTestCase):
|
|||||||
|
|
||||||
self.check_webhook("note_hook__issue_note", expected_topic_name, expected_message)
|
self.check_webhook("note_hook__issue_note", expected_topic_name, expected_message)
|
||||||
|
|
||||||
|
def test_note_design_event_message(self) -> None:
|
||||||
|
expected_topic_name = "testing-zulip-gitlab-integration / design Screenshot.png"
|
||||||
|
expected_message = "Satyam Bansal [commented](https://gitlab.com/sbansal1999/testing-zulip-gitlab-integration/-/issues/1/designs/Screenshot.png#note_1458583152) on design [Screenshot.png](https://gitlab.com/sbansal1999/testing-zulip-gitlab-integration/-/issues/1/designs/Screenshot.png):\n\n~~~ quote\nhello\n~~~"
|
||||||
|
|
||||||
|
self.check_webhook("note_hook__design_note", expected_topic_name, expected_message)
|
||||||
|
|
||||||
def test_note_confidential_issue_event_message(self) -> None:
|
def test_note_confidential_issue_event_message(self) -> None:
|
||||||
expected_subject = "testing-zulip-gitlab-integration / issue #1 Add more lines"
|
expected_subject = "testing-zulip-gitlab-integration / issue #1 Add more lines"
|
||||||
expected_message = "Satyam Bansal [commented](https://gitlab.com/sbansal1999/testing-zulip-gitlab-integration/-/issues/1#note_1406130881) on [issue #1](https://gitlab.com/sbansal1999/testing-zulip-gitlab-integration/-/issues/1):\n\n~~~ quote\nSome more comments\n~~~"
|
expected_message = "Satyam Bansal [commented](https://gitlab.com/sbansal1999/testing-zulip-gitlab-integration/-/issues/1#note_1406130881) on [issue #1](https://gitlab.com/sbansal1999/testing-zulip-gitlab-integration/-/issues/1):\n\n~~~ quote\nSome more comments\n~~~"
|
||||||
@@ -270,6 +276,13 @@ class GitlabHookTests(WebhookTestCase):
|
|||||||
|
|
||||||
self.check_webhook("note_hook__issue_note", expected_topic_name, expected_message)
|
self.check_webhook("note_hook__issue_note", expected_topic_name, expected_message)
|
||||||
|
|
||||||
|
def test_note_design_with_custom_topic_in_url(self) -> None:
|
||||||
|
self.url = self.build_webhook_url(topic="notifications")
|
||||||
|
expected_topic_name = "notifications"
|
||||||
|
expected_message = "[[testing-zulip-gitlab-integration](https://gitlab.com/sbansal1999/testing-zulip-gitlab-integration)] Satyam Bansal [commented](https://gitlab.com/sbansal1999/testing-zulip-gitlab-integration/-/issues/1/designs/Screenshot.png#note_1458583152) on design [Screenshot.png](https://gitlab.com/sbansal1999/testing-zulip-gitlab-integration/-/issues/1/designs/Screenshot.png):\n\n~~~ quote\nhello\n~~~"
|
||||||
|
|
||||||
|
self.check_webhook("note_hook__design_note", expected_topic_name, expected_message)
|
||||||
|
|
||||||
def test_note_snippet_old_event_message(self) -> None:
|
def test_note_snippet_old_event_message(self) -> None:
|
||||||
expected_topic_name = "my-awesome-project / snippet #2 test"
|
expected_topic_name = "my-awesome-project / snippet #2 test"
|
||||||
expected_message = "Tomasz Kolek [commented](https://gitlab.com/tomaszkolek0/my-awesome-project/snippets/2#note_14172058) on [snippet #2](https://gitlab.com/tomaszkolek0/my-awesome-project/-/snippets/2):\n\n~~~ quote\nNice snippet\n~~~"
|
expected_message = "Tomasz Kolek [commented](https://gitlab.com/tomaszkolek0/my-awesome-project/snippets/2#note_14172058) on [snippet #2](https://gitlab.com/tomaszkolek0/my-awesome-project/-/snippets/2):\n\n~~~ quote\nNice snippet\n~~~"
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ from zerver.lib.webhooks.git import (
|
|||||||
)
|
)
|
||||||
from zerver.models import UserProfile
|
from zerver.models import UserProfile
|
||||||
|
|
||||||
|
TOPIC_WITH_DESIGN_INFO_TEMPLATE = "{repo} / {type} {design_name}"
|
||||||
|
DESIGN_COMMENT_MESSAGE_TEMPLATE = (
|
||||||
|
"{user_name} {action} on design [{design_name}]({design_url}):\n{content_message}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def fixture_to_headers(fixture_name: str) -> dict[str, str]:
|
def fixture_to_headers(fixture_name: str) -> dict[str, str]:
|
||||||
if fixture_name.startswith("build"):
|
if fixture_name.startswith("build"):
|
||||||
@@ -209,6 +214,23 @@ def replace_assignees_username_with_name(
|
|||||||
return formatted_assignees
|
return formatted_assignees
|
||||||
|
|
||||||
|
|
||||||
|
def parse_design_comment(comment: WildValue, repository_url: str) -> tuple[str, str, str]:
|
||||||
|
note_id = comment["id"].tame(check_int)
|
||||||
|
|
||||||
|
# As there is no issue field in the payloads related to designs,
|
||||||
|
# we need to parse the issue number from the new_path field.
|
||||||
|
design_path = comment["position"]["new_path"].tame(check_string)
|
||||||
|
|
||||||
|
# Sample design_path: "designs/issue-1/Screenshot_20250302_230445.png"
|
||||||
|
_, issue_subpath, design_name = design_path.split("/")
|
||||||
|
|
||||||
|
issue_number = issue_subpath.split("-")[-1]
|
||||||
|
design_url = f"{repository_url}/-/issues/{issue_number}/designs/{design_name}"
|
||||||
|
comment_url = f"{design_url}#note_{note_id}"
|
||||||
|
|
||||||
|
return comment_url, design_url, design_name
|
||||||
|
|
||||||
|
|
||||||
def get_commented_commit_event_body(payload: WildValue, include_title: bool) -> str:
|
def get_commented_commit_event_body(payload: WildValue, include_title: bool) -> str:
|
||||||
comment = payload["object_attributes"]
|
comment = payload["object_attributes"]
|
||||||
action = "[commented]({})".format(comment["url"].tame(check_string))
|
action = "[commented]({})".format(comment["url"].tame(check_string))
|
||||||
@@ -253,6 +275,23 @@ def get_commented_issue_event_body(payload: WildValue, include_title: bool) -> s
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_commented_design_event_body(payload: WildValue, include_title: bool) -> str:
|
||||||
|
comment = payload["object_attributes"]
|
||||||
|
repository_url = payload["repository"]["homepage"].tame(check_string)
|
||||||
|
|
||||||
|
comment_url, design_url, design_name = parse_design_comment(comment, repository_url)
|
||||||
|
action = f"[commented]({comment_url})"
|
||||||
|
content_message = CONTENT_MESSAGE_TEMPLATE.format(message=comment["note"].tame(check_string))
|
||||||
|
|
||||||
|
return DESIGN_COMMENT_MESSAGE_TEMPLATE.format(
|
||||||
|
user_name=get_issue_user_name(payload),
|
||||||
|
action=action,
|
||||||
|
design_name=design_name,
|
||||||
|
design_url=design_url,
|
||||||
|
content_message=content_message,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_commented_snippet_event_body(payload: WildValue, include_title: bool) -> str:
|
def get_commented_snippet_event_body(payload: WildValue, include_title: bool) -> str:
|
||||||
comment = payload["object_attributes"]
|
comment = payload["object_attributes"]
|
||||||
action = "[commented]({}) on".format(comment["url"].tame(check_string))
|
action = "[commented]({}) on".format(comment["url"].tame(check_string))
|
||||||
@@ -405,6 +444,17 @@ def get_object_url(payload: WildValue) -> str:
|
|||||||
return payload["object_attributes"]["url"].tame(check_string)
|
return payload["object_attributes"]["url"].tame(check_string)
|
||||||
|
|
||||||
|
|
||||||
|
def skip_previews(event: str) -> bool:
|
||||||
|
# Add event names to this array for which previews should be skipped.
|
||||||
|
return event in [
|
||||||
|
# Design events link to images, but the images cannot be
|
||||||
|
# accessed without authentication, so trying to preview them
|
||||||
|
# doesn't work.
|
||||||
|
"Note Hook DesignManagement::Design",
|
||||||
|
"Confidential Note Hook DesignManagement::Design",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class EventFunction(Protocol):
|
class EventFunction(Protocol):
|
||||||
def __call__(self, payload: WildValue, include_title: bool) -> str: ...
|
def __call__(self, payload: WildValue, include_title: bool) -> str: ...
|
||||||
|
|
||||||
@@ -425,6 +475,8 @@ EVENT_FUNCTION_MAPPER: dict[str, EventFunction] = {
|
|||||||
"Note Hook MergeRequest": get_commented_merge_request_event_body,
|
"Note Hook MergeRequest": get_commented_merge_request_event_body,
|
||||||
"Note Hook Issue": get_commented_issue_event_body,
|
"Note Hook Issue": get_commented_issue_event_body,
|
||||||
"Confidential Note Hook Issue": get_commented_issue_event_body,
|
"Confidential Note Hook Issue": get_commented_issue_event_body,
|
||||||
|
"Note Hook DesignManagement::Design": get_commented_design_event_body,
|
||||||
|
"Confidential Note Hook DesignManagement::Design": get_commented_design_event_body,
|
||||||
"Note Hook Snippet": get_commented_snippet_event_body,
|
"Note Hook Snippet": get_commented_snippet_event_body,
|
||||||
"Merge Request Hook approved": partial(get_merge_request_event_body, "approved"),
|
"Merge Request Hook approved": partial(get_merge_request_event_body, "approved"),
|
||||||
"Merge Request Hook unapproved": partial(get_merge_request_event_body, "unapproved"),
|
"Merge Request Hook unapproved": partial(get_merge_request_event_body, "unapproved"),
|
||||||
@@ -469,7 +521,9 @@ def api_gitlab_webhook(
|
|||||||
body = f"[{project_url}] {body}"
|
body = f"[{project_url}] {body}"
|
||||||
|
|
||||||
topic_name = get_topic_based_on_event(event, payload, use_merge_request_title)
|
topic_name = get_topic_based_on_event(event, payload, use_merge_request_title)
|
||||||
check_send_webhook_message(request, user_profile, topic_name, body, event)
|
check_send_webhook_message(
|
||||||
|
request, user_profile, topic_name, body, event, no_previews=skip_previews(event)
|
||||||
|
)
|
||||||
return json_success(request)
|
return json_success(request)
|
||||||
|
|
||||||
|
|
||||||
@@ -514,6 +568,18 @@ def get_topic_based_on_event(event: str, payload: WildValue, use_merge_request_t
|
|||||||
id=payload["issue"]["iid"].tame(check_int),
|
id=payload["issue"]["iid"].tame(check_int),
|
||||||
title=payload["issue"]["title"].tame(check_string),
|
title=payload["issue"]["title"].tame(check_string),
|
||||||
)
|
)
|
||||||
|
elif event in (
|
||||||
|
"Note Hook DesignManagement::Design",
|
||||||
|
"Confidential Note Hook DesignManagement::Design",
|
||||||
|
):
|
||||||
|
design_path = payload["object_attributes"]["position"]["new_path"].tame(check_string)
|
||||||
|
design_name = design_path.split("/")[-1]
|
||||||
|
|
||||||
|
return TOPIC_WITH_DESIGN_INFO_TEMPLATE.format(
|
||||||
|
repo=get_repo_name(payload),
|
||||||
|
type="design",
|
||||||
|
design_name=design_name,
|
||||||
|
)
|
||||||
elif event == "Note Hook MergeRequest":
|
elif event == "Note Hook MergeRequest":
|
||||||
return TOPIC_WITH_PR_OR_ISSUE_INFO_TEMPLATE.format(
|
return TOPIC_WITH_PR_OR_ISSUE_INFO_TEMPLATE.format(
|
||||||
repo=get_repo_name(payload),
|
repo=get_repo_name(payload),
|
||||||
|
|||||||
Reference in New Issue
Block a user