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 @@ +
+ +