diff --git a/api_docs/changelog.md b/api_docs/changelog.md
index a306d3a361..d09f7c47c7 100644
--- a/api_docs/changelog.md
+++ b/api_docs/changelog.md
@@ -20,6 +20,13 @@ format used by the Zulip server that they are interacting with.
## Changes in Zulip 10.0
+**Feature level 318**
+
+* [`POST /register`](/api/register-queue): Updated
+ `realm_incoming_webhook_bots` with a new `config_options` key,
+ defining which options should be offered when creating URLs for this
+ integration.
+
**Feature level 317**
* [`POST /user_groups/create`](/api/create-user-group):
diff --git a/version.py b/version.py
index d5826b3aaf..fcc6efc6a1 100644
--- a/version.py
+++ b/version.py
@@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
# new level means in api_docs/changelog.md, as well as "**Changes**"
# entries in the endpoint's documentation in `zulip.yaml`.
-API_FEATURE_LEVEL = 317 # Last bumped for inclusion of `group_id` in user group creation response.
+API_FEATURE_LEVEL = 318 # Last bumped for `WebhookConfigOption` configuration update in realm_incoming_webhook_bots.
# Bump the minor PROVISION_VERSION to indicate that folks should provision
# only when going from an old version of the code to a newer version. Bump
diff --git a/web/src/integration_url_modal.ts b/web/src/integration_url_modal.ts
index 61f285fd01..5e6c25b3a6 100644
--- a/web/src/integration_url_modal.ts
+++ b/web/src/integration_url_modal.ts
@@ -1,7 +1,10 @@
import ClipboardJS from "clipboard";
import $ from "jquery";
import type * as tippy from "tippy.js";
+import {z} from "zod";
+import render_generate_integration_url_config_checkbox_modal from "../templates/settings/generate_integration_url_config_checkbox_modal.hbs";
+import render_generate_integration_url_config_text_modal from "../templates/settings/generate_integration_url_config_text_modal.hbs";
import render_generate_integration_url_modal from "../templates/settings/generate_integration_url_modal.hbs";
import render_integration_events from "../templates/settings/integration_events.hbs";
@@ -14,6 +17,20 @@ import {realm} from "./state_data";
import * as stream_data from "./stream_data";
import * as util from "./util";
+type ConfigOption = {
+ key: string;
+ label: string;
+ validator: string;
+};
+
+const config_option_schema = z.object({
+ key: z.string(),
+ label: z.string(),
+ validator: z.string(),
+});
+
+const config_options_schema = z.array(config_option_schema);
+
export function show_generate_integration_url_modal(api_key: string): void {
const default_url_message = $t_html({defaultMessage: "Integration URL will appear here."});
const streams = stream_data.subscribed_subs();
@@ -42,6 +59,7 @@ export function show_generate_integration_url_modal(api_key: string): void {
const $integration_url = $("#generate-integration-url-modal .integration-url");
const $dialog_submit_button = $("#generate-integration-url-modal .dialog_submit_button");
const $show_integration_events = $("#show-integration-events");
+ const $config_container = $("#integration-url-config-options-container");
$dialog_submit_button.prop("disabled", true);
$("#integration-url-stream_widget").prop("disabled", true);
@@ -57,6 +75,40 @@ export function show_generate_integration_url_modal(api_key: string): void {
);
});
+ function render_config(config: ConfigOption[]): void {
+ const validated_config = config_options_schema.parse(config);
+ $config_container.empty();
+
+ for (const option of validated_config) {
+ let $config_element: JQuery;
+
+ if (option.validator === "check_bool") {
+ const config_html = render_generate_integration_url_config_checkbox_modal({
+ key: option.key,
+ label: option.label,
+ });
+ $config_element = $(config_html);
+ $config_element
+ .find(`#integration-url-${option.key}-checkbox`)
+ .on("change", () => {
+ update_url();
+ });
+ } else if (option.validator === "check_string") {
+ const config_html = render_generate_integration_url_config_text_modal({
+ key: option.key,
+ label: option.label,
+ });
+ $config_element = $(config_html);
+ $config_element.find(`#integration-url-${option.key}-text`).on("change", () => {
+ update_url();
+ });
+ } else {
+ continue;
+ }
+ $config_container.append($config_element);
+ }
+ }
+
$override_topic.on("change", function () {
const checked = this.checked;
$topic_input.parent().toggleClass("hide", !checked);
@@ -109,6 +161,7 @@ export function show_generate_integration_url_modal(api_key: string): void {
(bot) => bot.name === selected_integration,
);
const all_event_types = selected_integration_data?.all_event_types;
+ const config = selected_integration_data?.config_options;
if (all_event_types !== null) {
$("#integration-events-parameter").removeClass("hide");
@@ -136,8 +189,27 @@ export function show_generate_integration_url_modal(api_key: string): void {
params.set("topic", topic_name);
}
}
+
const selected_events = set_events_param(params);
+ if (config) {
+ for (const option of config) {
+ let $input_element;
+ if (option.validator === "check_bool") {
+ $input_element = $(`#integration-url-${option.key}-checkbox`);
+ if ($input_element.prop("checked")) {
+ params.set(option.key, "true");
+ }
+ } else if (option.validator === "check_string") {
+ $input_element = $(`#integration-url-${option.key}-text`);
+ const value = $input_element.val();
+ if (value) {
+ params.set(option.key, value.toString());
+ }
+ }
+ }
+ }
+
const realm_url = realm.realm_url;
const base_url = `${realm_url}/api/v1/external/`;
$integration_url.text(`${base_url}${selected_integration}?${params.toString()}`);
@@ -181,6 +253,15 @@ export function show_generate_integration_url_modal(api_key: string): void {
integration_input_dropdown_widget.render();
$(".integration-url-name-wrapper").trigger("input");
+ const selected_integration = integration_input_dropdown_widget.value();
+ const selected_integration_data = realm.realm_incoming_webhook_bots.find(
+ (bot) => bot.name === selected_integration,
+ );
+
+ if (selected_integration_data?.config_options) {
+ render_config(selected_integration_data.config_options);
+ }
+
dropdown.hide();
event.preventDefault();
event.stopPropagation();
@@ -265,6 +346,7 @@ export function show_generate_integration_url_modal(api_key: string): void {
$topic_input.parent().addClass("hide");
stream_input_dropdown_widget.render(direct_messages_option.unique_id);
+ $config_container.empty();
}
}
diff --git a/web/src/state_data.ts b/web/src/state_data.ts
index 6956e25bf9..8e70f9f7d7 100644
--- a/web/src/state_data.ts
+++ b/web/src/state_data.ts
@@ -342,7 +342,15 @@ export const realm_schema = z.object({
display_name: z.string(),
name: z.string(),
all_event_types: z.nullable(z.array(z.string())),
- // We currently ignore the `config` field in these objects.
+ config_options: z
+ .array(
+ z.object({
+ key: z.string(),
+ label: z.string(),
+ validator: z.string(),
+ }),
+ )
+ .optional(),
}),
),
realm_inline_image_preview: z.boolean(),
diff --git a/web/templates/settings/generate_integration_url_config_checkbox_modal.hbs b/web/templates/settings/generate_integration_url_config_checkbox_modal.hbs
new file mode 100644
index 0000000000..5297008e5b
--- /dev/null
+++ b/web/templates/settings/generate_integration_url_config_checkbox_modal.hbs
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/web/templates/settings/generate_integration_url_config_text_modal.hbs b/web/templates/settings/generate_integration_url_config_text_modal.hbs
new file mode 100644
index 0000000000..b7ec813241
--- /dev/null
+++ b/web/templates/settings/generate_integration_url_config_text_modal.hbs
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/web/templates/settings/generate_integration_url_modal.hbs b/web/templates/settings/generate_integration_url_modal.hbs
index 6f6d58f882..c4c12a785f 100644
--- a/web/templates/settings/generate_integration_url_modal.hbs
+++ b/web/templates/settings/generate_integration_url_modal.hbs
@@ -25,6 +25,9 @@
+
+
+