mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 04:52:12 +00:00
integrations: Add URL option and UI for mapping messages to Zulip channels.
This commit adds a "mapping" URL option preset that adds "Matching Zulip channel" option to the stream dropdown widget. When that option is chosen from the dropdown, it adds another parameter to the integration URL -- "&mapping=channels". This "mapping" parameter is meant to be used by integrations like Slack to identify whether the user wants to map Slack channels to different Zulip channels or different topics within a single channel. This adds an icon for the `mapping`s' drop down option in the "Where to send notification" drop down field. Co-authored-by: Pieter CK <pieterceka123@gmail.com> Co-authored-by: Lauryn Menard <lauryn@zulip.com>
This commit is contained in:
@@ -304,6 +304,15 @@ Currently configured preset URL options:
|
||||
`ignore_private_repositories` boolean parameter will be added to the
|
||||
[generated integration URL](/help/generate-integration-url).
|
||||
|
||||
- **`MAPPING`**: This preset is intended to be used for [chat-app
|
||||
integrations](/integrations/communication) (like Slack), and adds a
|
||||
special option, **Matching Zulip channel**, to the UI for where to send
|
||||
Zulip notification messages. This special option maps the notification
|
||||
messages to Zulip channels that match the messages' original channel
|
||||
name in the third-party app. When selected, this requires setting a
|
||||
single topic for notification messages, and adds `&mapping=channels`
|
||||
to the [generated integration URL](/help/generate-integration-url).
|
||||
|
||||
## Step 4: Manually testing the webhook
|
||||
|
||||
For either one of the command line tools, first, you'll need to get an
|
||||
|
3
web/shared/icons/equal.svg
Normal file
3
web/shared/icons/equal.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M2.668 6c0-.367.298-.665.665-.665h9.334a.665.665 0 0 1 0 1.33H3.333A.665.665 0 0 1 2.668 6Zm0 4c0-.367.298-.665.665-.665h9.334a.665.665 0 0 1 0 1.33H3.333A.665.665 0 0 1 2.668 10Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 285 B |
@@ -35,6 +35,7 @@ const url_options_schema = z.array(url_option_schema);
|
||||
|
||||
const PresetUrlOption = {
|
||||
BRANCHES: "branches",
|
||||
MAPPING: "mapping",
|
||||
};
|
||||
|
||||
export function show_generate_integration_url_modal(api_key: string): void {
|
||||
@@ -49,6 +50,10 @@ export function show_generate_integration_url_modal(api_key: string): void {
|
||||
unique_id: -1,
|
||||
is_direct_message: true,
|
||||
};
|
||||
const map_channels_option: Option = {
|
||||
name: $t_html({defaultMessage: "Matching Zulip channel"}),
|
||||
unique_id: -2,
|
||||
};
|
||||
const html_body = render_generate_integration_url_modal({
|
||||
default_url_message,
|
||||
max_topic_length: realm.max_topic_length,
|
||||
@@ -125,6 +130,8 @@ export function show_generate_integration_url_modal(api_key: string): void {
|
||||
$config_element.find("#integration-url-all-branches").on("change", () => {
|
||||
show_branch_filtering_ui();
|
||||
});
|
||||
} else if (option.key === PresetUrlOption.MAPPING) {
|
||||
continue;
|
||||
} else if (option.validator === "check_bool") {
|
||||
const config_html = render_generate_integration_url_config_checkbox_modal({
|
||||
key: option.key,
|
||||
@@ -241,10 +248,16 @@ export function show_generate_integration_url_modal(api_key: string): void {
|
||||
if (url_options) {
|
||||
for (const option of url_options) {
|
||||
let $input_element;
|
||||
if (
|
||||
option.key === PresetUrlOption.BRANCHES &&
|
||||
!$("#integration-url-all-branches").prop("checked")
|
||||
) {
|
||||
if (option.key === PresetUrlOption.MAPPING) {
|
||||
const stream_input = stream_input_dropdown_widget.value();
|
||||
if (stream_input === map_channels_option?.unique_id) {
|
||||
params.delete("stream");
|
||||
params.set(PresetUrlOption.MAPPING, "channels");
|
||||
}
|
||||
} else if (option.key === PresetUrlOption.BRANCHES) {
|
||||
if ($("#integration-url-all-branches").prop("checked")) {
|
||||
continue;
|
||||
}
|
||||
const $pill_container = $(
|
||||
"#integration-url-filter-branches .pill-container",
|
||||
);
|
||||
@@ -319,9 +332,10 @@ export function show_generate_integration_url_modal(api_key: string): void {
|
||||
const selected_integration_data = realm.realm_incoming_webhook_bots.find(
|
||||
(bot) => bot.name === selected_integration,
|
||||
);
|
||||
const url_options = selected_integration_data?.url_options;
|
||||
|
||||
if (selected_integration_data?.url_options) {
|
||||
render_url_options(selected_integration_data.url_options);
|
||||
if (url_options) {
|
||||
render_url_options(url_options);
|
||||
}
|
||||
|
||||
dropdown.hide();
|
||||
@@ -339,9 +353,37 @@ export function show_generate_integration_url_modal(api_key: string): void {
|
||||
});
|
||||
stream_input_dropdown_widget.setup();
|
||||
|
||||
function get_additional_stream_dropdown_options(): Option[] {
|
||||
const additional_options: Option[] = [];
|
||||
|
||||
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) {
|
||||
return additional_options;
|
||||
}
|
||||
|
||||
const url_options = selected_integration_data?.url_options;
|
||||
if (!url_options) {
|
||||
return additional_options;
|
||||
}
|
||||
|
||||
const mapping_option = url_options?.find(
|
||||
(option) => option.key === PresetUrlOption.MAPPING,
|
||||
);
|
||||
|
||||
if (mapping_option) {
|
||||
additional_options.push(map_channels_option);
|
||||
}
|
||||
return additional_options;
|
||||
}
|
||||
|
||||
function get_options_for_stream_dropdown_widget(): Option[] {
|
||||
const additional_options = get_additional_stream_dropdown_options();
|
||||
const options = [
|
||||
direct_messages_option,
|
||||
...additional_options,
|
||||
...streams
|
||||
.filter((stream) => stream_data.can_post_messages_in_stream(stream))
|
||||
.map((stream) => ({
|
||||
@@ -365,6 +407,11 @@ export function show_generate_integration_url_modal(api_key: string): void {
|
||||
$override_topic.prop("checked", false).prop("disabled", true);
|
||||
$override_topic.closest(".input-group").addClass("control-label-disabled");
|
||||
$topic_input.val("");
|
||||
} else if (user_selected_option === map_channels_option.unique_id) {
|
||||
$override_topic.prop("checked", true).prop("disabled", true);
|
||||
$override_topic.closest(".input-group").addClass("control-label-disabled");
|
||||
$topic_input.val("");
|
||||
$topic_input.parent().removeClass("hide");
|
||||
} else {
|
||||
$override_topic.prop("disabled", false);
|
||||
$override_topic.closest(".input-group").removeClass("control-label-disabled");
|
||||
|
@@ -36,6 +36,9 @@
|
||||
{{t "Disable" }}
|
||||
{{/if}}
|
||||
</span>
|
||||
{{else if (eq unique_id -2)}}
|
||||
{{!-- This is the option for PresetUrlOption.MAPPING --}}
|
||||
<i class="zulip-icon zulip-icon-equal channel-privacy-type-icon" aria-hidden="true"></i> {{name}}
|
||||
{{else}}
|
||||
{{#if bold_current_selection}}
|
||||
<span class="dropdown-list-bold-selected">{{name}}</span>
|
||||
|
@@ -59,6 +59,7 @@ OptionalUserSpecifiedTopicStr: TypeAlias = Annotated[str | None, ApiParamConfig(
|
||||
class PresetUrlOption(str, Enum):
|
||||
BRANCHES = "branches"
|
||||
IGNORE_PRIVATE_REPOSITORIES = "ignore_private_repositories"
|
||||
MAPPING = "mapping"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -96,6 +97,12 @@ class WebhookUrlOption:
|
||||
label="Exclude notifications from private repositories",
|
||||
validator=check_bool,
|
||||
)
|
||||
case PresetUrlOption.MAPPING:
|
||||
return cls(
|
||||
name=config.value,
|
||||
label="",
|
||||
validator=check_string,
|
||||
)
|
||||
|
||||
raise AssertionError(_("Unknown 'PresetUrlOption': {config}").format(config=config))
|
||||
|
||||
|
Reference in New Issue
Block a user