mirror of
https://github.com/zulip/zulip.git
synced 2025-11-06 23:13:25 +00:00
Outgoing webhook System: first Iteration of outgoing webhook UI.
This commit is contained in:
@@ -75,6 +75,8 @@ exports.set_up = function () {
|
|||||||
$("#get_api_key_box").hide();
|
$("#get_api_key_box").hide();
|
||||||
$("#show_api_key_box").hide();
|
$("#show_api_key_box").hide();
|
||||||
$("#api_key_button_box").show();
|
$("#api_key_button_box").show();
|
||||||
|
$('#payload_url_inputbox').hide();
|
||||||
|
$('#create_payload_url').val('');
|
||||||
|
|
||||||
$('#api_key_button').click(function () {
|
$('#api_key_button').click(function () {
|
||||||
if (page_params.realm_password_auth_enabled !== false) {
|
if (page_params.realm_password_auth_enabled !== false) {
|
||||||
@@ -119,6 +121,8 @@ exports.set_up = function () {
|
|||||||
|
|
||||||
|
|
||||||
var create_avatar_widget = avatar.build_bot_create_widget();
|
var create_avatar_widget = avatar.build_bot_create_widget();
|
||||||
|
var OUTGOING_WEBHOOK_BOT_TYPE = '3';
|
||||||
|
var GENERIC_BOT_TYPE = '1';
|
||||||
|
|
||||||
$('#create_bot_form').validate({
|
$('#create_bot_form').validate({
|
||||||
errorClass: 'text-error',
|
errorClass: 'text-error',
|
||||||
@@ -129,12 +133,18 @@ exports.set_up = function () {
|
|||||||
var bot_type = $('#create_bot_type :selected').val();
|
var bot_type = $('#create_bot_type :selected').val();
|
||||||
var full_name = $('#create_bot_name').val();
|
var full_name = $('#create_bot_name').val();
|
||||||
var short_name = $('#create_bot_short_name').val() || $('#create_bot_short_name').text();
|
var short_name = $('#create_bot_short_name').val() || $('#create_bot_short_name').text();
|
||||||
|
var payload_url = $('#create_payload_url').val();
|
||||||
var formData = new FormData();
|
var formData = new FormData();
|
||||||
|
|
||||||
formData.append('csrfmiddlewaretoken', csrf_token);
|
formData.append('csrfmiddlewaretoken', csrf_token);
|
||||||
formData.append('bot_type', bot_type);
|
formData.append('bot_type', bot_type);
|
||||||
formData.append('full_name', full_name);
|
formData.append('full_name', full_name);
|
||||||
formData.append('short_name', short_name);
|
formData.append('short_name', short_name);
|
||||||
|
|
||||||
|
// If the selected bot_type is Outgoing webhook
|
||||||
|
if (bot_type === OUTGOING_WEBHOOK_BOT_TYPE) {
|
||||||
|
formData.append('payload_url', JSON.stringify(payload_url));
|
||||||
|
}
|
||||||
jQuery.each($('#bot_avatar_file_input')[0].files, function (i, file) {
|
jQuery.each($('#bot_avatar_file_input')[0].files, function (i, file) {
|
||||||
formData.append('file-'+i, file);
|
formData.append('file-'+i, file);
|
||||||
});
|
});
|
||||||
@@ -149,6 +159,9 @@ exports.set_up = function () {
|
|||||||
$('#bot_table_error').hide();
|
$('#bot_table_error').hide();
|
||||||
$('#create_bot_name').val('');
|
$('#create_bot_name').val('');
|
||||||
$('#create_bot_short_name').val('');
|
$('#create_bot_short_name').val('');
|
||||||
|
$('#create_payload_url').val('');
|
||||||
|
$('#payload_url_inputbox').hide();
|
||||||
|
$('#create_bot_type').val(GENERIC_BOT_TYPE);
|
||||||
$('#create_bot_button').show();
|
$('#create_bot_button').show();
|
||||||
create_avatar_widget.clear();
|
create_avatar_widget.clear();
|
||||||
},
|
},
|
||||||
@@ -162,6 +175,18 @@ exports.set_up = function () {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#create_bot_type").on("change", function () {
|
||||||
|
var bot_type = $('#create_bot_type :selected').val();
|
||||||
|
// If the selected bot_type is Outgoing webhook
|
||||||
|
if (bot_type === OUTGOING_WEBHOOK_BOT_TYPE) {
|
||||||
|
$('#payload_url_inputbox').show();
|
||||||
|
$('#create_payload_url').addClass('required');
|
||||||
|
} else {
|
||||||
|
$('#payload_url_inputbox').hide();
|
||||||
|
$('#create_payload_url').removeClass('required');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$("#active_bots_list").on("click", "button.delete_bot", function (e) {
|
$("#active_bots_list").on("click", "button.delete_bot", function (e) {
|
||||||
var email = $(e.currentTarget).data('email');
|
var email = $(e.currentTarget).data('email');
|
||||||
channel.del({
|
channel.del({
|
||||||
|
|||||||
@@ -1212,3 +1212,7 @@ thead .actions {
|
|||||||
.required-text.thick:empty::after {
|
.required-text.thick:empty::after {
|
||||||
width: 200%;
|
width: 200%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#payload_url_inputbox input[type=text] {
|
||||||
|
width: 340px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
<select name="bot_type" id="create_bot_type">
|
<select name="bot_type" id="create_bot_type">
|
||||||
<option value="1">{{t "Generic bot" }}</option>
|
<option value="1">{{t "Generic bot" }}</option>
|
||||||
<option value="2">{{t "Incoming webhook" }}</option>
|
<option value="2">{{t "Incoming webhook" }}</option>
|
||||||
|
<option value="3">{{t "Outgoing webhook" }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -50,6 +51,12 @@
|
|||||||
<label for="create_bot_short_name" generated="true" class="text-error"></label>
|
<label for="create_bot_short_name" generated="true" class="text-error"></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="input-group" id="payload_url_inputbox">
|
||||||
|
<label for="create_payload_url">{{t "Outgoing webhook service base URL" }}</label>
|
||||||
|
<input type="text" name="payload_url" id="create_payload_url"
|
||||||
|
maxlength=100 placeholder="https://hostname.example.com/bots/followup" value="" />
|
||||||
|
<div><label for="create_payload_url" generated="true" class="text-error"></label></div>
|
||||||
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div id="bot_avatar_file"></div>
|
<div id="bot_avatar_file"></div>
|
||||||
<input type="file" name="bot_avatar_file_input" class="notvisible" id="bot_avatar_file_input" value="{{t 'Upload avatar' }}" />
|
<input type="file" name="bot_avatar_file_input" class="notvisible" id="bot_avatar_file_input" value="{{t 'Upload avatar' }}" />
|
||||||
|
|||||||
@@ -157,6 +157,8 @@ def build_custom_checkers(by_lang):
|
|||||||
"return json_error(_(\"Email '%(email)s' not allowed for realm '%(realm)s'\") %"),
|
"return json_error(_(\"Email '%(email)s' not allowed for realm '%(realm)s'\") %"),
|
||||||
('zproject/settings.py',
|
('zproject/settings.py',
|
||||||
"'format': '%(asctime)s %(levelname)-8s %(message)s'"),
|
"'format': '%(asctime)s %(levelname)-8s %(message)s'"),
|
||||||
|
('static/templates/settings/bot-settings.handlebars',
|
||||||
|
"'https://hostname.example.com/bots/followup'"),
|
||||||
]),
|
]),
|
||||||
'description': 'Missing space around "%"'},
|
'description': 'Missing space around "%"'},
|
||||||
# This rule is constructed with + to avoid triggering on itself
|
# This rule is constructed with + to avoid triggering on itself
|
||||||
|
|||||||
@@ -528,6 +528,7 @@ class UserProfile(ModelReprMixin, AbstractBaseUser, PermissionsMixin):
|
|||||||
since they can't be used to read messages.
|
since they can't be used to read messages.
|
||||||
"""
|
"""
|
||||||
INCOMING_WEBHOOK_BOT = 2
|
INCOMING_WEBHOOK_BOT = 2
|
||||||
|
# This value is also being used in static/js/settings_bots.js. On updating it here, update it there as well.
|
||||||
OUTGOING_WEBHOOK_BOT = 3
|
OUTGOING_WEBHOOK_BOT = 3
|
||||||
"""
|
"""
|
||||||
Embedded bots run within the Zulip server itself; events are added to the
|
Embedded bots run within the Zulip server itself; events are added to the
|
||||||
@@ -539,6 +540,7 @@ class UserProfile(ModelReprMixin, AbstractBaseUser, PermissionsMixin):
|
|||||||
ALLOWED_BOT_TYPES = [
|
ALLOWED_BOT_TYPES = [
|
||||||
DEFAULT_BOT,
|
DEFAULT_BOT,
|
||||||
INCOMING_WEBHOOK_BOT,
|
INCOMING_WEBHOOK_BOT,
|
||||||
|
OUTGOING_WEBHOOK_BOT,
|
||||||
]
|
]
|
||||||
|
|
||||||
SERVICE_BOT_TYPES = [
|
SERVICE_BOT_TYPES = [
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from typing import Any, Dict, List
|
|||||||
|
|
||||||
from zerver.lib.actions import do_change_stream_invite_only
|
from zerver.lib.actions import do_change_stream_invite_only
|
||||||
from zerver.models import get_realm, get_stream, \
|
from zerver.models import get_realm, get_stream, \
|
||||||
Realm, Stream, UserProfile, get_user
|
Realm, Stream, UserProfile, get_user, get_bot_services
|
||||||
from zerver.lib.test_classes import ZulipTestCase, UploadSerializeMixin
|
from zerver.lib.test_classes import ZulipTestCase, UploadSerializeMixin
|
||||||
from zerver.lib.test_helpers import (
|
from zerver.lib.test_helpers import (
|
||||||
avatar_disk_path, get_test_image_file, tornado_redirected_to_list,
|
avatar_disk_path, get_test_image_file, tornado_redirected_to_list,
|
||||||
@@ -911,3 +911,32 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
|
|||||||
result = self.client_patch("/json/bots/nonexistent-bot@zulip.com", bot_info)
|
result = self.client_patch("/json/bots/nonexistent-bot@zulip.com", bot_info)
|
||||||
self.assert_json_error(result, 'No such user')
|
self.assert_json_error(result, 'No such user')
|
||||||
self.assert_num_bots_equal(1)
|
self.assert_num_bots_equal(1)
|
||||||
|
|
||||||
|
def test_create_outgoing_webhook_bot(self, **extras):
|
||||||
|
# type: (**Any) -> None
|
||||||
|
self.login(self.example_email('hamlet'))
|
||||||
|
bot_info = {
|
||||||
|
'full_name': 'Outgoing Webhook test bot',
|
||||||
|
'short_name': 'outgoingservicebot',
|
||||||
|
'bot_type': UserProfile.OUTGOING_WEBHOOK_BOT,
|
||||||
|
'payload_url': ujson.dumps('http://127.0.0.1:5002/bots/followup'),
|
||||||
|
}
|
||||||
|
bot_info.update(extras)
|
||||||
|
result = self.client_post("/json/bots", bot_info)
|
||||||
|
self.assert_json_success(result)
|
||||||
|
|
||||||
|
bot_email = "outgoingservicebot-bot@zulip.testserver"
|
||||||
|
bot_realm = get_realm('zulip')
|
||||||
|
bot = get_user(bot_email, bot_realm)
|
||||||
|
services = get_bot_services(bot.id)
|
||||||
|
service = services[0]
|
||||||
|
|
||||||
|
self.assertEqual(len(services), 1)
|
||||||
|
self.assertEqual(service.name, "outgoingservicebot")
|
||||||
|
self.assertEqual(service.base_url, "http://127.0.0.1:5002/bots/followup")
|
||||||
|
self.assertEqual(service.user_profile, bot)
|
||||||
|
|
||||||
|
# invalid URL test case.
|
||||||
|
bot_info['payload_url'] = ujson.dumps('http://127.0.0.:5002/bots/followup')
|
||||||
|
result = self.client_post("/json/bots", bot_info)
|
||||||
|
self.assert_json_error(result, "Enter a valid URL.")
|
||||||
|
|||||||
@@ -22,11 +22,12 @@ from zerver.lib.avatar import avatar_url, get_avatar_url
|
|||||||
from zerver.lib.response import json_error, json_success
|
from zerver.lib.response import json_error, json_success
|
||||||
from zerver.lib.streams import access_stream_by_name
|
from zerver.lib.streams import access_stream_by_name
|
||||||
from zerver.lib.upload import upload_avatar_image
|
from zerver.lib.upload import upload_avatar_image
|
||||||
from zerver.lib.validator import check_bool, check_string, check_int
|
from zerver.lib.validator import check_bool, check_string, check_int, check_url
|
||||||
from zerver.lib.users import check_valid_bot_type, check_change_full_name, check_full_name
|
from zerver.lib.users import check_valid_bot_type, check_change_full_name, check_full_name
|
||||||
from zerver.lib.utils import generate_random_token
|
from zerver.lib.utils import generate_random_token
|
||||||
from zerver.models import UserProfile, Stream, Realm, Message, get_user_profile_by_email, \
|
from zerver.models import UserProfile, Stream, Realm, Message, get_user_profile_by_email, \
|
||||||
email_allowed_for_realm, get_user_profile_by_id, get_user
|
email_allowed_for_realm, get_user_profile_by_id, get_user, Service
|
||||||
|
from zerver.lib.create_user import random_api_key
|
||||||
|
|
||||||
|
|
||||||
def deactivate_user_backend(request, user_profile, email):
|
def deactivate_user_backend(request, user_profile, email):
|
||||||
@@ -229,13 +230,23 @@ def regenerate_bot_api_key(request, user_profile, email):
|
|||||||
)
|
)
|
||||||
return json_success(json_result)
|
return json_success(json_result)
|
||||||
|
|
||||||
|
def add_outgoing_webhook_service(name, user_profile, base_url, interface, token):
|
||||||
|
# type: (Text, UserProfile, Text, int, Text) -> None
|
||||||
|
Service.objects.create(name=name,
|
||||||
|
user_profile=user_profile,
|
||||||
|
base_url=base_url,
|
||||||
|
interface=interface,
|
||||||
|
token=token)
|
||||||
|
|
||||||
@has_request_variables
|
@has_request_variables
|
||||||
def add_bot_backend(request, user_profile, full_name_raw=REQ("full_name"), short_name=REQ(),
|
def add_bot_backend(request, user_profile, full_name_raw=REQ("full_name"), short_name=REQ(),
|
||||||
bot_type=REQ(validator=check_int, default=UserProfile.DEFAULT_BOT),
|
bot_type=REQ(validator=check_int, default=UserProfile.DEFAULT_BOT),
|
||||||
|
payload_url=REQ(validator=check_url, default=None),
|
||||||
default_sending_stream_name=REQ('default_sending_stream', default=None),
|
default_sending_stream_name=REQ('default_sending_stream', default=None),
|
||||||
default_events_register_stream_name=REQ('default_events_register_stream', default=None),
|
default_events_register_stream_name=REQ('default_events_register_stream', default=None),
|
||||||
default_all_public_streams=REQ(validator=check_bool, default=None)):
|
default_all_public_streams=REQ(validator=check_bool, default=None)):
|
||||||
# type: (HttpRequest, UserProfile, Text, Text, int, Optional[Text], Optional[Text], Optional[bool]) -> HttpResponse
|
# type: (HttpRequest, UserProfile, Text, Text, int, Optional[Text], Optional[Text], Optional[Text], Optional[bool]) -> HttpResponse
|
||||||
|
service_name = short_name
|
||||||
short_name += "-bot"
|
short_name += "-bot"
|
||||||
full_name = check_full_name(full_name_raw)
|
full_name = check_full_name(full_name_raw)
|
||||||
email = '%s@%s' % (short_name, user_profile.realm.get_bot_domain())
|
email = '%s@%s' % (short_name, user_profile.realm.get_bot_domain())
|
||||||
@@ -279,6 +290,14 @@ def add_bot_backend(request, user_profile, full_name_raw=REQ("full_name"), short
|
|||||||
if len(request.FILES) == 1:
|
if len(request.FILES) == 1:
|
||||||
user_file = list(request.FILES.values())[0]
|
user_file = list(request.FILES.values())[0]
|
||||||
upload_avatar_image(user_file, user_profile, bot_profile)
|
upload_avatar_image(user_file, user_profile, bot_profile)
|
||||||
|
|
||||||
|
if bot_type == UserProfile.OUTGOING_WEBHOOK_BOT:
|
||||||
|
add_outgoing_webhook_service(name=service_name,
|
||||||
|
user_profile=bot_profile,
|
||||||
|
base_url=payload_url,
|
||||||
|
interface=1,
|
||||||
|
token=random_api_key())
|
||||||
|
|
||||||
json_result = dict(
|
json_result = dict(
|
||||||
api_key=bot_profile.api_key,
|
api_key=bot_profile.api_key,
|
||||||
avatar_url=avatar_url(bot_profile),
|
avatar_url=avatar_url(bot_profile),
|
||||||
|
|||||||
Reference in New Issue
Block a user