integrations: Add OpsGenie integration.

This commit is contained in:
Robert Hönig
2017-08-14 18:03:36 +02:00
committed by Tim Abbott
parent a33790946b
commit 4be814fc16
21 changed files with 589 additions and 0 deletions

View File

@@ -0,0 +1 @@
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1745.7 1745.7"><style>.st0{fill:#ff8f1c}.st1{fill:#fff}</style><path class="st0" d="M872.8 85C437.7 85 85 437.7 85 872.8c0 435.1 352.7 787.8 787.8 787.8 435.1 0 787.8-352.7 787.8-787.8.1-435.1-352.7-787.8-787.8-787.8z"/><path class="st1" d="M1168.5 461.6c8.8 183.1-138.6 268.5-138.6 268.5 7.5-166.5-114.6-249.4-146.3-253.2 215.9 25.6 166.6 192.9 166.6 192.9 73.7-93.5 67.8-177.2 66.6-208.4-8.4-104.2-64.4-178.4-195.2-223.3 0 .1 237.1 17 246.9 223.5zM850.2 215.9c35-45.4 28.3-62.3 28.3-62.3 21.8 26.4 28.5 47.2 5.2 80.1-23.3 32.8 2.7 68.2 2.7 68.2-16.4-4.5-72.1-29.2-36.2-86z"/><path class="st1" d="M862.9 330.3h.8c23.4.3 41.6 32.3 41 71.3-.8 39.1-20.2 70.3-43.7 70-23.4-.4-41.6-32.2-41-71.3.6-38.5 19.8-69.6 42.9-70zM826 842c-1.5-.9-177.1-141.4-58-346.3 0 0-88.7 73.4-85.2 167.3-68.1-90.7-62.7-171.1-61.6-201.6 8.4-104.2 64.4-178.4 195.2-223.3 0 0-237 16.9-246.9 223.4-7 146.5 85.8 223.9 123.3 249 16.6 43.1 56.1 88.3 133.2 131.5zm-483.2 465.7c-69.1-109.9-156.2-300.9 106-327.7C645 959.8 735.5 879.2 746.1 858.2c-50.2 148.9-245.4 141.9-356.3 195.2-99.1 47.7-47 254.3-47 254.3z"/><path class="st1" d="M453.1 1242.7c17.1-91.4 95.7-119.7 165.4-119.7 72.7 0 136.2 30.4 136.2 30.4-218.9-30.3-301.6 89.3-301.6 89.3zm186.5 278l-76.5-31.6s166.8-23.4 313.3-142.2c0 0-75.9 138.4-236.8 173.8zm-65-233.1c58.8-72.5 174.2-121.8 307.1-121.8 129.5 0 242.5 46.8 302.6 116.4-77.3-45.8-183.3-74-300.5-74-121.7 0-231.3 30.5-309.2 79.4zm545.4 231.9c-160.9-35.4-232-172.6-232-172.6 146.5 118.8 305.4 142.2 305.4 142.2l-73.4 30.4zm288.7-211.8s52.1-206.5-47-254.3c-110.7-53.3-306.1-46.2-356.3-195.2 10.6 21 101.1 101.6 297.3 121.8 262 26.7 175 217.7 106 327.7z"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -276,6 +276,7 @@ WEBHOOK_INTEGRATIONS = [
WebhookIntegration('librato', ['monitoring']),
WebhookIntegration('mention', ['marketing'], display_name='Mention'),
WebhookIntegration('newrelic', ['monitoring'], display_name='New Relic'),
WebhookIntegration('opsgenie', ['meta-integration', 'monitoring'], display_name='OpsGenie'),
WebhookIntegration('pagerduty', ['monitoring']),
WebhookIntegration('papertrail', ['monitoring']),
WebhookIntegration('pingdom', ['monitoring']),

View File

View File

@@ -0,0 +1,11 @@
{!create-stream.md!}
{!create-bot-construct-url.md!}
Go to the [integration settings page](https://app.opsgenie.com/integration) of your OpsGenie account.
Under **Add New Integrations**, select *Webhook*, add the above URL and name the integration,
Zulip.
{!congrats.md!}
![](/static/images/integrations/opsgenie/000.png)

View File

@@ -0,0 +1,24 @@
{
"source":{
"name":"",
"type":"web"
},
"alert":{
"updatedAt":1420452224764002246,
"tags": [
"tag1", "tag2"
],
"message":"test alert",
"username":"fili@ifountain.com",
"alertId":"052652ac-5d1c-464a-812a-7dd18bbfba8c",
"source":"fili@ifountain.com",
"alias":"aliastest",
"tinyId":"23",
"createdAt":1420452191104,
"userId":"daed1180-0ce8-438b-8f8e-57e1a5920a2d",
"entity":""
},
"action":"Acknowledge",
"integrationId":"37c8f316-17c6-49d7-899b-9c7e540c048d",
"integrationName":"Integration1"
}

View File

@@ -0,0 +1,25 @@
{
"source":{
"name":"",
"type":"web"
},
"alert":{
"updatedAt":1420452275002000962,
"tags": [
"tag1", "tag2"
],
"message":"test alert",
"username":"fili@ifountain.com",
"alertId":"052652ac-5d1c-464a-812a-7dd18bbfba8c",
"source":"fili@ifountain.com",
"alias":"aliastest",
"tinyId":"23",
"createdAt":1420452191104,
"userId":"daed1180-0ce8-438b-8f8e-57e1a5920a2d",
"entity":"",
"note":"note to test alert"
},
"action":"AddNote",
"integrationId":"37c8f316-17c6-49d7-899b-9c7e540c048d",
"integrationName":"Integration1"
}

View File

@@ -0,0 +1,25 @@
{
"source":{
"name":"",
"type":"web"
},
"alert":{
"updatedAt":1420452274617001925,
"tags": [
"tag1", "tag2"
],
"message":"test alert",
"username":"fili@ifountain.com",
"alertId":"052652ac-5d1c-464a-812a-7dd18bbfba8c",
"source":"fili@ifountain.com",
"alias":"aliastest",
"tinyId":"23",
"createdAt":1420452191104,
"userId":"daed1180-0ce8-438b-8f8e-57e1a5920a2d",
"entity":"",
"recipient":"team2_escalation"
},
"action":"AddRecipient",
"integrationId":"37c8f316-17c6-49d7-899b-9c7e540c048d",
"integrationName":"Integration1"
}

View File

@@ -0,0 +1,25 @@
{
"source":{
"name":"",
"type":"web"
},
"alert":{
"updatedAt":1420452275002000962,
"tags": [
"tag1", "tag2", "tag3"
],
"addedTags":"tag1,tag2,tag3",
"message":"test alert",
"username":"fili@ifountain.com",
"alertId":"052652ac-5d1c-464a-812a-7dd18bbfba8c",
"source":"fili@ifountain.com",
"alias":"aliastest",
"tinyId":"23",
"createdAt":1420452191104,
"userId":"daed1180-0ce8-438b-8f8e-57e1a5920a2d",
"entity":""
},
"action":"AddTags",
"integrationId":"37c8f316-17c6-49d7-899b-9c7e540c048d",
"integrationName":"Integration1"
}

View File

@@ -0,0 +1,25 @@
{
"source":{
"name":"",
"type":"web"
},
"alert":{
"updatedAt":1420452256147001924,
"tags": [
"tag1", "tag2"
],
"message":"test alert",
"username":"fili@ifountain.com",
"alertId":"052652ac-5d1c-464a-812a-7dd18bbfba8c",
"source":"fili@ifountain.com",
"alias":"aliastest",
"tinyId":"23",
"createdAt":1420452191104,
"userId":"daed1180-0ce8-438b-8f8e-57e1a5920a2d",
"entity":"",
"team":"team2"
},
"action":"AddTeam",
"integrationId":"37c8f316-17c6-49d7-899b-9c7e540c048d",
"integrationName":"Integration1"
}

View File

@@ -0,0 +1,25 @@
{
"source":{
"name":"",
"type":"web"
},
"alert":{
"updatedAt":1420452374669001603,
"tags": [
"tag1", "tag2"
],
"message":"test alert",
"username":"fili@ifountain.com",
"alertId":"052652ac-5d1c-464a-812a-7dd18bbfba8c",
"source":"fili@ifountain.com",
"alias":"aliastest",
"tinyId":"23",
"createdAt":1420452191104,
"userId":"daed1180-0ce8-438b-8f8e-57e1a5920a2d",
"entity":"",
"owner" : "user2@ifountain.com"
},
"action":"AssignOwnership",
"integrationId":"37c8f316-17c6-49d7-899b-9c7e540c048d",
"integrationName":"Integration1"
}

View File

@@ -0,0 +1,23 @@
{
"source":{
"name":"",
"type":"web"
},
"alert":{
"updatedAt":1420452374669001603,
"tags": [
],
"message":"test alert",
"username":"fili@ifountain.com",
"alertId":"052652ac-5d1c-464a-812a-7dd18bbfba8c",
"source":"fili@ifountain.com",
"alias":"aliastest",
"tinyId":"23",
"createdAt":1420452191104,
"userId":"daed1180-0ce8-438b-8f8e-57e1a5920a2d",
"entity":""
},
"action":"Close",
"integrationId":"37c8f316-17c6-49d7-899b-9c7e540c048d",
"integrationName":"Integration1"
}

View File

@@ -0,0 +1,39 @@
{
"action":"Create",
"alert":{
"alertId":"ec03dad6-62c8-4c94-b38b-d88f398e900f",
"message":"another alert",
"tags":[
"vip"
],
"tinyId":"2",
"source":"test@example.com",
"entity":"Server",
"alias":"alert-alias",
"createdAt":1502709956288,
"updatedAt":1502709956288000110,
"username":"test@example.com",
"userId":"8d43133c-a002-4e95-bf2b-2d6f95e47c96",
"recipients":[
],
"teams":[
],
"insertedAt":1502709956288000110,
"priority":3
},
"source":{
"name":"web",
"type":"API"
},
"integrationId":"daca46c6-e26b-4dda-8f8a-21f6db7eeaef",
"integrationName":"Webhook",
"integrationType":"Webhook",
"alertFlowContext":{
"requestId":"df3ad546-c7b6-4520-8de3-52bb12d1e99a",
"traceId":"df3ad546-c7b6-4520-8de3-52bb12d1e99a",
"content":{
}
}
}

View File

@@ -0,0 +1,24 @@
{
"source":{
"name":"",
"type":"web"
},
"alert":{
"updatedAt":1420452275002000962,
"tags": [
"tag1", "tag2"
],
"message":"test alert",
"username":"fili@ifountain.com",
"alertId":"052652ac-5d1c-464a-812a-7dd18bbfba8c",
"source":"fili@ifountain.com",
"alias":"aliastest",
"tinyId":"23",
"createdAt":1420452191104,
"userId":"daed1180-0ce8-438b-8f8e-57e1a5920a2d",
"entity":""
},
"action":"TestAction",
"integrationId":"37c8f316-17c6-49d7-899b-9c7e540c048d",
"integrationName":"Integration1"
}

View File

@@ -0,0 +1,21 @@
{
"source":{
"name":"",
"type":"web"
},
"alert":{
"tinyId":"23",
"alias":"aliastest",
"entity":"",
"message":"test alert",
"updatedAt":1420452374669001603,
"alertId":"052652ac-5d1c-464a-812a-7dd18bbfba8c",
"username":"fili@ifountain.com",
"source":"fili@ifountain.com",
"createdAt":1420452191104,
"userId":"daed1180-0ce8-438b-8f8e-57e1a5920a2d"
},
"action":"Delete",
"integrationId":"37c8f316-17c6-49d7-899b-9c7e540c048d",
"integrationName":"Integration1"
}

View File

@@ -0,0 +1,21 @@
{
"escalationId":"51859f57-7fad-467b-ad79-59acbc69cb6a",
"headers":{
"foo":"bar"
},
"integrationName":"Webhook_Test",
"escalationNotify":{
"name":"test@ifountain.com",
"id":"64818849-71d6-40ce-87c6-ed5e588702fd",
"type":"default",
"entity":"user"
},
"integrationId":"868be72a-8015-432e-8b23-c1f7f4374baa",
"escalationName":"test_esc",
"alert":{
"alertId":"7ba97e3a-d328-4b5e-8f9a-39e945a3869a"
},
"escalationTime":0,
"action":"Escalate",
"repeatCount":0
}

View File

@@ -0,0 +1,25 @@
{
"source":{
"name":"",
"type":"web"
},
"alert":{
"updatedAt":1420452275002000962,
"tags": [
"tag1", "tag2"
],
"removedTags":"tag3",
"message":"test alert",
"username":"fili@ifountain.com",
"alertId":"052652ac-5d1c-464a-812a-7dd18bbfba8c",
"source":"fili@ifountain.com",
"alias":"aliastest",
"tinyId":"23",
"createdAt":1420452191104,
"userId":"daed1180-0ce8-438b-8f8e-57e1a5920a2d",
"entity":""
},
"action":"RemoveTags",
"integrationId":"37c8f316-17c6-49d7-899b-9c7e540c048d",
"integrationName":"Integration1"
}

View File

@@ -0,0 +1,25 @@
{
"integrationType":"Integration1",
"alert":{
"createdAt":1470226893192,
"tinyId":"47",
"alias":"8a745a79-3ed3-4044-8427-98e067c0623c",
"alertId":"8a745a79-3ed3-4044-8427-98e067c0623c",
"source":"test@test.com",
"message":"message test",
"userId":"ac6a9ab7-98fe-4256-8a0e-30dc082a55e7",
"entity":"",
"tags":[
"tag1","tag2"
],
"updatedAt":1470383477928000335,
"username":"test@test.com"
},
"integrationName":"Webhook",
"action":"TakeOwnership",
"integrationId":"fd8755c1-7a5e-4829-9ecc-8990e1a2eed3",
"source":{
"name":"",
"type":"web"
}
}

View File

@@ -0,0 +1,24 @@
{
"source":{
"name":"",
"type":"web"
},
"alert":{
"updatedAt":1420452224764002246,
"tags": [
"tag1", "tag2"
],
"message":"test alert",
"username":"fili@ifountain.com",
"alertId":"052652ac-5d1c-464a-812a-7dd18bbfba8c",
"source":"fili@ifountain.com",
"alias":"aliastest",
"tinyId":"23",
"createdAt":1420452191104,
"userId":"daed1180-0ce8-438b-8f8e-57e1a5920a2d",
"entity":""
},
"action":"UnAcknowledge",
"integrationId":"37c8f316-17c6-49d7-899b-9c7e540c048d",
"integrationName":"Integration1"
}

View File

@@ -0,0 +1,170 @@
# -*- coding: utf-8 -*-
from typing import Text
from zerver.lib.test_classes import WebhookTestCase
class OpsGenieHookTests(WebhookTestCase):
STREAM_NAME = 'opsgenie'
URL_TEMPLATE = "/api/v1/external/opsgenie?&api_key={api_key}"
FIXTURE_DIR_NAME = 'opsgenie'
def test_acknowledge_alert(self):
# type: () -> None
expected_subject = u"Integration1"
expected_message = (u"**OpsGenie: [Alert for Integration1.](https://app.opsgenie.com/alert/V2#/show/052652ac-5d1c-464a-812a-7dd18bbfba8c)**\n"
u"Type: *Acknowledge*\n"
u"Message: *test alert*\n"
u"`tag1` `tag2`"
)
self.send_and_test_stream_message('acknowledge', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def test_addnote_alert(self):
# type: () -> None
expected_subject = u"Integration1"
expected_message = (u"**OpsGenie: [Alert for Integration1.](https://app.opsgenie.com/alert/V2#/show/052652ac-5d1c-464a-812a-7dd18bbfba8c)**\n"
u"Type: *AddNote*\n"
u"Note: *note to test alert*\n"
u"Message: *test alert*\n"
u"`tag1` `tag2`"
)
self.send_and_test_stream_message('addnote', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def test_addrecipient_alert(self):
# type: () -> None
expected_subject = u"Integration1"
expected_message = (u"**OpsGenie: [Alert for Integration1.](https://app.opsgenie.com/alert/V2#/show/052652ac-5d1c-464a-812a-7dd18bbfba8c)**\n"
u"Type: *AddRecipient*\n"
u"Recipient: *team2_escalation*\n"
u"Message: *test alert*\n"
u"`tag1` `tag2`"
)
# use fixture named helloworld_hello
self.send_and_test_stream_message('addrecipient', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def test_addtags_alert(self):
# type: () -> None
expected_subject = u"Integration1"
expected_message = (u"**OpsGenie: [Alert for Integration1.](https://app.opsgenie.com/alert/V2#/show/052652ac-5d1c-464a-812a-7dd18bbfba8c)**\n"
u"Type: *AddTags*\n"
u"Added tags: *tag1,tag2,tag3*\n"
u"Message: *test alert*\n"
u"`tag1` `tag2` `tag3`"
)
self.send_and_test_stream_message('addtags', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def test_addteam_alert(self):
# type: () -> None
expected_subject = u"Integration1"
expected_message = (u"**OpsGenie: [Alert for Integration1.](https://app.opsgenie.com/alert/V2#/show/052652ac-5d1c-464a-812a-7dd18bbfba8c)**\n"
u"Type: *AddTeam*\n"
u"Added team: *team2*\n"
u"Message: *test alert*\n"
u"`tag1` `tag2`"
)
self.send_and_test_stream_message('addteam', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def test_assignownership_alert(self):
# type: () -> None
expected_subject = u"Integration1"
expected_message = (u"**OpsGenie: [Alert for Integration1.](https://app.opsgenie.com/alert/V2#/show/052652ac-5d1c-464a-812a-7dd18bbfba8c)**\n"
u"Type: *AssignOwnership*\n"
u"Assigned owner: *user2@ifountain.com*\n"
u"Message: *test alert*\n"
u"`tag1` `tag2`"
)
self.send_and_test_stream_message('assignownership', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def test_close_alert(self):
# type: () -> None
expected_subject = u"Integration1"
expected_message = (u"**OpsGenie: [Alert for Integration1.](https://app.opsgenie.com/alert/V2#/show/052652ac-5d1c-464a-812a-7dd18bbfba8c)**\n"
u"Type: *Close*\n"
u"Message: *test alert*"
)
self.send_and_test_stream_message('close', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def test_create_alert(self):
# type: () -> None
expected_subject = u"Webhook"
expected_message = (u"**OpsGenie: [Alert for Webhook.](https://app.opsgenie.com/alert/V2#/show/ec03dad6-62c8-4c94-b38b-d88f398e900f)**\n"
u"Type: *Create*\n"
u"Message: *another alert*\n"
u"`vip`"
)
self.send_and_test_stream_message('create', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def test_customaction_alert(self):
# type: () -> None
expected_subject = u"Integration1"
expected_message = (u"**OpsGenie: [Alert for Integration1.](https://app.opsgenie.com/alert/V2#/show/052652ac-5d1c-464a-812a-7dd18bbfba8c)**\n"
u"Type: *TestAction*\n"
u"Message: *test alert*\n"
u"`tag1` `tag2`"
)
self.send_and_test_stream_message('customaction', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def test_delete_alert(self):
# type: () -> None
expected_subject = u"Integration1"
expected_message = (u"**OpsGenie: [Alert for Integration1.](https://app.opsgenie.com/alert/V2#/show/052652ac-5d1c-464a-812a-7dd18bbfba8c)**\n"
u"Type: *Delete*\n"
u"Message: *test alert*"
)
self.send_and_test_stream_message('delete', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def test_escalate_alert(self):
# type: () -> None
expected_subject = u"Webhook_Test"
expected_message = (u"**OpsGenie: [Alert for Webhook_Test.](https://app.opsgenie.com/alert/V2#/show/7ba97e3a-d328-4b5e-8f9a-39e945a3869a)**\n"
u"Type: *Escalate*\n"
u"Escalation: *test_esc*"
)
self.send_and_test_stream_message('escalate', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def test_removetags_alert(self):
# type: () -> None
expected_subject = u"Integration1"
expected_message = (u"**OpsGenie: [Alert for Integration1.](https://app.opsgenie.com/alert/V2#/show/052652ac-5d1c-464a-812a-7dd18bbfba8c)**\n"
u"Type: *RemoveTags*\n"
u"Removed tags: *tag3*\n"
u"Message: *test alert*\n"
u"`tag1` `tag2`"
)
self.send_and_test_stream_message('removetags', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def test_takeownership_alert(self):
# type: () -> None
expected_subject = u"Webhook"
expected_message = (u"**OpsGenie: [Alert for Webhook.](https://app.opsgenie.com/alert/V2#/show/8a745a79-3ed3-4044-8427-98e067c0623c)**\n"
u"Type: *TakeOwnership*\n"
u"Message: *message test*\n"
u"`tag1` `tag2`"
)
self.send_and_test_stream_message('takeownership', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def test_unacknowledge_alert(self):
# type: () -> None
expected_subject = u"Integration1"
expected_message = (u"**OpsGenie: [Alert for Integration1.](https://app.opsgenie.com/alert/V2#/show/052652ac-5d1c-464a-812a-7dd18bbfba8c)**\n"
u"Type: *UnAcknowledge*\n"
u"Message: *test alert*\n"
u"`tag1` `tag2`"
)
self.send_and_test_stream_message('unacknowledge', expected_subject, expected_message,
content_type="application/x-www-form-urlencoded")
def get_body(self, fixture_name):
# type: (Text) -> Text
return self.fixture_data("opsgenie", fixture_name, file_type="json")

View File

@@ -0,0 +1,55 @@
from __future__ import absolute_import
from django.utils.translation import ugettext as _
from zerver.lib.actions import check_send_message
from zerver.lib.response import json_success, json_error
from zerver.decorator import REQ, has_request_variables, api_key_only_webhook_view
from zerver.lib.validator import check_dict, check_string
from zerver.models import UserProfile
from django.http import HttpRequest, HttpResponse
from typing import Dict, Any, Iterable, Optional, Text
@api_key_only_webhook_view('OpsGenie')
@has_request_variables
def api_opsgenie_webhook(request, user_profile,
payload=REQ(argument_type='body'),
stream=REQ(default='opsgenie')):
# type: (HttpRequest, UserProfile, Dict[str, Any], Text) -> HttpResponse
# construct the body of the message
info = {"additional_info": '',
"alert_type": payload['action'],
"alert_id": payload['alert']['alertId'],
"integration_name": payload['integrationName'],
"tags": ' '.join(['`' + tag + '`' for tag in payload['alert'].get('tags', [])]),
}
topic = info['integration_name']
if 'note' in payload['alert']:
info['additional_info'] += "Note: *{}*\n".format(payload['alert']['note'])
if 'recipient' in payload['alert']:
info['additional_info'] += "Recipient: *{}*\n".format(payload['alert']['recipient'])
if 'addedTags' in payload['alert']:
info['additional_info'] += "Added tags: *{}*\n".format(payload['alert']['addedTags'])
if 'team' in payload['alert']:
info['additional_info'] += "Added team: *{}*\n".format(payload['alert']['team'])
if 'owner' in payload['alert']:
info['additional_info'] += "Assigned owner: *{}*\n".format(payload['alert']['owner'])
if 'escalationName' in payload:
info['additional_info'] += "Escalation: *{}*\n".format(payload['escalationName'])
if 'removedTags' in payload['alert']:
info['additional_info'] += "Removed tags: *{}*\n".format(payload['alert']['removedTags'])
if 'message' in payload['alert']:
info['additional_info'] += "Message: *{}*\n".format(payload['alert']['message'])
body = ''
try:
body_template = "**OpsGenie: [Alert for {integration_name}.](https://app.opsgenie.com/alert/V2#/show/{alert_id})**\n" \
"Type: *{alert_type}*\n" \
"{additional_info}" \
"{tags}"
body += body_template.format(**info)
except KeyError as e:
return json_error(_("Missing key {} in JSON").format(str(e)))
# send the message
check_send_message(user_profile, request.client, 'stream', [stream], topic, body)
return json_success()