mirror of
https://github.com/zulip/zulip.git
synced 2025-11-06 06:53:25 +00:00
devtools: Add custom HTTP headers support to the integrations dev panel.
This commit introduces a simple field where the user can now specify custom HTTP headers. This commit does not introduce an improved system for storing HTTP headers as fixtures - such a change would modify both the existing unit tests as well as this devtool.
This commit is contained in:
committed by
Tim Abbott
parent
64b4fd5923
commit
2bd9c8cb42
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 30 KiB |
@@ -17,6 +17,7 @@ var clear_handlers = {
|
|||||||
integration_name: function () { $('#integration_name').children()[0].selected = true; },
|
integration_name: function () { $('#integration_name').children()[0].selected = true; },
|
||||||
fixture_name: function () { $('#fixture_name').empty(); },
|
fixture_name: function () { $('#fixture_name').empty(); },
|
||||||
fixture_body: function () { $("#fixture_body")[0].value = ""; },
|
fixture_body: function () { $("#fixture_body")[0].value = ""; },
|
||||||
|
custom_http_headers: function () { $("#custom_http_headers")[0].value = ""; },
|
||||||
};
|
};
|
||||||
|
|
||||||
function clear_elements(elements) {
|
function clear_elements(elements) {
|
||||||
@@ -141,7 +142,7 @@ function get_fixtures(integration_name) {
|
|||||||
/* Request fixtures from the backend for any integrations that we don't already have fixtures
|
/* Request fixtures from the backend for any integrations that we don't already have fixtures
|
||||||
for (which would be stored in the JS variable called "loaded_fixtures"). */
|
for (which would be stored in the JS variable called "loaded_fixtures"). */
|
||||||
if (integration_name === "") {
|
if (integration_name === "") {
|
||||||
clear_elements(["fixture_body", "fixture_name", "URL", "message"]);
|
clear_elements(["custom_http_headers", "fixture_body", "fixture_name", "URL", "message"]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,9 +191,21 @@ function send_webhook_fixture_message() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var custom_headers = $("#custom_http_headers").val();
|
||||||
|
if (custom_headers !== "") {
|
||||||
|
// JSON.parse("") would trigger an error, as empty strings do not qualify as JSON.
|
||||||
|
try {
|
||||||
|
// Let JavaScript validate the JSON for us.
|
||||||
|
custom_headers = JSON.stringify(JSON.parse(custom_headers));
|
||||||
|
} catch (err) {
|
||||||
|
set_message("Custom HTTP headers are not in a valid JSON format.", "warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
channel.post({
|
channel.post({
|
||||||
url: "/devtools/integrations/check_send_webhook_fixture_message",
|
url: "/devtools/integrations/check_send_webhook_fixture_message",
|
||||||
data: {url: url, body: body},
|
data: {url: url, body: body, custom_headers: custom_headers},
|
||||||
beforeSend: function (xhr) {xhr.setRequestHeader('X-CSRFToken', csrftoken);},
|
beforeSend: function (xhr) {xhr.setRequestHeader('X-CSRFToken', csrftoken);},
|
||||||
success: function () {
|
success: function () {
|
||||||
// If the previous fixture body was sent successfully, then we should change the success
|
// If the previous fixture body was sent successfully, then we should change the success
|
||||||
@@ -214,7 +227,7 @@ function send_webhook_fixture_message() {
|
|||||||
// Initialization
|
// Initialization
|
||||||
$(function () {
|
$(function () {
|
||||||
clear_elements(["stream_name", "topic_name", "URL", "bot_name", "integration_name",
|
clear_elements(["stream_name", "topic_name", "URL", "bot_name", "integration_name",
|
||||||
"fixture_name", "fixture_body", "message"]);
|
"fixture_name", "custom_http_headers", "fixture_body", "message"]);
|
||||||
|
|
||||||
var potential_default_bot = $("#bot_name")[0][1];
|
var potential_default_bot = $("#bot_name")[0][1];
|
||||||
if (potential_default_bot !== undefined) {
|
if (potential_default_bot !== undefined) {
|
||||||
@@ -222,7 +235,7 @@ $(function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$('#integration_name').change(function () {
|
$('#integration_name').change(function () {
|
||||||
clear_elements(["fixture_body", "fixture_name", "message"]);
|
clear_elements(["custom_http_headers", "fixture_body", "fixture_name", "message"]);
|
||||||
var integration_name = $(this).children("option:selected").val();
|
var integration_name = $(this).children("option:selected").val();
|
||||||
get_fixtures(integration_name);
|
get_fixtures(integration_name);
|
||||||
update_url();
|
update_url();
|
||||||
|
|||||||
@@ -7,6 +7,11 @@
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#custom_http_headers {
|
||||||
|
height: 200px;
|
||||||
|
width: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
#fixture_body {
|
#fixture_body {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
width: 700px;
|
width: 700px;
|
||||||
|
|||||||
@@ -238,16 +238,18 @@ This is the GUI tool.
|
|||||||
1. Run `./tools/run-dev.py` then go to http://localhost:9991/devtools/integrations/.
|
1. Run `./tools/run-dev.py` then go to http://localhost:9991/devtools/integrations/.
|
||||||
|
|
||||||
2. Set the following mandatory fields:
|
2. Set the following mandatory fields:
|
||||||
- **Bot** - Any incoming webhook bot.
|
**Bot** - Any incoming webhook bot.
|
||||||
- **Integration** - One of the integrations.
|
**Integration** - One of the integrations.
|
||||||
- **Fixture** - Though not mandatory, it's recommended that you select one and then tweak it if necessary.
|
**Fixture** - Though not mandatory, it's recommended that you select one and then tweak it if necessary.
|
||||||
The remaining fields are optional, and the URL will automatically be generated.
|
The remaining fields are optional, and the URL will automatically be generated.
|
||||||
|
|
||||||
3. Click **Send**!
|
3. Click **Send**!
|
||||||
|
|
||||||
By opening Zulip in one tab and this tool in another, you can quickly tweak
|
By opening Zulip in one tab and then this tool in another, you can quickly tweak
|
||||||
your code and send sample messages for many different test fixtures.
|
your code and send sample messages for many different test fixtures.
|
||||||
|
|
||||||
|
Note: Custom HTTP Headers must be entered as a JSON dictionary, if you want to use any in the first place that is.
|
||||||
|
Feel free to use 4-spaces as tabs for indentation if you'd like!
|
||||||
|
|
||||||
Your sample notification may look like:
|
Your sample notification may look like:
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,15 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="center-text pad-top"><b>Custom HTTP Headers</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="form-group">
|
||||||
|
<textarea id="custom_http_headers" class="form-control"></textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2" class="center-text pad-top"><b>JSON Body</b></td>
|
<td colspan="2" class="center-text pad-top"><b>JSON Body</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ class TestIntegrationsDevPanel(ZulipTestCase):
|
|||||||
|
|
||||||
data = {
|
data = {
|
||||||
"url": url,
|
"url": url,
|
||||||
"body": body
|
"body": body,
|
||||||
|
"custom_headers": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client_post(target_url, data)
|
response = self.client_post(target_url, data)
|
||||||
@@ -24,7 +25,7 @@ class TestIntegrationsDevPanel(ZulipTestCase):
|
|||||||
expected_response = {"result": "error", "msg": "Internal server error"}
|
expected_response = {"result": "error", "msg": "Internal server error"}
|
||||||
self.assertEqual(ujson.loads(response.content), expected_response)
|
self.assertEqual(ujson.loads(response.content), expected_response)
|
||||||
|
|
||||||
def test_check_send_webhook_fixture_message_for_success(self) -> None:
|
def test_check_send_webhook_fixture_message_for_success_without_headers(self) -> None:
|
||||||
bot = get_user('webhook-bot@zulip.com', self.zulip_realm)
|
bot = get_user('webhook-bot@zulip.com', self.zulip_realm)
|
||||||
url = "/api/v1/external/airbrake?api_key={key}&stream=Denmark&topic=Airbrake Notifications".format(key=bot.api_key)
|
url = "/api/v1/external/airbrake?api_key={key}&stream=Denmark&topic=Airbrake Notifications".format(key=bot.api_key)
|
||||||
target_url = "/devtools/integrations/check_send_webhook_fixture_message"
|
target_url = "/devtools/integrations/check_send_webhook_fixture_message"
|
||||||
@@ -34,6 +35,7 @@ class TestIntegrationsDevPanel(ZulipTestCase):
|
|||||||
data = {
|
data = {
|
||||||
"url": url,
|
"url": url,
|
||||||
"body": body,
|
"body": body,
|
||||||
|
"custom_headers": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client_post(target_url, data)
|
response = self.client_post(target_url, data)
|
||||||
@@ -45,6 +47,28 @@ class TestIntegrationsDevPanel(ZulipTestCase):
|
|||||||
self.assertEqual(Stream.objects.get(id=latest_msg.recipient.type_id).name, "Denmark")
|
self.assertEqual(Stream.objects.get(id=latest_msg.recipient.type_id).name, "Denmark")
|
||||||
self.assertEqual(latest_msg.topic_name(), "Airbrake Notifications")
|
self.assertEqual(latest_msg.topic_name(), "Airbrake Notifications")
|
||||||
|
|
||||||
|
def test_check_send_webhook_fixture_message_for_success_with_headers(self) -> None:
|
||||||
|
bot = get_user('webhook-bot@zulip.com', self.zulip_realm)
|
||||||
|
url = "/api/v1/external/github?api_key={key}&stream=Denmark&topic=GitHub Notifications".format(key=bot.api_key)
|
||||||
|
target_url = "/devtools/integrations/check_send_webhook_fixture_message"
|
||||||
|
with open("zerver/webhooks/github/fixtures/ping_organization.json", "r") as f:
|
||||||
|
body = f.read()
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"url": url,
|
||||||
|
"body": body,
|
||||||
|
"custom_headers": ujson.dumps({"X_GITHUB_EVENT": "ping"}),
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client_post(target_url, data)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
latest_msg = Message.objects.latest('id')
|
||||||
|
expected_message = "GitHub webhook has been successfully configured by eeshangarg."
|
||||||
|
self.assertEqual(latest_msg.content, expected_message)
|
||||||
|
self.assertEqual(Stream.objects.get(id=latest_msg.recipient.type_id).name, "Denmark")
|
||||||
|
self.assertEqual(latest_msg.topic_name(), "GitHub Notifications")
|
||||||
|
|
||||||
def test_get_fixtures_for_nonexistant_integration(self) -> None:
|
def test_get_fixtures_for_nonexistant_integration(self) -> None:
|
||||||
target_url = "/devtools/integrations/somerandomnonexistantintegration/fixtures"
|
target_url = "/devtools/integrations/somerandomnonexistantintegration/fixtures"
|
||||||
response = self.client_get(target_url)
|
response = self.client_get(target_url)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from zerver.lib.integrations import WEBHOOK_INTEGRATIONS
|
|||||||
from zerver.lib.request import has_request_variables, REQ
|
from zerver.lib.request import has_request_variables, REQ
|
||||||
from zerver.lib.response import json_success, json_error
|
from zerver.lib.response import json_success, json_error
|
||||||
from zerver.models import UserProfile, get_realm
|
from zerver.models import UserProfile, get_realm
|
||||||
|
from zerver.management.commands.send_webhook_fixture_message import parse_headers
|
||||||
|
|
||||||
|
|
||||||
ZULIP_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../../')
|
ZULIP_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../../')
|
||||||
@@ -58,10 +59,19 @@ def get_fixtures(request: HttpResponse,
|
|||||||
@has_request_variables
|
@has_request_variables
|
||||||
def check_send_webhook_fixture_message(request: HttpRequest,
|
def check_send_webhook_fixture_message(request: HttpRequest,
|
||||||
url: str=REQ(),
|
url: str=REQ(),
|
||||||
body: str=REQ()) -> HttpResponse:
|
body: str=REQ(),
|
||||||
|
custom_headers: str=REQ()) -> HttpResponse:
|
||||||
client = Client()
|
client = Client()
|
||||||
realm = get_realm("zulip")
|
realm = get_realm("zulip")
|
||||||
response = client.post(url, body, content_type="application/json", HTTP_HOST=realm.host)
|
try:
|
||||||
|
headers = parse_headers(custom_headers)
|
||||||
|
except ValueError as ve:
|
||||||
|
return json_error("Custom HTTP headers are not in a valid JSON format. {}".format(ve)) # nolint
|
||||||
|
if not headers:
|
||||||
|
headers = {}
|
||||||
|
http_host = headers.pop("HTTP_HOST", realm.host)
|
||||||
|
content_type = headers.pop("HTTP_CONTENT_TYPE", "application/json")
|
||||||
|
response = client.post(url, body, content_type=content_type, HTTP_HOST=http_host, **headers)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return json_success()
|
return json_success()
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user