diff --git a/zerver/lib/integrations.py b/zerver/lib/integrations.py index b379134dfe..0df06e0d2b 100644 --- a/zerver/lib/integrations.py +++ b/zerver/lib/integrations.py @@ -330,6 +330,13 @@ WEBHOOK_INTEGRATIONS = [ WebhookIntegration('librato', ['monitoring']), WebhookIntegration('mention', ['marketing'], display_name='Mention'), WebhookIntegration('newrelic', ['monitoring'], display_name='New Relic'), + WebhookIntegration( + 'opbeat', + ['monitoring'], + display_name='Opbeat', + stream_name='opbeat', + function='zerver.webhooks.opbeat.view.api_opbeat_webhook' + ), WebhookIntegration('opsgenie', ['meta-integration', 'monitoring'], display_name='OpsGenie'), WebhookIntegration('pagerduty', ['monitoring']), WebhookIntegration('papertrail', ['monitoring']), diff --git a/zerver/webhooks/opbeat/__init__.py b/zerver/webhooks/opbeat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/zerver/webhooks/opbeat/comment.json b/zerver/webhooks/opbeat/comment.json deleted file mode 100644 index 477e345b51..0000000000 --- a/zerver/webhooks/opbeat/comment.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "id": "5df00003ea4e42458db48446692f6d37", - "url": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/activities/5df00003ea4e42458db48446692f6d37", - "html_url": "https://opbeat.com/topkek/test-flask-app/errors/2/#activity-5df00003ea4e42458db48446692f6d37", - "author": { - "id": "5802edc6e84b4843b23edff00c6d300a", - "legacy_id": 14883, - "first_name": "Ivche", - "last_name": "E", - "email": "ivchepro@gmail.com", - "avatar": { - "40": "https://secure.gravatar.com/avatar/cf936cf5a05314e692830f4d17ec434e?d=https%3A%2F%2Fopbeat.com%2Fstatic%2Fimages%2Fcommon%2Favatars%2Favatar_default_user.png&size=40", - "80": "https://secure.gravatar.com/avatar/cf936cf5a05314e692830f4d17ec434e?d=https%3A%2F%2Fopbeat.com%2Fstatic%2Fimages%2Fcommon%2Favatars%2Favatar_default_user.png&size=80", - "180": "https://secure.gravatar.com/avatar/cf936cf5a05314e692830f4d17ec434e?d=https%3A%2F%2Fopbeat.com%2Fstatic%2Fimages%2Fcommon%2Favatars%2Favatar_default_user.png&size=180", - "240": "https://secure.gravatar.com/avatar/cf936cf5a05314e692830f4d17ec434e?d=https%3A%2F%2Fopbeat.com%2Fstatic%2Fimages%2Fcommon%2Favatars%2Favatar_default_user.png&size=240", - "120": "https://secure.gravatar.com/avatar/cf936cf5a05314e692830f4d17ec434e?d=https%3A%2F%2Fopbeat.com%2Fstatic%2Fimages%2Fcommon%2Favatars%2Favatar_default_user.png&size=120" - }, - "html_url": "https://opbeat.com/account/settings/", - "type_name": "user" - }, - "created": "2017-12-10T18:14:23Z", - "occurred": "2017-12-10T18:14:23Z", - "title": "Ivche commented on E#2", - "summary": "comment", - "organization": { - "id": "f9a77c5e6b044c2686612539fe4a3a6f", - "name": "topkek", - "short_name": "topkek", - "url": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f", - "type_name": "organization" - }, - "app": { - "id": "53daf84bfc", - "name": "Test-flask-app", - "short_name": "test-flask-app", - "url": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc", - "type_name": "app" - }, - "subject_type": "comment", - "subject": { - "id": "ca45bf96cd674b3eaa914251f0a08773", - "created": "2017-12-10T18:14:23Z", - "text": "comment", - "html": "
comment
", - "author": { - "id": "5802edc6e84b4843b23edff00c6d300a", - "legacy_id": 14883, - "first_name": "Ivche", - "last_name": "E", - "email": "ivchepro@gmail.com", - "avatar": { - "40": "https://secure.gravatar.com/avatar/cf936cf5a05314e692830f4d17ec434e?d=https%3A%2F%2Fopbeat.com%2Fstatic%2Fimages%2Fcommon%2Favatars%2Favatar_default_user.png&size=40", - "80": "https://secure.gravatar.com/avatar/cf936cf5a05314e692830f4d17ec434e?d=https%3A%2F%2Fopbeat.com%2Fstatic%2Fimages%2Fcommon%2Favatars%2Favatar_default_user.png&size=80", - "180": "https://secure.gravatar.com/avatar/cf936cf5a05314e692830f4d17ec434e?d=https%3A%2F%2Fopbeat.com%2Fstatic%2Fimages%2Fcommon%2Favatars%2Favatar_default_user.png&size=180", - "240": "https://secure.gravatar.com/avatar/cf936cf5a05314e692830f4d17ec434e?d=https%3A%2F%2Fopbeat.com%2Fstatic%2Fimages%2Fcommon%2Favatars%2Favatar_default_user.png&size=240", - "120": "https://secure.gravatar.com/avatar/cf936cf5a05314e692830f4d17ec434e?d=https%3A%2F%2Fopbeat.com%2Fstatic%2Fimages%2Fcommon%2Favatars%2Favatar_default_user.png&size=120" - }, - "html_url": "https://opbeat.com/account/settings/", - "type_name": "user" - }, - "html_url": "https://opbeat.com/topkek/test-flask-app/errors/2/#activity-5df00003ea4e42458db48446692f6d37", - "subject_type": "errorgroup", - "subject": { - "id": "eac027998bc44fb0b2f6269eb0ab0418", - "number": 2, - "created": "2017-12-10T17:52:48Z", - "first_seen": "2017-12-10T17:52:47Z", - "last_seen": "2017-12-10T18:07:55Z", - "times_seen": { - "this_release": 10, - "total": 10, - "24h": 10.0, - "72h": 10.0 - }, - "url": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418", - "html_url": "https://opbeat.com/topkek/test-flask-app/errors/2/", - "links": { - "activities": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418/activities", - "undo-mark-as-fixed": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418/actions/undo-mark-fixed", - "mark-as-temporarily-ignored": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418/actions/temporarily-ignore", - "comments": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418/comments", - "mark-as-fixed": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418/actions/mark-fixed", - "assignable-users": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/assignable-users", - "undo-mark-as-ignored": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418/actions/undo-ignore", - "mark-as-ignored": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418/actions/ignore", - "undo-mark-as-temporarily-ignored": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418/actions/undo-temporarily-ignore", - "assign": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418/actions/assign" - }, - "last_occurrence": { - "id": "24210da90e5b4d659965b2e3ebf360f4", - "created": "2017-12-10T18:07:55Z", - "culprit": "", - "message": "A warning occurred (42 apples)", - "logger": "__main__", - "client_supplied_id": "13a4410d11724b30af92cd6ef1d556e8", - "extra": { - "thread": "140621113497408", - "process": "6120", - "threadName": "MainThread", - "stack_info": "None", - "module": "app", - "funcName": "foo", - "processName": "MainProcess", - "pathname": "app.py", - "lineno": "17", - "message": "A warning occurred (42 apples)", - "filename": "app.py" - }, - "type_name": "errorlog" - }, - "resolved_state": "regressed", - "allowed_resolve_transitions": [ - "mark-as-temporarily-ignored", - "mark-as-fixed", - "mark-as-ignored" - ], - "is_alarm": false, - "num_comments": 4, - "is_expired": false, - "type_name": "errorgroup", - "first_error": { - "id": "24210da90e5b4d659965b2e3ebf360f4", - "created": "2017-12-10T18:07:55Z", - "culprit": "", - "message": "A warning occurred (42 apples)", - "logger": "__main__", - "client_supplied_id": "13a4410d11724b30af92cd6ef1d556e8", - "extra": { - "thread": "140621113497408", - "process": "6120", - "threadName": "MainThread", - "stack_info": "None", - "module": "app", - "funcName": "foo", - "processName": "MainProcess", - "pathname": "app.py", - "lineno": "17", - "message": "A warning occurred (42 apples)", - "filename": "app.py" - }, - "type_name": "errorlog" - } - }, - "type_name": "comment" - }, - "action": "created", - "type_name": "activity" -} diff --git a/zerver/webhooks/opbeat/error_fixed.json b/zerver/webhooks/opbeat/fixtures/error_fixed.json similarity index 94% rename from zerver/webhooks/opbeat/error_fixed.json rename to zerver/webhooks/opbeat/fixtures/error_fixed.json index ae6c13393c..7ea08abb77 100644 --- a/zerver/webhooks/opbeat/error_fixed.json +++ b/zerver/webhooks/opbeat/fixtures/error_fixed.json @@ -1,11 +1,11 @@ { "id": "bf991a45d9184b0ca6fb3d48d3db4c38", "url": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/activities/bf991a45d9184b0ca6fb3d48d3db4c38", - "html_url": "https://opbeat.com/topkek/test-flask-app/errors/2/#activity-bf991a45d9184b0ca6fb3d48d3db4c38", + "html_url": "https://opbeat.com/test_org/test-flask-app/errors/2/#activity-bf991a45d9184b0ca6fb3d48d3db4c38", "author": { "id": "5802edc6e84b4843b23edff00c6d300a", "legacy_id": 14883, - "first_name": "Ivche", + "first_name": "foo", "last_name": "E", "email": "ivchepro@gmail.com", "avatar": { @@ -20,12 +20,12 @@ }, "created": "2017-12-10T18:15:07Z", "occurred": "2017-12-10T18:15:07Z", - "title": "Ivche marked E#2 as fixed", - "summary": "Ivche marked the error group as fixed", + "title": "foo marked E#2 as fixed", + "summary": "foo marked the error group as fixed", "organization": { "id": "f9a77c5e6b044c2686612539fe4a3a6f", - "name": "topkek", - "short_name": "topkek", + "name": "test_org", + "short_name": "test_org", "url": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f", "type_name": "organization" }, @@ -50,7 +50,7 @@ "72h": 10.0 }, "url": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418", - "html_url": "https://opbeat.com/topkek/test-flask-app/errors/2/", + "html_url": "https://opbeat.com/test_org/test-flask-app/errors/2/", "links": { "activities": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418/activities", "undo-mark-as-fixed": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418/actions/undo-mark-fixed", diff --git a/zerver/webhooks/opbeat/error_reopen.json b/zerver/webhooks/opbeat/fixtures/error_reopen.json similarity index 94% rename from zerver/webhooks/opbeat/error_reopen.json rename to zerver/webhooks/opbeat/fixtures/error_reopen.json index e85f36a42b..53b1bb194d 100644 --- a/zerver/webhooks/opbeat/error_reopen.json +++ b/zerver/webhooks/opbeat/fixtures/error_reopen.json @@ -1,11 +1,11 @@ { "id": "38a556dfc0b04a59a586359bbce1463d", "url": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/activities/38a556dfc0b04a59a586359bbce1463d", - "html_url": "https://opbeat.com/topkek/test-flask-app/errors/2/#activity-38a556dfc0b04a59a586359bbce1463d", + "html_url": "https://opbeat.com/test_org/test-flask-app/errors/2/#activity-38a556dfc0b04a59a586359bbce1463d", "author": { "id": "5802edc6e84b4843b23edff00c6d300a", "legacy_id": 14883, - "first_name": "Ivche", + "first_name": "foo", "last_name": "E", "email": "ivchepro@gmail.com", "avatar": { @@ -20,12 +20,12 @@ }, "created": "2017-12-10T18:15:39Z", "occurred": "2017-12-10T18:15:39Z", - "title": "Ivche reopened E#2", - "summary": "Ivche reopened the error group", + "title": "foo reopened E#2", + "summary": "foo reopened the error group", "organization": { "id": "f9a77c5e6b044c2686612539fe4a3a6f", - "name": "topkek", - "short_name": "topkek", + "name": "test_org", + "short_name": "test_org", "url": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f", "type_name": "organization" }, @@ -50,7 +50,7 @@ "72h": 10.0 }, "url": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418", - "html_url": "https://opbeat.com/topkek/test-flask-app/errors/2/", + "html_url": "https://opbeat.com/test_org/test-flask-app/errors/2/", "links": { "activities": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418/activities", "undo-mark-as-fixed": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418/actions/undo-mark-fixed", diff --git a/zerver/webhooks/opbeat/fixtures/new_app.json b/zerver/webhooks/opbeat/fixtures/new_app.json new file mode 100644 index 0000000000..cb5fffc80b --- /dev/null +++ b/zerver/webhooks/opbeat/fixtures/new_app.json @@ -0,0 +1,37 @@ +{ + "id": "47f1f10158d94cbf93a0b539abc64b0f", + "html_url": null, + "title": "foo", + "summary": "App foo created", + "organization": { + "id": "27a4c0633f5c4a24a15a8896754145c4", + "name": "bar", + "short_name": "bar", + "url": "https://opbeat.com/api/v2/organizations/27a4c0633f5c4a24a15a8896754145c4", + "type_name": "organization" + }, + "app": { + "id": "bdb4751769", + "name": "foo", + "short_name": "foo", + "url": "https://opbeat.com/api/v2/organizations/27a4c0633f5c4a24a15a8896754145c4/apps/bdb4751769", + "type_name": "app" + }, + "subject_type": "app", + "subject": { + "id": "bdb4751769", + "created": "2018-01-03T11:16:49Z", + "name": "foo", + "short_name": "foo", + "settings": null, + "url": "https://opbeat.com/api/v2/organizations/27a4c0633f5c4a24a15a8896754145c4/apps/bdb4751769", + "html_url": "/bar/foo/", + "has_unread_notifications": false, + "has_metrics_available": false, + "language": "nodejs", + "framework": "custom", + "type_name": "app" + }, + "action": "created", + "type_name": "activity" +} diff --git a/zerver/webhooks/opbeat/fixtures/new_comment.json b/zerver/webhooks/opbeat/fixtures/new_comment.json new file mode 100644 index 0000000000..66c49f0af2 --- /dev/null +++ b/zerver/webhooks/opbeat/fixtures/new_comment.json @@ -0,0 +1,47 @@ +{ + "id": "5df00003ea4e42458db48446692f6d37", + "html_url": "https://opbeat.com/foo/test-flask-app/errors/2/#activity-5df00003ea4e42458db48446692f6d37", + "author": { + "first_name": "foo", + "last_name": "bar" + }, + "title": "foo commented on E#2", + "summary": "test comment", + "subject_type": "comment", + "subject": { + "id": "ca45bf96cd674b3eaa914251f0a08773", + "text": "test comment", + "author": { + "id": "5802edc6e84b4843b23edff00c6d300a", + "first_name": "foo", + "last_name": "barE" + }, + "html_url": "https://opbeat.com/foo/test-flask-app/errors/2/#activity-5df00003ea4e42458db48446692f6d37", + "subject_type": "errorgroup", + "subject": { + "id": "eac027998bc44fb0b2f6269eb0ab0418", + "number": 2, + "html_url": "https://opbeat.com/foo/test-flask-app/errors/2/", + "last_occurrence": { + "id": "24210da90e5b4d659965b2e3ebf360f4", + "created": "2017-12-10T18:07:55Z", + "culprit": "", + "message": "A warning occurred (42 apples)", + "logger": "__main__", + "client_supplied_id": "13a4410d11724b30af92cd6ef1d556e8", + "extra": { + "module": "app", + "funcName": "foo", + "processName": "MainProcess", + "pathname": "app.py", + "lineno": "17", + "message": "A warning occurred (42 apples)" + }, + "type_name": "errorlog" + } + }, + "type_name": "comment" + }, + "action": "created", + "type_name": "activity" +} diff --git a/zerver/webhooks/opbeat/new_error.json b/zerver/webhooks/opbeat/fixtures/new_error.json similarity index 95% rename from zerver/webhooks/opbeat/new_error.json rename to zerver/webhooks/opbeat/fixtures/new_error.json index 846a75d212..ab3b40246b 100644 --- a/zerver/webhooks/opbeat/new_error.json +++ b/zerver/webhooks/opbeat/fixtures/new_error.json @@ -2,15 +2,15 @@ { "id": "c0396f38323a4fa7b314f87d5ed9cdd2", "url": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/activities/c0396f38323a4fa7b314f87d5ed9cdd2", - "html_url": "https://opbeat.com/topkek/test-flask-app/errors/2/#activity-c0396f38323a4fa7b314f87d5ed9cdd2", + "html_url": "https://opbeat.com/test_org/test-flask-app/errors/2/#activity-c0396f38323a4fa7b314f87d5ed9cdd2", "created": "2017-12-10T18:07:55Z", "occurred": "2017-12-10T18:07:55Z", "title": "E#2 regressed", "summary": "The error group regressed", "organization": { "id": "f9a77c5e6b044c2686612539fe4a3a6f", - "name": "topkek", - "short_name": "topkek", + "name": "test_org", + "short_name": "test_org", "url": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f", "type_name": "organization" }, @@ -35,7 +35,7 @@ "72h": 10.0 }, "url": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418", - "html_url": "https://opbeat.com/topkek/test-flask-app/errors/2/", + "html_url": "https://opbeat.com/test_org/test-flask-app/errors/2/", "links": { "activities": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418/activities", "undo-mark-as-fixed": "https://opbeat.com/api/v2/organizations/f9a77c5e6b044c2686612539fe4a3a6f/apps/53daf84bfc/errorgroups/eac027998bc44fb0b2f6269eb0ab0418/actions/undo-mark-fixed", diff --git a/zerver/webhooks/opbeat/fixtures/unsupported_object.json b/zerver/webhooks/opbeat/fixtures/unsupported_object.json new file mode 100644 index 0000000000..4f6ef0aec2 --- /dev/null +++ b/zerver/webhooks/opbeat/fixtures/unsupported_object.json @@ -0,0 +1,30 @@ +{ + "id": "47f1f10158d94cbf93a0b539abc64b0f", + "html_url": null, + "title": "test title", + "summary": "test summary", + "organization": { + "id": "27a4c0633f5c4a24a15a8896754145c4", + "name": "bar", + "short_name": "bar", + "url": "https://opbeat.com/api/v2/organizations/27a4c0633f5c4a24a15a8896754145c4", + "type_name": "organization" + }, + "app": { + "id": "bdb4751769", + "name": "foo", + "short_name": "foo", + "url": "https://opbeat.com/api/v2/organizations/27a4c0633f5c4a24a15a8896754145c4/apps/bdb4751769", + "type_name": "app" + }, + "subject_type": "foo", + "subject": { + "id": "bdb4751769", + "created": "2018-01-03T11:16:49Z", + "name": "foo", + "short_name": "foo", + "settings": null + }, + "action": "created", + "type_name": "activity" +} diff --git a/zerver/webhooks/opbeat/tests.py b/zerver/webhooks/opbeat/tests.py new file mode 100644 index 0000000000..05b9ef7748 --- /dev/null +++ b/zerver/webhooks/opbeat/tests.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +from typing import Text + +from zerver.lib.test_classes import WebhookTestCase +from zerver.webhooks.opbeat.view import get_value + + +class OpbeatHookTests(WebhookTestCase): + STREAM_NAME = 'opbeat' + URL_TEMPLATE = u"/api/v1/external/opbeat?api_key={api_key}" + FIXTURE_DIR_NAME = 'opbeat' + + def test_comment(self) -> None: + expected_subject = "foo commented on E#2" + expected_message = ''' +**[foo commented on E#2](https://opbeat.com/foo/test-flask-app/errors/2/#activity-5df00003ea4e42458db48446692f6d37)** +test comment + + +**[E#2](https://opbeat.com/foo/test-flask-app/errors/2/)** + +>**Most recent Occurrence** +>in app.py +>A warning occurred (42 apples)''' + self.send_and_test_stream_message('new_comment', expected_subject, expected_message, + content_type="application/json") + + def test_new_app(self) -> None: + expected_subject = "foo" + expected_message = ''' +**foo** +App foo created + +**[foo](https://opbeat.com/bar/foo/)** +>language: nodejs +>framework: custom''' + self.send_and_test_stream_message('new_app', expected_subject, expected_message, + content_type="application/json") + + def test_get_empty_value(self) -> None: + self.assertEqual(get_value({'key': 'value'}, 'foo'), '') + + def test_no_subject_type(self) -> None: + expected_subject = "test title" + expected_message = ''' +**test title** +test summary''' + self.send_and_test_stream_message( + 'unsupported_object', + expected_subject, + expected_message, + content_type='application/json' + ) + + def test_error_fixed(self) -> None: + expected_subject = 'foo marked E#2 as fixed' + expected_message = ''' +**[foo marked E#2 as fixed](https://opbeat.com/test_org/test-flask-app/errors/2/#activity-bf991a45d9184b0ca6fb3d48d3db4c38)** +foo marked the error group as fixed + +**[E#2](https://opbeat.com/test_org/test-flask-app/errors/2/)** + +>**Most recent Occurrence** +>in app.py +>A warning occurred (42 apples)''' + self.send_and_test_stream_message( + 'error_fixed', expected_subject, expected_message, content_type='application/json') + + def test_error_reopened(self) -> None: + expected_subject = 'foo reopened E#2' + expected_message = ''' +**[foo reopened E#2](https://opbeat.com/test_org/test-flask-app/errors/2/#activity-38a556dfc0b04a59a586359bbce1463d)** +foo reopened the error group + +**[E#2](https://opbeat.com/test_org/test-flask-app/errors/2/)** + +>**Most recent Occurrence** +>in app.py +>A warning occurred (42 apples)''' + self.send_and_test_stream_message( + 'error_reopen', expected_subject, expected_message, content_type='application/json') + + def test_error_regressed(self) -> None: + expected_subject = 'E#2 regressed' + expected_message = ''' +**[E#2 regressed](https://opbeat.com/test_org/test-flask-app/errors/2/#activity-c0396f38323a4fa7b314f87d5ed9cdd2)** +The error group regressed + +**[E#2](https://opbeat.com/test_org/test-flask-app/errors/2/)** + +>**Most recent Occurrence** +>in app.py +>A warning occurred (42 apples)''' + self.send_and_test_stream_message( + 'new_error', expected_subject, expected_message, content_type='application/json') diff --git a/zerver/webhooks/opbeat/view.py b/zerver/webhooks/opbeat/view.py new file mode 100644 index 0000000000..b62512d395 --- /dev/null +++ b/zerver/webhooks/opbeat/view.py @@ -0,0 +1,120 @@ +# Webhooks for external integrations. +from typing import Text, Dict, Any, List, Tuple, Union + +from django.http import HttpRequest, HttpResponse + +from zerver.decorator import api_key_only_webhook_view +from zerver.lib.actions import check_send_stream_message +from zerver.lib.request import REQ, has_request_variables +from zerver.lib.response import json_success +from zerver.models import UserProfile, get_client + +subject_types = { + 'app': [ # Object type name + ['name'], # Title + ['html_url'], # Automatically put into title + ['language'], # Other properties. + ['framework'] + ], + 'base': [ + ['title'], + ['html_url'], + ['#summary'], + ['subject'] + ], + 'comment': [ + [''], + ['subject'] + ], + 'errorgroup': [ + ['E#{}', 'number'], + ['html_url'], + ['last_occurrence:error'] + ], + 'error': [ + [''], + ['">**Most recent Occurrence**'], + ['in {}', 'extra/pathname'], + ['!message'] + ] +} # type: Dict[str, List[List[str]]] + + +def get_value(_obj: Dict[str, Any], key: str) -> str: + for _key in key.lstrip('!').split('/'): + if _key in _obj.keys(): + _obj = _obj[_key] + else: + return '' + return str(_obj) + + +def format_object( + obj: Dict[str, Any], + subject_type: str, + message: str +) -> str: + if subject_type not in subject_types.keys(): + return message + keys = subject_types[subject_type][1:] # type: List[List[str]] + title = subject_types[subject_type][0] + if title[0] != '': + title_str = '' + if len(title) > 1: + title_str = title[0].format(get_value(obj, title[1])) + else: + title_str = obj[title[0]] + if obj['html_url'] is not None: + url = obj['html_url'] # type: str + if 'opbeat.com' not in url: + url = 'https://opbeat.com/' + url.lstrip('/') + message += '\n**[{}]({})**'.format(title_str, url) + else: + message += '\n**{}**'.format(title_str) + for key_list in keys: + if len(key_list) > 1: + value = key_list[0].format(get_value(obj, key_list[1])) + message += '\n>{}'.format(value) + else: + key = key_list[0] + key_raw = key.lstrip('!').lstrip('#').lstrip('"') + if key_raw != 'html_url' and key_raw != 'subject' and ':' not in key_raw: + value = get_value(obj, key_raw) + if key.startswith('!'): + message += '\n>{}'.format(value) + elif key.startswith('#'): + message += '\n{}'.format(value) + elif key.startswith('"'): + message += '\n{}'.format(key_raw) + else: + message += '\n>{}: {}'.format(key, value) + if key == 'subject': + message = format_object( + obj['subject'], obj['subject_type'], message + '\n') + if ':' in key: + value, value_type = key.split(':') + message = format_object(obj[value], value_type, message + '\n') + return message + + +@api_key_only_webhook_view("Opbeat") +@has_request_variables +def api_opbeat_webhook( + request: HttpRequest, + user_profile: UserProfile, + payload: Dict[str, Any]=REQ(argument_type='body'), + stream: str=REQ(default="opbeat") +) -> HttpResponse: + """ + This uses the subject name from opbeat to make the subject, + and the summary from Opbeat as the message body, with + details about the object mentioned. + """ + + message_subject = payload['title'] + + message = format_object(payload, 'base', '') + + check_send_stream_message(user_profile, get_client('ZulipOpbeatWebhook'), + stream, message_subject, message) + return json_success()