From 41c0e9ba479edae5c4f3c8aa4c96c74028438489 Mon Sep 17 00:00:00 2001 From: Eeshan Garg Date: Thu, 1 Nov 2018 17:26:22 -0230 Subject: [PATCH] webhooks: Add ReviewBoard integration. --- .../images/integrations/logos/reviewboard.png | Bin 0 -> 16432 bytes .../images/integrations/reviewboard/001.png | Bin 0 -> 65353 bytes zerver/lib/integrations.py | 1 + zerver/webhooks/reviewboard/__init__.py | 0 zerver/webhooks/reviewboard/doc.md | 17 ++ .../reviewboard/fixtures/reply_published.json | 219 ++++++++++++++++++ .../fixtures/review_published.json | 212 +++++++++++++++++ .../fixtures/review_request_closed.json | 159 +++++++++++++ .../fixtures/review_request_published.json | 117 ++++++++++ ...published_with_multiple_target_people.json | 127 ++++++++++ .../fixtures/review_request_reopened.json | 158 +++++++++++++ zerver/webhooks/reviewboard/tests.py | 64 +++++ zerver/webhooks/reviewboard/view.py | 180 ++++++++++++++ 13 files changed, 1254 insertions(+) create mode 100644 static/images/integrations/logos/reviewboard.png create mode 100644 static/images/integrations/reviewboard/001.png create mode 100644 zerver/webhooks/reviewboard/__init__.py create mode 100644 zerver/webhooks/reviewboard/doc.md create mode 100644 zerver/webhooks/reviewboard/fixtures/reply_published.json create mode 100644 zerver/webhooks/reviewboard/fixtures/review_published.json create mode 100644 zerver/webhooks/reviewboard/fixtures/review_request_closed.json create mode 100644 zerver/webhooks/reviewboard/fixtures/review_request_published.json create mode 100644 zerver/webhooks/reviewboard/fixtures/review_request_published_with_multiple_target_people.json create mode 100644 zerver/webhooks/reviewboard/fixtures/review_request_reopened.json create mode 100644 zerver/webhooks/reviewboard/tests.py create mode 100644 zerver/webhooks/reviewboard/view.py diff --git a/static/images/integrations/logos/reviewboard.png b/static/images/integrations/logos/reviewboard.png new file mode 100644 index 0000000000000000000000000000000000000000..9a354e19cb4e4300916c33081af28c250965ff8e GIT binary patch literal 16432 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 literal 0 HcmV?d00001 diff --git a/static/images/integrations/reviewboard/001.png b/static/images/integrations/reviewboard/001.png new file mode 100644 index 0000000000000000000000000000000000000000..d66b13d34b4af9ddde7353b52e5e81534201f42d GIT binary patch literal 65353 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 AddSpaces.java (dc54788b2bf0324a3228faa7224c3c523582d9f6)" + } + }, + "timestamp":"2018-10-26T15:57:39Z", + "text_type":"markdown", + "public":true, + "text":"I agree, I'll push another diff addressing that. Thanks for the review! :)", + "first_line":81, + "extra_data":{ + + }, + "id":778505, + "issue_status":"" + } + ], + "file_attachment_comments":[ + + ], + "general_comments":[ + + ], + "screenshot_comments":[ + + ], + "reply":{ + "body_top":"", + "extra_data":{ + + }, + "links":{ + "diff_comments":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/651728/replies/651732/diff-comments/", + "method":"GET" + }, + "file_attachment_comments":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/651728/replies/651732/file-attachment-comments/", + "method":"GET" + }, + "self":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/651728/replies/651732/", + "method":"GET" + }, + "update":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/651728/replies/651732/", + "method":"PUT" + }, + "general_comments":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/651728/replies/651732/general-comments/", + "method":"GET" + }, + "screenshot_comments":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/651728/replies/651732/screenshot-comments/", + "method":"GET" + }, + "user":{ + "href":"https://rbcommons.com/s/zulip/api/users/drsbgarg/", + "method":"GET", + "title":"drsbgarg" + }, + "delete":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/651728/replies/651732/", + "method":"DELETE" + } + }, + "timestamp":"2018-10-26T15:57:39Z", + "public":true, + "text_type":null, + "body_bottom":"", + "body_top_text_type":"plain", + "id":651732, + "body_bottom_text_type":"plain" + }, + "review_request":{ + "status":"pending", + "last_updated":"2018-10-26T15:57:39Z", + "target_people":[ + { + "href":"https://rbcommons.com/s/zulip/api/users/drsbgarg/", + "method":"GET", + "title":"drsbgarg" + } + ], + "depends_on":[ + + ], + "description_text_type":"plain", + "issue_resolved_count":0, + "commit_id":"4f8a093f7046fcc58b0826d21586d1b537b0112b", + "ship_it_count":0, + "close_description_text_type":"plain", + "id":1, + "links":{ + "diffs":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/diffs/", + "method":"GET" + }, + "latest_diff":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/diffs/1/", + "method":"GET" + }, + "repository":{ + "href":"https://rbcommons.com/s/zulip/api/repositories/2147/", + "method":"GET", + "title":"Scheduler" + }, + "screenshots":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/screenshots/", + "method":"GET" + }, + "self":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/", + "method":"GET" + }, + "status_updates":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/status-updates/", + "method":"GET" + }, + "update":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/", + "method":"PUT" + }, + "last_update":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/last-update/", + "method":"GET" + }, + "reviews":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/", + "method":"GET" + }, + "file_attachments":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/file-attachments/", + "method":"GET" + }, + "draft":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/draft/", + "method":"GET" + }, + "diff_context":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/diff-context/", + "method":"GET" + }, + "submitter":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/", + "method":"GET", + "title":"eeshangarg" + }, + "changes":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/changes/", + "method":"GET" + }, + "delete":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/", + "method":"DELETE" + } + }, + "issue_dropped_count":0, + "bugs_closed":[ + + ], + "testing_done":"This was tested thoroughly", + "branch":"master", + "text_type":null, + "time_added":"2018-10-26T15:24:18Z", + "extra_data":{ + "calculated_trophies":true + }, + "public":true, + "issue_verifying_count":0, + "close_description":null, + "blocks":[ + + ], + "description":"Initial commit (first iteration)", + "testing_done_text_type":"markdown", + "issue_open_count":0, + "approved":false, + "url":"/s/zulip/r/1/", + "absolute_url":"https://rbcommons.com/s/zulip/r/1/", + "target_groups":[ + + ], + "summary":"Initial commit (first iteration)", + "changenum":null, + "approval_failure":"The review request has not been marked \"Ship It!\"" + }, + "event":"reply_published" +} diff --git a/zerver/webhooks/reviewboard/fixtures/review_published.json b/zerver/webhooks/reviewboard/fixtures/review_published.json new file mode 100644 index 0000000000..e24d3bb7e8 --- /dev/null +++ b/zerver/webhooks/reviewboard/fixtures/review_published.json @@ -0,0 +1,212 @@ +{ + "diff_comments":[ + { + "issue_opened":false, + "interfilediff":null, + "num_lines":1, + "links":{ + "self":{ + "href":"https://rbcommons.com", + "method":"GET" + }, + "user":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/", + "method":"GET", + "title":"eeshangarg" + }, + "filediff":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/diffs/1/files/6128836/", + "method":"GET", + "title":"AddSpaces.java (PRE-CREATION) -> AddSpaces.java (dc54788b2bf0324a3228faa7224c3c523582d9f6)" + } + }, + "timestamp":"2018-10-26T15:52:29Z", + "id":778500, + "issue_status":"", + "text":"I think we should get rid of this extra whitespace here", + "first_line":81, + "extra_data":{ + "require_verification":false + }, + "public":true, + "text_type":"markdown" + } + ], + "file_attachment_comments":[ + + ], + "review":{ + "body_top":"Left some minor comments, thanks!", + "ship_it":false, + "extra_data":{ + + }, + "links":{ + "diff_comments":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/651728/diff-comments/", + "method":"GET" + }, + "file_attachment_comments":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/651728/file-attachment-comments/", + "method":"GET" + }, + "self":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/651728/", + "method":"GET" + }, + "update":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/651728/", + "method":"PUT" + }, + "general_comments":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/651728/general-comments/", + "method":"GET" + }, + "screenshot_comments":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/651728/screenshot-comments/", + "method":"GET" + }, + "user":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/", + "method":"GET", + "title":"eeshangarg" + }, + "replies":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/651728/replies/", + "method":"GET" + }, + "delete":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/651728/", + "method":"DELETE" + } + }, + "timestamp":"2018-10-26T15:52:29Z", + "absolute_url":"https://rbcommons.com/s/zulip/r/1/#review651728", + "public":true, + "text_type":null, + "body_bottom":"", + "body_top_text_type":"markdown", + "id":651728, + "body_bottom_text_type":"plain" + }, + "general_comments":[ + + ], + "screenshot_comments":[ + + ], + "review_request":{ + "status":"pending", + "last_updated":"2018-10-26T15:52:29Z", + "target_people":[ + { + "href":"https://rbcommons.com/s/zulip/api/users/drsbgarg/", + "method":"GET", + "title":"drsbgarg" + } + ], + "depends_on":[ + + ], + "description_text_type":"plain", + "issue_resolved_count":0, + "commit_id":"4f8a093f7046fcc58b0826d21586d1b537b0112b", + "ship_it_count":0, + "close_description_text_type":"plain", + "id":1, + "links":{ + "diffs":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/diffs/", + "method":"GET" + }, + "latest_diff":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/diffs/1/", + "method":"GET" + }, + "repository":{ + "href":"https://rbcommons.com/s/zulip/api/repositories/2147/", + "method":"GET", + "title":"Scheduler" + }, + "screenshots":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/screenshots/", + "method":"GET" + }, + "self":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/", + "method":"GET" + }, + "status_updates":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/status-updates/", + "method":"GET" + }, + "update":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/", + "method":"PUT" + }, + "last_update":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/last-update/", + "method":"GET" + }, + "reviews":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/", + "method":"GET" + }, + "file_attachments":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/file-attachments/", + "method":"GET" + }, + "draft":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/draft/", + "method":"GET" + }, + "diff_context":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/diff-context/", + "method":"GET" + }, + "submitter":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/", + "method":"GET", + "title":"eeshangarg" + }, + "changes":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/changes/", + "method":"GET" + }, + "delete":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/", + "method":"DELETE" + } + }, + "issue_dropped_count":0, + "bugs_closed":[ + + ], + "testing_done":"This was tested thoroughly", + "branch":"master", + "text_type":null, + "time_added":"2018-10-26T15:24:18Z", + "extra_data":{ + "calculated_trophies":true + }, + "public":true, + "issue_verifying_count":0, + "close_description":null, + "blocks":[ + + ], + "description":"Initial commit (first iteration)", + "testing_done_text_type":"markdown", + "issue_open_count":0, + "approved":false, + "url":"/s/zulip/r/1/", + "absolute_url":"https://rbcommons.com/s/zulip/r/1/", + "target_groups":[ + + ], + "summary":"Initial commit (first iteration)", + "changenum":null, + "approval_failure":"The review request has not been marked \"Ship It!\"" + }, + "event":"review_published" +} diff --git a/zerver/webhooks/reviewboard/fixtures/review_request_closed.json b/zerver/webhooks/reviewboard/fixtures/review_request_closed.json new file mode 100644 index 0000000000..f5acdfb443 --- /dev/null +++ b/zerver/webhooks/reviewboard/fixtures/review_request_closed.json @@ -0,0 +1,159 @@ +{ + "close_type":"submitted", + "review_request":{ + "status":"submitted", + "last_updated":"2018-10-26T15:41:55Z", + "target_people":[ + { + "href":"https://rbcommons.com/s/zulip/api/users/drsbgarg/", + "method":"GET", + "title":"drsbgarg" + } + ], + "depends_on":[ + + ], + "description_text_type":"plain", + "issue_resolved_count":0, + "commit_id":"4f8a093f7046fcc58b0826d21586d1b537b0112b", + "ship_it_count":0, + "close_description_text_type":"plain", + "id":1, + "links":{ + "diffs":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/diffs/", + "method":"GET" + }, + "latest_diff":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/diffs/1/", + "method":"GET" + }, + "repository":{ + "href":"https://rbcommons.com/s/zulip/api/repositories/2147/", + "method":"GET", + "title":"Scheduler" + }, + "screenshots":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/screenshots/", + "method":"GET" + }, + "self":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/", + "method":"GET" + }, + "status_updates":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/status-updates/", + "method":"GET" + }, + "update":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/", + "method":"PUT" + }, + "last_update":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/last-update/", + "method":"GET" + }, + "reviews":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/", + "method":"GET" + }, + "file_attachments":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/file-attachments/", + "method":"GET" + }, + "draft":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/draft/", + "method":"GET" + }, + "diff_context":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/diff-context/", + "method":"GET" + }, + "submitter":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/", + "method":"GET", + "title":"eeshangarg" + }, + "changes":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/changes/", + "method":"GET" + }, + "delete":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/", + "method":"DELETE" + } + }, + "issue_dropped_count":0, + "bugs_closed":[ + + ], + "testing_done":"This was tested thoroughly", + "branch":"master", + "text_type":null, + "time_added":"2018-10-26T15:24:18Z", + "extra_data":{ + "calculated_trophies":true + }, + "public":true, + "issue_verifying_count":0, + "close_description":"", + "blocks":[ + + ], + "description":"Initial commit (first iteration)", + "testing_done_text_type":"markdown", + "issue_open_count":0, + "approved":false, + "url":"/s/zulip/r/1/", + "absolute_url":"https://rbcommons.com/s/zulip/r/1/", + "target_groups":[ + + ], + "summary":"Initial commit (first iteration)", + "changenum":null, + "approval_failure":"The review request has not been marked \"Ship It!\"" + }, + "event":"review_request_closed", + "closed_by":{ + "username":"eeshangarg", + "first_name":"Eeshan", + "last_name":"Garg", + "links":{ + "api_tokens":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/api-tokens/", + "method":"GET" + }, + "self":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/", + "method":"GET" + }, + "archived_review_requests":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/archived-review-requests/", + "method":"GET" + }, + "user_file_attachments":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/user-file-attachments/", + "method":"GET" + }, + "muted_review_requests":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/muted-review-requests/", + "method":"GET" + }, + "watched":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/watched/", + "method":"GET" + } + }, + "url":"/s/zulip/users/eeshangarg/", + "is_active":true, + "fullname":"Eeshan Garg", + "email":"jerryguitarist@gmail.com", + "avatar_url":"https://secure.gravatar.com/avatar/cd181af88d928dab53c55600c9f7551d?s=48&d=mm", + "avatar_urls":{ + "1x":"https://secure.gravatar.com/avatar/cd181af88d928dab53c55600c9f7551d?s=48&d=mm", + "3x":"https://secure.gravatar.com/avatar/cd181af88d928dab53c55600c9f7551d?s=144&d=mm", + "2x":"https://secure.gravatar.com/avatar/cd181af88d928dab53c55600c9f7551d?s=96&d=mm" + }, + "id":11032 + } +} diff --git a/zerver/webhooks/reviewboard/fixtures/review_request_published.json b/zerver/webhooks/reviewboard/fixtures/review_request_published.json new file mode 100644 index 0000000000..0bed00f1fd --- /dev/null +++ b/zerver/webhooks/reviewboard/fixtures/review_request_published.json @@ -0,0 +1,117 @@ +{ + "review_request":{ + "status":"pending", + "last_updated":"2018-10-26T15:39:46Z", + "target_people":[ + { + "href":"https://rbcommons.com/s/zulip/api/users/drsbgarg/", + "method":"GET", + "title":"drsbgarg" + } + ], + "depends_on":[ + + ], + "description_text_type":"plain", + "issue_resolved_count":0, + "commit_id":"0c694b62d4cfd159e1b94b9368bc573338c7c77a", + "ship_it_count":0, + "close_description_text_type":"plain", + "id":2, + "links":{ + "diffs":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/diffs/", + "method":"GET" + }, + "latest_diff":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/diffs/1/", + "method":"GET" + }, + "repository":{ + "href":"https://rbcommons.com/s/zulip/api/repositories/2147/", + "method":"GET", + "title":"Scheduler" + }, + "screenshots":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/screenshots/", + "method":"GET" + }, + "self":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/", + "method":"GET" + }, + "status_updates":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/status-updates/", + "method":"GET" + }, + "update":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/", + "method":"PUT" + }, + "last_update":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/last-update/", + "method":"GET" + }, + "reviews":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/reviews/", + "method":"GET" + }, + "file_attachments":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/file-attachments/", + "method":"GET" + }, + "draft":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/draft/", + "method":"GET" + }, + "diff_context":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/diff-context/", + "method":"GET" + }, + "submitter":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/", + "method":"GET", + "title":"eeshangarg" + }, + "changes":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/changes/", + "method":"GET" + }, + "delete":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/", + "method":"DELETE" + } + }, + "issue_dropped_count":0, + "bugs_closed":[ + + ], + "testing_done":"This was tested thoroughly.", + "branch":"master", + "text_type":null, + "time_added":"2018-10-26T15:39:46Z", + "extra_data":{ + "calculated_trophies":true + }, + "public":true, + "issue_verifying_count":0, + "close_description":null, + "blocks":[ + + ], + "description":"Initial commit", + "testing_done_text_type":"markdown", + "issue_open_count":0, + "approved":false, + "url":"/s/zulip/r/2/", + "absolute_url":"https://rbcommons.com/s/zulip/r/2/", + "target_groups":[ + + ], + "summary":"Initial commit", + "changenum":null, + "approval_failure":"The review request has not been marked \"Ship It!\"" + }, + "event":"review_request_published", + "is_new":true +} diff --git a/zerver/webhooks/reviewboard/fixtures/review_request_published_with_multiple_target_people.json b/zerver/webhooks/reviewboard/fixtures/review_request_published_with_multiple_target_people.json new file mode 100644 index 0000000000..077cd8c788 --- /dev/null +++ b/zerver/webhooks/reviewboard/fixtures/review_request_published_with_multiple_target_people.json @@ -0,0 +1,127 @@ +{ + "review_request":{ + "status":"pending", + "last_updated":"2018-10-26T15:39:46Z", + "target_people":[ + { + "href":"https://rbcommons.com/s/zulip/api/users/drsbgarg/", + "method":"GET", + "title":"drsbgarg" + }, + { + "href":"https://rbcommons.com/s/zulip/api/users/johndoe/", + "method":"GET", + "title":"johndoe" + }, + { + "href":"https://rbcommons.com/s/zulip/api/users/janedoe/", + "method":"GET", + "title":"janedoe" + } + ], + "depends_on":[ + + ], + "description_text_type":"plain", + "issue_resolved_count":0, + "commit_id":"0c694b62d4cfd159e1b94b9368bc573338c7c77a", + "ship_it_count":0, + "close_description_text_type":"plain", + "id":2, + "links":{ + "diffs":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/diffs/", + "method":"GET" + }, + "latest_diff":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/diffs/1/", + "method":"GET" + }, + "repository":{ + "href":"https://rbcommons.com/s/zulip/api/repositories/2147/", + "method":"GET", + "title":"Scheduler" + }, + "screenshots":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/screenshots/", + "method":"GET" + }, + "self":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/", + "method":"GET" + }, + "status_updates":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/status-updates/", + "method":"GET" + }, + "update":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/", + "method":"PUT" + }, + "last_update":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/last-update/", + "method":"GET" + }, + "reviews":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/reviews/", + "method":"GET" + }, + "file_attachments":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/file-attachments/", + "method":"GET" + }, + "draft":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/draft/", + "method":"GET" + }, + "diff_context":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/diff-context/", + "method":"GET" + }, + "submitter":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/", + "method":"GET", + "title":"eeshangarg" + }, + "changes":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/changes/", + "method":"GET" + }, + "delete":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/2/", + "method":"DELETE" + } + }, + "issue_dropped_count":0, + "bugs_closed":[ + + ], + "testing_done":"This was tested thoroughly.", + "branch":"master", + "text_type":null, + "time_added":"2018-10-26T15:39:46Z", + "extra_data":{ + "calculated_trophies":true + }, + "public":true, + "issue_verifying_count":0, + "close_description":null, + "blocks":[ + + ], + "description":"Initial commit", + "testing_done_text_type":"markdown", + "issue_open_count":0, + "approved":false, + "url":"/s/zulip/r/2/", + "absolute_url":"https://rbcommons.com/s/zulip/r/2/", + "target_groups":[ + + ], + "summary":"Initial commit", + "changenum":null, + "approval_failure":"The review request has not been marked \"Ship It!\"" + }, + "event":"review_request_published", + "is_new":true +} diff --git a/zerver/webhooks/reviewboard/fixtures/review_request_reopened.json b/zerver/webhooks/reviewboard/fixtures/review_request_reopened.json new file mode 100644 index 0000000000..f3136ea89d --- /dev/null +++ b/zerver/webhooks/reviewboard/fixtures/review_request_reopened.json @@ -0,0 +1,158 @@ +{ + "reopened_by":{ + "username":"eeshangarg", + "first_name":"Eeshan", + "last_name":"Garg", + "links":{ + "api_tokens":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/api-tokens/", + "method":"GET" + }, + "self":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/", + "method":"GET" + }, + "archived_review_requests":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/archived-review-requests/", + "method":"GET" + }, + "user_file_attachments":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/user-file-attachments/", + "method":"GET" + }, + "muted_review_requests":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/muted-review-requests/", + "method":"GET" + }, + "watched":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/watched/", + "method":"GET" + } + }, + "url":"/s/zulip/users/eeshangarg/", + "is_active":true, + "fullname":"Eeshan Garg", + "email":"jerryguitarist@gmail.com", + "avatar_url":"https://secure.gravatar.com/avatar/cd181af88d928dab53c55600c9f7551d?s=48&d=mm", + "avatar_urls":{ + "1x":"https://secure.gravatar.com/avatar/cd181af88d928dab53c55600c9f7551d?s=48&d=mm", + "3x":"https://secure.gravatar.com/avatar/cd181af88d928dab53c55600c9f7551d?s=144&d=mm", + "2x":"https://secure.gravatar.com/avatar/cd181af88d928dab53c55600c9f7551d?s=96&d=mm" + }, + "id":11032 + }, + "review_request":{ + "status":"pending", + "last_updated":"2018-10-26T15:45:49Z", + "target_people":[ + { + "href":"https://rbcommons.com/s/zulip/api/users/drsbgarg/", + "method":"GET", + "title":"drsbgarg" + } + ], + "depends_on":[ + + ], + "description_text_type":"plain", + "issue_resolved_count":0, + "commit_id":"4f8a093f7046fcc58b0826d21586d1b537b0112b", + "ship_it_count":0, + "close_description_text_type":"plain", + "id":1, + "links":{ + "diffs":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/diffs/", + "method":"GET" + }, + "latest_diff":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/diffs/1/", + "method":"GET" + }, + "repository":{ + "href":"https://rbcommons.com/s/zulip/api/repositories/2147/", + "method":"GET", + "title":"Scheduler" + }, + "screenshots":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/screenshots/", + "method":"GET" + }, + "self":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/", + "method":"GET" + }, + "status_updates":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/status-updates/", + "method":"GET" + }, + "update":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/", + "method":"PUT" + }, + "last_update":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/last-update/", + "method":"GET" + }, + "reviews":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/reviews/", + "method":"GET" + }, + "file_attachments":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/file-attachments/", + "method":"GET" + }, + "draft":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/draft/", + "method":"GET" + }, + "diff_context":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/diff-context/", + "method":"GET" + }, + "submitter":{ + "href":"https://rbcommons.com/s/zulip/api/users/eeshangarg/", + "method":"GET", + "title":"eeshangarg" + }, + "changes":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/changes/", + "method":"GET" + }, + "delete":{ + "href":"https://rbcommons.com/s/zulip/api/review-requests/1/", + "method":"DELETE" + } + }, + "issue_dropped_count":0, + "bugs_closed":[ + + ], + "testing_done":"This was tested thoroughly", + "branch":"master", + "text_type":null, + "time_added":"2018-10-26T15:24:18Z", + "extra_data":{ + "calculated_trophies":true + }, + "public":true, + "issue_verifying_count":0, + "close_description":null, + "blocks":[ + + ], + "description":"Initial commit (first iteration)", + "testing_done_text_type":"markdown", + "issue_open_count":0, + "approved":false, + "url":"/s/zulip/r/1/", + "absolute_url":"https://rbcommons.com/s/zulip/r/1/", + "target_groups":[ + + ], + "summary":"Initial commit (first iteration)", + "changenum":null, + "approval_failure":"The review request has not been marked \"Ship It!\"" + }, + "event":"review_request_reopened" +} diff --git a/zerver/webhooks/reviewboard/tests.py b/zerver/webhooks/reviewboard/tests.py new file mode 100644 index 0000000000..257ae1179b --- /dev/null +++ b/zerver/webhooks/reviewboard/tests.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +from typing import Optional + +from zerver.lib.test_classes import WebhookTestCase + + +class ReviewBoardHookTests(WebhookTestCase): + STREAM_NAME = 'reviewboard' + URL_TEMPLATE = '/api/v1/external/reviewboard?&api_key={api_key}&stream={stream}' + FIXTURE_DIR_NAME = 'reviewboard' + + def test_review_request_published(self) -> None: + expected_topic = 'Scheduler' + expected_message = '**eeshangarg** opened [#2: Initial commit](https://rbcommons.com/s/zulip/r/2/):\n\n``` quote\n**Description**: Initial commit\n**Status**: pending\n**Target people**: **drsbgarg**\n**Branch**: master\n```' + self.send_and_test_stream_message( + 'review_request_published', + expected_topic, expected_message, + HTTP_X_REVIEWBOARD_EVENT='review_request_published' + ) + + def test_review_request_published_with_multiple_target_people(self) -> None: + expected_topic = 'Scheduler' + expected_message = '**eeshangarg** opened [#2: Initial commit](https://rbcommons.com/s/zulip/r/2/):\n\n``` quote\n**Description**: Initial commit\n**Status**: pending\n**Target people**: **drsbgarg**, **johndoe**, and **janedoe**\n**Branch**: master\n```' + self.send_and_test_stream_message( + 'review_request_published_with_multiple_target_people', + expected_topic, expected_message, + HTTP_X_REVIEWBOARD_EVENT='review_request_published' + ) + + def test_review_request_reopened(self) -> None: + expected_topic = 'Scheduler' + expected_message = '**eeshangarg** reopened [#1: Initial commit (first iteration)](https://rbcommons.com/s/zulip/r/1/):\n\n``` quote\n**Description**: Initial commit (first iteration)\n**Status**: pending\n**Target people**: **drsbgarg**\n**Branch**: master\n```' + self.send_and_test_stream_message( + 'review_request_reopened', + expected_topic, expected_message, + HTTP_X_REVIEWBOARD_EVENT='review_request_reopened' + ) + + def test_review_request_closed(self) -> None: + expected_topic = 'Scheduler' + expected_message = '**eeshangarg** closed [#1: Initial commit (first iteration)](https://rbcommons.com/s/zulip/r/1/):\n\n``` quote\n**Description**: Initial commit (first iteration)\n**Status**: submitted\n**Target people**: **drsbgarg**\n**Close type**: submitted\n**Branch**: master\n```' + self.send_and_test_stream_message( + 'review_request_closed', + expected_topic, expected_message, + HTTP_X_REVIEWBOARD_EVENT='review_request_closed' + ) + + def test_review_published(self) -> None: + expected_topic = 'Scheduler' + expected_message = '**eeshangarg** [reviewed](https://rbcommons.com/s/zulip/r/1/#review651728) [#1: Initial commit (first iteration)](https://rbcommons.com/s/zulip/r/1/):\n\n**Review**:\n``` quote\nLeft some minor comments, thanks!\n```' + self.send_and_test_stream_message( + 'review_published', + expected_topic, expected_message, + HTTP_X_REVIEWBOARD_EVENT='review_published' + ) + + def test_reply_published(self) -> None: + expected_topic = 'Scheduler' + expected_message = '**drsbgarg** [replied](https://rbcommons.com/s/zulip/api/review-requests/1/reviews/651728/replies/651732/) to [#1: Initial commit (first iteration)](https://rbcommons.com/s/zulip/api/review-requests/1/):\n\n**Reply**:\n``` quote\n\n```' + self.send_and_test_stream_message( + 'reply_published', + expected_topic, expected_message, + HTTP_X_REVIEWBOARD_EVENT='reply_published' + ) diff --git a/zerver/webhooks/reviewboard/view.py b/zerver/webhooks/reviewboard/view.py new file mode 100644 index 0000000000..aa19eba97b --- /dev/null +++ b/zerver/webhooks/reviewboard/view.py @@ -0,0 +1,180 @@ +from typing import Any, Dict, Iterable, Optional + +from django.http import HttpRequest, HttpResponse +from django.utils.translation import ugettext as _ + +from zerver.decorator import api_key_only_webhook_view +from zerver.lib.webhooks.common import check_send_webhook_message, \ + validate_extract_webhook_http_header, UnexpectedWebhookEventType +from zerver.lib.request import REQ, has_request_variables +from zerver.lib.response import json_error, json_success +from zerver.lib.validator import check_dict, check_string +from zerver.models import UserProfile + +REVIEW_REQUEST_PUBLISHED = """ +**{user_name}** opened [#{id}: {review_request_title}]({review_request_url}): +""" + +REVIEW_REQUEST_REOPENED = """ +**{user_name}** reopened [#{id}: {review_request_title}]({review_request_url}): +""" + +REVIEW_REQUEST_CLOSED = """ +**{user_name}** closed [#{id}: {review_request_title}]({review_request_url}): +""" + +REVIEW_PUBLISHED = """ +**{user_name}** [reviewed]({review_url}) [#{id}: {review_request_title}]({review_request_url}): + +**Review**: +``` quote +{review_body_top} +``` +""" + +REVIEW_REQUEST_DETAILS = """ +``` quote +**Description**: {description} +**Status**: {status} +**Target people**: {target_people} +{extra_info} +``` +""" + +REPLY_PUBLISHED = """ +**{user_name}** [replied]({reply_url}) to [#{id}: {review_request_title}]({review_request_url}): + +**Reply**: +``` quote +{reply_body_top} +``` +""" + +BRANCH_TEMPLATE = "**Branch**: {branch_name}" + +def get_target_people_string(payload: Dict[str, Any]) -> str: + result = "" + target_people = payload['review_request']['target_people'] + if len(target_people) == 1: + result = "**{title}**".format(**target_people[0]) + else: + for target_person in target_people[:-1]: + result += "**{title}**, ".format(**target_person) + result += "and **{title}**".format(**target_people[-1]) + + return result + +def get_review_published_body(payload: Dict[str, Any]) -> str: + kwargs = { + 'review_url': payload['review']['absolute_url'], + 'id': payload['review_request']['id'], + 'review_request_title': payload['review_request']['summary'], + 'review_request_url': payload['review_request']['absolute_url'], + 'user_name': payload['review']['links']['user']['title'], + 'review_body_top': payload['review']['body_top'], + } + + return REVIEW_PUBLISHED.format(**kwargs).strip() + +def get_reply_published_body(payload: Dict[str, Any]) -> str: + kwargs = { + 'reply_url': payload['reply']['links']['self']['href'], + 'id': payload['review_request']['id'], + 'review_request_title': payload['review_request']['summary'], + 'review_request_url': payload['review_request']['links']['self']['href'], + 'user_name': payload['reply']['links']['user']['title'], + 'user_url': payload['reply']['links']['user']['href'], + 'reply_body_top': payload['reply']['body_top'], + } + + return REPLY_PUBLISHED.format(**kwargs).strip() + +def get_review_request_published_body(payload: Dict[str, Any]) -> str: + kwargs = { + 'id': payload['review_request']['id'], + 'review_request_title': payload['review_request']['summary'], + 'review_request_url': payload['review_request']['absolute_url'], + 'user_name': payload['review_request']['links']['submitter']['title'], + 'description': payload['review_request']['description'], + 'status': payload['review_request']['status'], + 'target_people': get_target_people_string(payload), + 'extra_info': '', + } + + message = REVIEW_REQUEST_PUBLISHED + REVIEW_REQUEST_DETAILS + branch = payload['review_request'].get('branch') + if branch and branch is not None: + branch_info = BRANCH_TEMPLATE.format(branch_name=branch) + kwargs['extra_info'] = branch_info + + return message.format(**kwargs).strip() + +def get_review_request_reopened_body(payload: Dict[str, Any]) -> str: + kwargs = { + 'id': payload['review_request']['id'], + 'review_request_title': payload['review_request']['summary'], + 'review_request_url': payload['review_request']['absolute_url'], + 'user_name': payload['reopened_by']['username'], + 'description': payload['review_request']['description'], + 'status': payload['review_request']['status'], + 'target_people': get_target_people_string(payload), + 'extra_info': '', + } + + message = REVIEW_REQUEST_REOPENED + REVIEW_REQUEST_DETAILS + branch = payload['review_request'].get('branch') + if branch and branch is not None: + branch_info = BRANCH_TEMPLATE.format(branch_name=branch) + kwargs['extra_info'] = branch_info + + return message.format(**kwargs).strip() + +def get_review_request_closed_body(payload: Dict[str, Any]) -> str: + kwargs = { + 'id': payload['review_request']['id'], + 'review_request_title': payload['review_request']['summary'], + 'review_request_url': payload['review_request']['absolute_url'], + 'user_name': payload['closed_by']['username'], + 'description': payload['review_request']['description'], + 'status': payload['review_request']['status'], + 'target_people': get_target_people_string(payload), + 'extra_info': '**Close type**: {}'.format(payload['close_type']), + } + + message = REVIEW_REQUEST_CLOSED + REVIEW_REQUEST_DETAILS + branch = payload['review_request'].get('branch') + if branch and branch is not None: + branch_info = BRANCH_TEMPLATE.format(branch_name=branch) + kwargs['extra_info'] = '{}\n{}'.format(kwargs['extra_info'], branch_info) + + return message.format(**kwargs).strip() + +def get_review_request_repo_title(payload: Dict[str, Any]) -> str: + return payload['review_request']['links']['repository']['title'] + +RB_MESSAGE_FUNCTIONS = { + 'review_request_published': get_review_request_published_body, + 'review_request_reopened': get_review_request_reopened_body, + 'review_request_closed': get_review_request_closed_body, + 'review_published': get_review_published_body, + 'reply_published': get_reply_published_body, +} + +@api_key_only_webhook_view('ReviewBoard') +@has_request_variables +def api_reviewboard_webhook( + request: HttpRequest, user_profile: UserProfile, + payload: Dict[str, Iterable[Dict[str, Any]]]=REQ(argument_type='body') +) -> HttpResponse: + event_type = validate_extract_webhook_http_header( + request, 'X_REVIEWBOARD_EVENT', 'ReviewBoard') + + body_function = RB_MESSAGE_FUNCTIONS.get(event_type) + if body_function is not None: + body = body_function(payload) + topic = get_review_request_repo_title(payload) + check_send_webhook_message(request, user_profile, topic, body) + else: + raise UnexpectedWebhookEventType('ReviewBoard', event_type) + + return json_success()