Outgoing webhook System: first Iteration of outgoing webhook UI.

This commit is contained in:
vaibhav 2017-06-10 22:13:31 +05:30 committed by showell
parent f32c1892ff
commit 33c0c00cd6
7 changed files with 92 additions and 4 deletions

View File

@ -75,6 +75,8 @@ exports.set_up = function () {
$("#get_api_key_box").hide();
$("#show_api_key_box").hide();
$("#api_key_button_box").show();
$('#payload_url_inputbox').hide();
$('#create_payload_url').val('');
$('#api_key_button').click(function () {
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 OUTGOING_WEBHOOK_BOT_TYPE = '3';
var GENERIC_BOT_TYPE = '1';
$('#create_bot_form').validate({
errorClass: 'text-error',
@ -129,12 +133,18 @@ exports.set_up = function () {
var bot_type = $('#create_bot_type :selected').val();
var full_name = $('#create_bot_name').val();
var short_name = $('#create_bot_short_name').val() || $('#create_bot_short_name').text();
var payload_url = $('#create_payload_url').val();
var formData = new FormData();
formData.append('csrfmiddlewaretoken', csrf_token);
formData.append('bot_type', bot_type);
formData.append('full_name', full_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) {
formData.append('file-'+i, file);
});
@ -149,6 +159,9 @@ exports.set_up = function () {
$('#bot_table_error').hide();
$('#create_bot_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_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) {
var email = $(e.currentTarget).data('email');
channel.del({

View File

@ -1212,3 +1212,7 @@ thead .actions {
.required-text.thick:empty::after {
width: 200%;
}
#payload_url_inputbox input[type=text] {
width: 340px;
}

View File

@ -33,6 +33,7 @@
<select name="bot_type" id="create_bot_type">
<option value="1">{{t "Generic bot" }}</option>
<option value="2">{{t "Incoming webhook" }}</option>
<option value="3">{{t "Outgoing webhook" }}</option>
</select>
</div>
<div class="input-group">
@ -50,6 +51,12 @@
<label for="create_bot_short_name" generated="true" class="text-error"></label>
</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 id="bot_avatar_file"></div>
<input type="file" name="bot_avatar_file_input" class="notvisible" id="bot_avatar_file_input" value="{{t 'Upload avatar' }}" />

View File

@ -157,6 +157,8 @@ def build_custom_checkers(by_lang):
"return json_error(_(\"Email '%(email)s' not allowed for realm '%(realm)s'\") %"),
('zproject/settings.py',
"'format': '%(asctime)s %(levelname)-8s %(message)s'"),
('static/templates/settings/bot-settings.handlebars',
"'https://hostname.example.com/bots/followup'"),
]),
'description': 'Missing space around "%"'},
# This rule is constructed with + to avoid triggering on itself

View File

@ -528,6 +528,7 @@ class UserProfile(ModelReprMixin, AbstractBaseUser, PermissionsMixin):
since they can't be used to read messages.
"""
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
"""
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 = [
DEFAULT_BOT,
INCOMING_WEBHOOK_BOT,
OUTGOING_WEBHOOK_BOT,
]
SERVICE_BOT_TYPES = [

View File

@ -13,7 +13,7 @@ from typing import Any, Dict, List
from zerver.lib.actions import do_change_stream_invite_only
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_helpers import (
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)
self.assert_json_error(result, 'No such user')
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.")

View File

@ -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.streams import access_stream_by_name
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.utils import generate_random_token
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):
@ -229,13 +230,23 @@ def regenerate_bot_api_key(request, user_profile, email):
)
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
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),
payload_url=REQ(validator=check_url, 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_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"
full_name = check_full_name(full_name_raw)
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:
user_file = list(request.FILES.values())[0]
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(
api_key=bot_profile.api_key,
avatar_url=avatar_url(bot_profile),