mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	embedded bots: Add config data UI.
This adds UI fields in the bot settings for specifying configuration values like API keys for a bot. The names and placeholder values for each bot's config fields are fetched from the bot's <bot>.conf template file in the zulip_bots package. This also adds giphy and followup as embedded bots.
This commit is contained in:
		@@ -1,6 +1,9 @@
 | 
				
			|||||||
set_global("page_params", {
 | 
					set_global("page_params", {
 | 
				
			||||||
    realm_uri: "https://chat.example.com",
 | 
					    realm_uri: "https://chat.example.com",
 | 
				
			||||||
    realm_embedded_bots: ["converter", "xkcd"],
 | 
					    realm_embedded_bots: [{name: "converter", config: {}},
 | 
				
			||||||
 | 
					                          {name:"giphy", config: {key: "12345678"}},
 | 
				
			||||||
 | 
					                          {name:"foobot", config: {bar: "baz", qux: "quux"}},
 | 
				
			||||||
 | 
					                         ],
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set_global("avatar", {});
 | 
					set_global("avatar", {});
 | 
				
			||||||
@@ -10,6 +13,8 @@ set_global('document', 'document-stub');
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
zrequire('bot_data');
 | 
					zrequire('bot_data');
 | 
				
			||||||
zrequire('settings_bots');
 | 
					zrequire('settings_bots');
 | 
				
			||||||
 | 
					zrequire('Handlebars', 'handlebars');
 | 
				
			||||||
 | 
					zrequire('templates');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
(function test_generate_zuliprc_uri() {
 | 
					(function test_generate_zuliprc_uri() {
 | 
				
			||||||
    var bot = {
 | 
					    var bot = {
 | 
				
			||||||
@@ -55,6 +60,7 @@ zrequire('settings_bots');
 | 
				
			|||||||
function test_create_bot_type_input_box_toggle(f) {
 | 
					function test_create_bot_type_input_box_toggle(f) {
 | 
				
			||||||
    var create_payload_url = $('#create_payload_url');
 | 
					    var create_payload_url = $('#create_payload_url');
 | 
				
			||||||
    var payload_url_inputbox = $('#payload_url_inputbox');
 | 
					    var payload_url_inputbox = $('#payload_url_inputbox');
 | 
				
			||||||
 | 
					    var config_inputbox = $('#config_inputbox');
 | 
				
			||||||
    var EMBEDDED_BOT_TYPE = '4';
 | 
					    var EMBEDDED_BOT_TYPE = '4';
 | 
				
			||||||
    var OUTGOING_WEBHOOK_BOT_TYPE = '3';
 | 
					    var OUTGOING_WEBHOOK_BOT_TYPE = '3';
 | 
				
			||||||
    var GENERIC_BOT_TYPE = '1';
 | 
					    var GENERIC_BOT_TYPE = '1';
 | 
				
			||||||
@@ -65,16 +71,19 @@ function test_create_bot_type_input_box_toggle(f) {
 | 
				
			|||||||
    assert(!payload_url_inputbox.visible());
 | 
					    assert(!payload_url_inputbox.visible());
 | 
				
			||||||
    assert($('#select_service_name').hasClass('required'));
 | 
					    assert($('#select_service_name').hasClass('required'));
 | 
				
			||||||
    assert($('#service_name_list').visible());
 | 
					    assert($('#service_name_list').visible());
 | 
				
			||||||
 | 
					    assert(config_inputbox.visible());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $('#create_bot_type :selected').val(OUTGOING_WEBHOOK_BOT_TYPE);
 | 
					    $('#create_bot_type :selected').val(OUTGOING_WEBHOOK_BOT_TYPE);
 | 
				
			||||||
    f.apply();
 | 
					    f.apply();
 | 
				
			||||||
    assert(create_payload_url.hasClass('required'));
 | 
					    assert(create_payload_url.hasClass('required'));
 | 
				
			||||||
    assert(payload_url_inputbox.visible());
 | 
					    assert(payload_url_inputbox.visible());
 | 
				
			||||||
 | 
					    assert(!config_inputbox.visible());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $('#create_bot_type :selected').val(GENERIC_BOT_TYPE);
 | 
					    $('#create_bot_type :selected').val(GENERIC_BOT_TYPE);
 | 
				
			||||||
    f.apply();
 | 
					    f.apply();
 | 
				
			||||||
    assert(!(create_payload_url.hasClass('required')));
 | 
					    assert(!(create_payload_url.hasClass('required')));
 | 
				
			||||||
    assert(!payload_url_inputbox.visible());
 | 
					    assert(!payload_url_inputbox.visible());
 | 
				
			||||||
 | 
					    assert(!config_inputbox.visible());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
(function test_set_up() {
 | 
					(function test_set_up() {
 | 
				
			||||||
@@ -93,14 +102,27 @@ function test_create_bot_type_input_box_toggle(f) {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var embedded_bots_added = 0;
 | 
					    var embedded_bots_added = 0;
 | 
				
			||||||
 | 
					    var config_fields_added = 0;
 | 
				
			||||||
    $('#select_service_name').append = function () {
 | 
					    $('#select_service_name').append = function () {
 | 
				
			||||||
        embedded_bots_added += 1;
 | 
					        embedded_bots_added += 1;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    $('#config_inputbox').append = function () {
 | 
				
			||||||
 | 
					        config_fields_added += 1;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    $('#config_inputbox').children = function () {
 | 
				
			||||||
 | 
					        var mock_children = {
 | 
				
			||||||
 | 
					            hide: function () {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        return mock_children;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    global.compile_template('embedded_bot_config_item');
 | 
				
			||||||
    avatar.build_bot_create_widget = function () {};
 | 
					    avatar.build_bot_create_widget = function () {};
 | 
				
			||||||
    avatar.build_bot_edit_widget = function () {};
 | 
					    avatar.build_bot_edit_widget = function () {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    settings_bots.set_up();
 | 
					    settings_bots.set_up();
 | 
				
			||||||
    assert(embedded_bots_added === page_params.realm_embedded_bots.length);
 | 
					    assert(embedded_bots_added === page_params.realm_embedded_bots.length);
 | 
				
			||||||
 | 
					    assert(config_fields_added === 3);
 | 
				
			||||||
}());
 | 
					}());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1427,6 +1427,19 @@ function render(template_name, args) {
 | 
				
			|||||||
    assert.equal($(html).find("tr").data("topic"), "Verona2");
 | 
					    assert.equal($(html).find("tr").data("topic"), "Verona2");
 | 
				
			||||||
}());
 | 
					}());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function embedded_bot_config_item() {
 | 
				
			||||||
 | 
					    var args = {
 | 
				
			||||||
 | 
					        botname: 'giphy',
 | 
				
			||||||
 | 
					        key: 'api_key',
 | 
				
			||||||
 | 
					        value: '12345678',
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    var html = render('embedded_bot_config_item', args);
 | 
				
			||||||
 | 
					    assert.equal($(html).attr('name'), args.botname);
 | 
				
			||||||
 | 
					    assert.equal($(html).attr('id'), args.botname+'_'+args.key);
 | 
				
			||||||
 | 
					    assert.equal($(html).find('label').text(), args.key);
 | 
				
			||||||
 | 
					    assert.equal($(html).find('input').attr('placeholder'), args.value);
 | 
				
			||||||
 | 
					}());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// By the end of this test, we should have compiled all our templates.  Ideally,
 | 
					// By the end of this test, we should have compiled all our templates.  Ideally,
 | 
				
			||||||
// we will also have exercised them to some degree, but that's a little trickier
 | 
					// we will also have exercised them to some degree, but that's a little trickier
 | 
				
			||||||
// to enforce.
 | 
					// to enforce.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -86,13 +86,22 @@ exports.set_up = function () {
 | 
				
			|||||||
    $('#payload_url_inputbox').hide();
 | 
					    $('#payload_url_inputbox').hide();
 | 
				
			||||||
    $('#create_payload_url').val('');
 | 
					    $('#create_payload_url').val('');
 | 
				
			||||||
    $('#service_name_list').hide();
 | 
					    $('#service_name_list').hide();
 | 
				
			||||||
    page_params.realm_embedded_bots.forEach(function (bot_name) {
 | 
					    $('#config_inputbox').hide();
 | 
				
			||||||
 | 
					    page_params.realm_embedded_bots.forEach(function (bot) {
 | 
				
			||||||
        $('#select_service_name').append($('<option>', {
 | 
					        $('#select_service_name').append($('<option>', {
 | 
				
			||||||
            value: bot_name,
 | 
					            value: bot.name,
 | 
				
			||||||
            text: bot_name,
 | 
					            text: bot.name,
 | 
				
			||||||
        }));
 | 
					        }));
 | 
				
			||||||
 | 
					        _.each(bot.config, function (key) {
 | 
				
			||||||
 | 
					            var rendered_config_item = templates.render('embedded_bot_config_item',
 | 
				
			||||||
 | 
					                {botname: bot.name, key: key, value: bot.config[key]});
 | 
				
			||||||
 | 
					            $('#config_inputbox').append(rendered_config_item);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    $('#select_service_name').val('converter'); // TODO: Use 'select a bot'.
 | 
					    var selected_embedded_bot = 'converter';
 | 
				
			||||||
 | 
					    $('#select_service_name').val(selected_embedded_bot); // TODO: Use 'select a bot'.
 | 
				
			||||||
 | 
					    $('#config_inputbox').children().hide();
 | 
				
			||||||
 | 
					    $("[name*='"+selected_embedded_bot+"']").show();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $('#download_flaskbotrc').click(function () {
 | 
					    $('#download_flaskbotrc').click(function () {
 | 
				
			||||||
        var OUTGOING_WEBHOOK_BOT_TYPE_INT = 3;
 | 
					        var OUTGOING_WEBHOOK_BOT_TYPE_INT = 3;
 | 
				
			||||||
@@ -153,6 +162,11 @@ exports.set_up = function () {
 | 
				
			|||||||
                formData.append('interface_type', interface_type);
 | 
					                formData.append('interface_type', interface_type);
 | 
				
			||||||
            } else if (bot_type === EMBEDDED_BOT_TYPE) {
 | 
					            } else if (bot_type === EMBEDDED_BOT_TYPE) {
 | 
				
			||||||
                formData.append('service_name', service_name);
 | 
					                formData.append('service_name', service_name);
 | 
				
			||||||
 | 
					                var config_data = {};
 | 
				
			||||||
 | 
					                $("[name*='"+service_name+"'] input").each(function () {
 | 
				
			||||||
 | 
					                    config_data[$(this).attr('name')] = $(this).val();
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                formData.append('config_data', JSON.stringify(config_data));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            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);
 | 
				
			||||||
@@ -170,6 +184,10 @@ exports.set_up = function () {
 | 
				
			|||||||
                    $('#create_bot_short_name').val('');
 | 
					                    $('#create_bot_short_name').val('');
 | 
				
			||||||
                    $('#create_payload_url').val('');
 | 
					                    $('#create_payload_url').val('');
 | 
				
			||||||
                    $('#payload_url_inputbox').hide();
 | 
					                    $('#payload_url_inputbox').hide();
 | 
				
			||||||
 | 
					                    $('#config_inputbox').hide();
 | 
				
			||||||
 | 
					                    $("[name*='"+service_name+"'] input").each(function () {
 | 
				
			||||||
 | 
					                        $(this).val('');
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
                    $('#create_bot_type').val(GENERIC_BOT_TYPE);
 | 
					                    $('#create_bot_type').val(GENERIC_BOT_TYPE);
 | 
				
			||||||
                    $('#select_service_name').val('converter'); // TODO: Later we can change this to hello bot or similar
 | 
					                    $('#select_service_name').val('converter'); // TODO: Later we can change this to hello bot or similar
 | 
				
			||||||
                    $('#service_name_list').hide();
 | 
					                    $('#service_name_list').hide();
 | 
				
			||||||
@@ -194,6 +212,7 @@ exports.set_up = function () {
 | 
				
			|||||||
        // For "generic bot" or "incoming webhook" both these fields need not be displayed.
 | 
					        // For "generic bot" or "incoming webhook" both these fields need not be displayed.
 | 
				
			||||||
        $('#service_name_list').hide();
 | 
					        $('#service_name_list').hide();
 | 
				
			||||||
        $('#select_service_name').removeClass('required');
 | 
					        $('#select_service_name').removeClass('required');
 | 
				
			||||||
 | 
					        $('#config_inputbox').hide();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $('#payload_url_inputbox').hide();
 | 
					        $('#payload_url_inputbox').hide();
 | 
				
			||||||
        $('#create_payload_url').removeClass('required');
 | 
					        $('#create_payload_url').removeClass('required');
 | 
				
			||||||
@@ -204,9 +223,17 @@ exports.set_up = function () {
 | 
				
			|||||||
        } else if (bot_type === EMBEDDED_BOT_TYPE) {
 | 
					        } else if (bot_type === EMBEDDED_BOT_TYPE) {
 | 
				
			||||||
            $('#service_name_list').show();
 | 
					            $('#service_name_list').show();
 | 
				
			||||||
            $('#select_service_name').addClass('required');
 | 
					            $('#select_service_name').addClass('required');
 | 
				
			||||||
 | 
					            $("#select_service_name").trigger('change');
 | 
				
			||||||
 | 
					            $('#config_inputbox').show();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $("#select_service_name").on("change", function () {
 | 
				
			||||||
 | 
					        $('#config_inputbox').children().hide();
 | 
				
			||||||
 | 
					        var selected_bot = $('#select_service_name :selected').val();
 | 
				
			||||||
 | 
					        $("[name*='"+selected_bot+"']").show();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $("#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({
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5
									
								
								static/templates/embedded_bot_config_item.handlebars
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								static/templates/embedded_bot_config_item.handlebars
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					<div class="input-group" name="{{botname}}" id="{{botname}}_{{key}}">
 | 
				
			||||||
 | 
					    <label for="{{botname}}_{{key}}_input">{{key}}</label>
 | 
				
			||||||
 | 
					    <input type="text" name="{{key}}" id="{{botname}}_{{key}}_input"
 | 
				
			||||||
 | 
					        maxlength=1000 placeholder="{{value}}" value="" />
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
@@ -78,6 +78,8 @@
 | 
				
			|||||||
                        <select name="service_name" id="select_service_name">
 | 
					                        <select name="service_name" id="select_service_name">
 | 
				
			||||||
                        </select>
 | 
					                        </select>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div id="config_inputbox">
 | 
				
			||||||
 | 
					                    </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' }}" />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,7 @@ session_engine = import_module(settings.SESSION_ENGINE)
 | 
				
			|||||||
from zerver.lib.alert_words import user_alert_words
 | 
					from zerver.lib.alert_words import user_alert_words
 | 
				
			||||||
from zerver.lib.attachments import user_attachments
 | 
					from zerver.lib.attachments import user_attachments
 | 
				
			||||||
from zerver.lib.avatar import avatar_url, get_avatar_field
 | 
					from zerver.lib.avatar import avatar_url, get_avatar_field
 | 
				
			||||||
 | 
					from zerver.lib.bot_config import load_bot_config_template
 | 
				
			||||||
from zerver.lib.hotspots import get_next_hotspots
 | 
					from zerver.lib.hotspots import get_next_hotspots
 | 
				
			||||||
from zerver.lib.integrations import EMBEDDED_BOTS
 | 
					from zerver.lib.integrations import EMBEDDED_BOTS
 | 
				
			||||||
from zerver.lib.message import (
 | 
					from zerver.lib.message import (
 | 
				
			||||||
@@ -218,7 +219,11 @@ def fetch_initial_state_data(user_profile: UserProfile,
 | 
				
			|||||||
    # This does not yet have an apply_event counterpart, since currently,
 | 
					    # This does not yet have an apply_event counterpart, since currently,
 | 
				
			||||||
    # new entries for EMBEDDED_BOTS can only be added directly in the codebase.
 | 
					    # new entries for EMBEDDED_BOTS can only be added directly in the codebase.
 | 
				
			||||||
    if want('realm_embedded_bots'):
 | 
					    if want('realm_embedded_bots'):
 | 
				
			||||||
        state['realm_embedded_bots'] = list(bot.name for bot in EMBEDDED_BOTS)
 | 
					        realm_embedded_bots = []
 | 
				
			||||||
 | 
					        for bot in EMBEDDED_BOTS:
 | 
				
			||||||
 | 
					            realm_embedded_bots.append({'name': bot.name,
 | 
				
			||||||
 | 
					                                        'config': load_bot_config_template(bot.name)})
 | 
				
			||||||
 | 
					        state['realm_embedded_bots'] = realm_embedded_bots
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if want('subscription'):
 | 
					    if want('subscription'):
 | 
				
			||||||
        subscriptions, unsubscribed, never_subscribed = gather_subscriptions_helper(
 | 
					        subscriptions, unsubscribed, never_subscribed = gather_subscriptions_helper(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -256,6 +256,8 @@ EMBEDDED_BOTS = [
 | 
				
			|||||||
    EmbeddedBotIntegration('encrypt', []),
 | 
					    EmbeddedBotIntegration('encrypt', []),
 | 
				
			||||||
    EmbeddedBotIntegration('helloworld', []),
 | 
					    EmbeddedBotIntegration('helloworld', []),
 | 
				
			||||||
    EmbeddedBotIntegration('virtual_fs', []),
 | 
					    EmbeddedBotIntegration('virtual_fs', []),
 | 
				
			||||||
 | 
					    EmbeddedBotIntegration('giphy', []),
 | 
				
			||||||
 | 
					    EmbeddedBotIntegration('followup', []),
 | 
				
			||||||
]  # type: List[EmbeddedBotIntegration]
 | 
					]  # type: List[EmbeddedBotIntegration]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WEBHOOK_INTEGRATIONS = [
 | 
					WEBHOOK_INTEGRATIONS = [
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ from mock import patch
 | 
				
			|||||||
from typing import Any, Dict, List, Mapping
 | 
					from typing import Any, Dict, List, Mapping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.lib.actions import do_change_stream_invite_only
 | 
					from zerver.lib.actions import do_change_stream_invite_only
 | 
				
			||||||
 | 
					from zerver.lib.bot_config import get_bot_config
 | 
				
			||||||
from zerver.models import get_realm, get_stream, \
 | 
					from zerver.models import get_realm, get_stream, \
 | 
				
			||||||
    Realm, Stream, UserProfile, get_user, get_bot_services, Service, \
 | 
					    Realm, Stream, UserProfile, get_user, get_bot_services, Service, \
 | 
				
			||||||
    is_cross_realm_bot_email
 | 
					    is_cross_realm_bot_email
 | 
				
			||||||
@@ -994,11 +995,13 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
 | 
				
			|||||||
        self.login(self.example_email('hamlet'))
 | 
					        self.login(self.example_email('hamlet'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test to create embedded bot with correct service_name
 | 
					        # Test to create embedded bot with correct service_name
 | 
				
			||||||
 | 
					        bot_config_info = {'key': 'value'}
 | 
				
			||||||
        bot_info = {
 | 
					        bot_info = {
 | 
				
			||||||
            'full_name': 'Embedded test bot',
 | 
					            'full_name': 'Embedded test bot',
 | 
				
			||||||
            'short_name': 'embeddedservicebot',
 | 
					            'short_name': 'embeddedservicebot',
 | 
				
			||||||
            'bot_type': UserProfile.EMBEDDED_BOT,
 | 
					            'bot_type': UserProfile.EMBEDDED_BOT,
 | 
				
			||||||
            'service_name': 'converter',
 | 
					            'service_name': 'followup',
 | 
				
			||||||
 | 
					            'config_data': ujson.dumps(bot_config_info),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        bot_info.update(extras)
 | 
					        bot_info.update(extras)
 | 
				
			||||||
        with self.settings(EMBEDDED_BOTS_ENABLED=False):
 | 
					        with self.settings(EMBEDDED_BOTS_ENABLED=False):
 | 
				
			||||||
@@ -1013,9 +1016,10 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
 | 
				
			|||||||
        bot = get_user(bot_email, bot_realm)
 | 
					        bot = get_user(bot_email, bot_realm)
 | 
				
			||||||
        services = get_bot_services(bot.id)
 | 
					        services = get_bot_services(bot.id)
 | 
				
			||||||
        service = services[0]
 | 
					        service = services[0]
 | 
				
			||||||
 | 
					        bot_config = get_bot_config(bot)
 | 
				
			||||||
 | 
					        self.assertEqual(bot_config, bot_config_info)
 | 
				
			||||||
        self.assertEqual(len(services), 1)
 | 
					        self.assertEqual(len(services), 1)
 | 
				
			||||||
        self.assertEqual(service.name, "converter")
 | 
					        self.assertEqual(service.name, "followup")
 | 
				
			||||||
        self.assertEqual(service.user_profile, bot)
 | 
					        self.assertEqual(service.user_profile, bot)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test to create embedded bot with incorrect service_name
 | 
					        # Test to create embedded bot with incorrect service_name
 | 
				
			||||||
@@ -1029,6 +1033,19 @@ class BotTest(ZulipTestCase, UploadSerializeMixin):
 | 
				
			|||||||
        result = self.client_post("/json/bots", bot_info)
 | 
					        result = self.client_post("/json/bots", bot_info)
 | 
				
			||||||
        self.assert_json_error(result, 'Invalid embedded bot name.')
 | 
					        self.assert_json_error(result, 'Invalid embedded bot name.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Test to create embedded bot with an invalid config value
 | 
				
			||||||
 | 
					        malformatted_bot_config_info = {'foo': ['bar', 'baz']}
 | 
				
			||||||
 | 
					        bot_info = {
 | 
				
			||||||
 | 
					            'full_name': 'Embedded test bot',
 | 
				
			||||||
 | 
					            'short_name': 'embeddedservicebot2',
 | 
				
			||||||
 | 
					            'bot_type': UserProfile.EMBEDDED_BOT,
 | 
				
			||||||
 | 
					            'service_name': 'followup',
 | 
				
			||||||
 | 
					            'config_data': ujson.dumps(malformatted_bot_config_info)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        bot_info.update(extras)
 | 
				
			||||||
 | 
					        result = self.client_post("/json/bots", bot_info)
 | 
				
			||||||
 | 
					        self.assert_json_error(result, 'config_data contains a value that is not a string')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_is_cross_realm_bot_email(self) -> None:
 | 
					    def test_is_cross_realm_bot_email(self) -> None:
 | 
				
			||||||
        self.assertTrue(is_cross_realm_bot_email("notification-bot@zulip.com"))
 | 
					        self.assertTrue(is_cross_realm_bot_email("notification-bot@zulip.com"))
 | 
				
			||||||
        self.assertTrue(is_cross_realm_bot_email("notification-BOT@zulip.com"))
 | 
					        self.assertTrue(is_cross_realm_bot_email("notification-BOT@zulip.com"))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,13 +17,14 @@ from zerver.lib.actions import do_change_avatar_fields, do_change_bot_owner, \
 | 
				
			|||||||
    do_create_user, do_deactivate_user, do_reactivate_user, do_regenerate_api_key, \
 | 
					    do_create_user, do_deactivate_user, do_reactivate_user, do_regenerate_api_key, \
 | 
				
			||||||
    check_change_full_name
 | 
					    check_change_full_name
 | 
				
			||||||
from zerver.lib.avatar import avatar_url, get_gravatar_url, get_avatar_field
 | 
					from zerver.lib.avatar import avatar_url, get_gravatar_url, get_avatar_field
 | 
				
			||||||
 | 
					from zerver.lib.bot_config import set_bot_config
 | 
				
			||||||
from zerver.lib.exceptions import JsonableError
 | 
					from zerver.lib.exceptions import JsonableError
 | 
				
			||||||
from zerver.lib.integrations import EMBEDDED_BOTS
 | 
					from zerver.lib.integrations import EMBEDDED_BOTS
 | 
				
			||||||
from zerver.lib.request import has_request_variables, REQ
 | 
					from zerver.lib.request import has_request_variables, REQ
 | 
				
			||||||
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, check_url
 | 
					from zerver.lib.validator import check_bool, check_string, check_int, check_url, check_dict
 | 
				
			||||||
from zerver.lib.users import check_valid_bot_type, \
 | 
					from zerver.lib.users import check_valid_bot_type, \
 | 
				
			||||||
    check_full_name, check_short_name, check_valid_interface_type
 | 
					    check_full_name, check_short_name, check_valid_interface_type
 | 
				
			||||||
from zerver.lib.utils import generate_random_token
 | 
					from zerver.lib.utils import generate_random_token
 | 
				
			||||||
@@ -246,6 +247,8 @@ def add_bot_backend(
 | 
				
			|||||||
        bot_type: int=REQ(validator=check_int, default=UserProfile.DEFAULT_BOT),
 | 
					        bot_type: int=REQ(validator=check_int, default=UserProfile.DEFAULT_BOT),
 | 
				
			||||||
        payload_url: Optional[Text]=REQ(validator=check_url, default=""),
 | 
					        payload_url: Optional[Text]=REQ(validator=check_url, default=""),
 | 
				
			||||||
        service_name: Optional[Text]=REQ(default=None),
 | 
					        service_name: Optional[Text]=REQ(default=None),
 | 
				
			||||||
 | 
					        config_data: Optional[Dict[Text, Text]]=REQ(default=None,
 | 
				
			||||||
 | 
					                                                    validator=check_dict(value_validator=check_string)),
 | 
				
			||||||
        interface_type: int=REQ(validator=check_int, default=Service.GENERIC),
 | 
					        interface_type: int=REQ(validator=check_int, default=Service.GENERIC),
 | 
				
			||||||
        default_sending_stream_name: Optional[Text]=REQ('default_sending_stream', default=None),
 | 
					        default_sending_stream_name: Optional[Text]=REQ('default_sending_stream', default=None),
 | 
				
			||||||
        default_events_register_stream_name: Optional[Text]=REQ('default_events_register_stream',
 | 
					        default_events_register_stream_name: Optional[Text]=REQ('default_events_register_stream',
 | 
				
			||||||
@@ -313,6 +316,10 @@ def add_bot_backend(
 | 
				
			|||||||
                    interface=interface_type,
 | 
					                    interface=interface_type,
 | 
				
			||||||
                    token=random_api_key())
 | 
					                    token=random_api_key())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if bot_type == UserProfile.EMBEDDED_BOT:
 | 
				
			||||||
 | 
					        for key, value in config_data.items():
 | 
				
			||||||
 | 
					            set_bot_config(bot_profile, key, value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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