mirror of
https://github.com/zulip/zulip.git
synced 2025-11-01 12:33:40 +00:00
webhook_common: Add a method to build preset WebhookUrlOption.
This adds `WebhookUrlOption.build_preset_config` method which builds pre-configured WebhookUrlOptions objects. It can be used to abstract commonly used WebhookUrlOption settings or to construct special settings that have additional logic and UI in the web-app modal for generating an incoming webhook URL. Currently, one such setting is the "branches" url option. This setting is meant to be used by "versioncontrol" integrations such as GitHub, Gitea, etc. It adds UI that lets the user to choose which branches of their repository can trigger notifications. So, we refactor those integrations to use `build_preset_config` for the "branches" option. Co-authored-by: Lauryn Menard <lauryn@zulip.com>
This commit is contained in:
@@ -259,6 +259,44 @@ would want to include in a Zulip notification message.
|
|||||||
The `config_options` field in the `WebhookIntegration` class is reserved
|
The `config_options` field in the `WebhookIntegration` class is reserved
|
||||||
for this use case.
|
for this use case.
|
||||||
|
|
||||||
|
### WebhookUrlOption presets
|
||||||
|
|
||||||
|
The `build_preset_config` method creates `WebhookUrlOption` objects with
|
||||||
|
pre-configured fields. These preset URL options primarily serve two
|
||||||
|
purposes:
|
||||||
|
|
||||||
|
- To construct common `WebhookUrlOption` objects that are used in various
|
||||||
|
incoming webhook integrations.
|
||||||
|
|
||||||
|
- To construct `WebhookUrlOption` objects with special UI in the web-app
|
||||||
|
for [generating incoming webhook URLs](/help/generate-integration-url).
|
||||||
|
|
||||||
|
Using a preset URL option with the `build_preset_config` method:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# zerver/lib/integrations.py
|
||||||
|
from zerver.lib.webhooks.common import PresetUrlOption, WebhookUrlOption
|
||||||
|
# -- snip --
|
||||||
|
WebhookIntegration(
|
||||||
|
"github",
|
||||||
|
# -- snip --
|
||||||
|
url_options=[
|
||||||
|
WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
```
|
||||||
|
|
||||||
|
Currently configured preset URL options:
|
||||||
|
|
||||||
|
- **`BRANCHES`**: This preset is intended to be used for [version control
|
||||||
|
integrations](/integrations/version-control), and adds UI for the user to
|
||||||
|
configure which branches of a project's repository will trigger Zulip
|
||||||
|
notification messages. When the user specifies which branches to receive
|
||||||
|
notifications from, the `branches` parameter will be added to the [generated
|
||||||
|
integration URL](/help/generate-integration-url). For example, if the user
|
||||||
|
input `main` and `dev` for the branches of their repository, then
|
||||||
|
`&branches=main%2Cdev` would be appended to the generated integration URL.
|
||||||
|
|
||||||
## Step 4: Manually testing the webhook
|
## Step 4: Manually testing the webhook
|
||||||
|
|
||||||
For either one of the command line tools, first, you'll need to get an
|
For either one of the command line tools, first, you'll need to get an
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ const url_option_schema = z.object({
|
|||||||
|
|
||||||
const url_options_schema = z.array(url_option_schema);
|
const url_options_schema = z.array(url_option_schema);
|
||||||
|
|
||||||
|
const PresetUrlOption = {
|
||||||
|
BRANCHES: "branches",
|
||||||
|
};
|
||||||
|
|
||||||
export function show_generate_integration_url_modal(api_key: string): void {
|
export function show_generate_integration_url_modal(api_key: string): void {
|
||||||
const default_url_message = $t_html({defaultMessage: "Integration URL will appear here."});
|
const default_url_message = $t_html({defaultMessage: "Integration URL will appear here."});
|
||||||
const streams = stream_data.subscribed_subs();
|
const streams = stream_data.subscribed_subs();
|
||||||
@@ -114,7 +118,7 @@ export function show_generate_integration_url_modal(api_key: string): void {
|
|||||||
for (const option of validated_config) {
|
for (const option of validated_config) {
|
||||||
let $config_element: JQuery;
|
let $config_element: JQuery;
|
||||||
|
|
||||||
if (option.key === "branches") {
|
if (option.key === PresetUrlOption.BRANCHES) {
|
||||||
const filter_branches_html =
|
const filter_branches_html =
|
||||||
render_generate_integration_url_filter_branches_modal();
|
render_generate_integration_url_filter_branches_modal();
|
||||||
$config_element = $(filter_branches_html);
|
$config_element = $(filter_branches_html);
|
||||||
@@ -238,7 +242,7 @@ export function show_generate_integration_url_modal(api_key: string): void {
|
|||||||
for (const option of url_options) {
|
for (const option of url_options) {
|
||||||
let $input_element;
|
let $input_element;
|
||||||
if (
|
if (
|
||||||
option.key === "branches" &&
|
option.key === PresetUrlOption.BRANCHES &&
|
||||||
!$("#integration-url-all-branches").prop("checked")
|
!$("#integration-url-all-branches").prop("checked")
|
||||||
) {
|
) {
|
||||||
const $pill_container = $(
|
const $pill_container = $(
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ from django.views.decorators.csrf import csrf_exempt
|
|||||||
from django_stubs_ext import StrPromise
|
from django_stubs_ext import StrPromise
|
||||||
|
|
||||||
from zerver.lib.storage import static_path
|
from zerver.lib.storage import static_path
|
||||||
from zerver.lib.validator import check_bool, check_string
|
from zerver.lib.validator import check_bool
|
||||||
from zerver.lib.webhooks.common import WebhookConfigOption, WebhookUrlOption
|
from zerver.lib.webhooks.common import PresetUrlOption, WebhookConfigOption, WebhookUrlOption
|
||||||
|
|
||||||
"""This module declares all of the (documented) integrations available
|
"""This module declares all of the (documented) integrations available
|
||||||
in the Zulip server. The Integration class is used as part of
|
in the Zulip server. The Integration class is used as part of
|
||||||
@@ -414,7 +414,7 @@ WEBHOOK_INTEGRATIONS: list[WebhookIntegration] = [
|
|||||||
"azuredevops",
|
"azuredevops",
|
||||||
["version-control"],
|
["version-control"],
|
||||||
display_name="AzureDevOps",
|
display_name="AzureDevOps",
|
||||||
url_options=[WebhookUrlOption(name="branches", label="", validator=check_string)],
|
url_options=[WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES)],
|
||||||
),
|
),
|
||||||
WebhookIntegration("beanstalk", ["version-control"], stream_name="commits"),
|
WebhookIntegration("beanstalk", ["version-control"], stream_name="commits"),
|
||||||
WebhookIntegration("basecamp", ["project-management"]),
|
WebhookIntegration("basecamp", ["project-management"]),
|
||||||
@@ -425,7 +425,7 @@ WEBHOOK_INTEGRATIONS: list[WebhookIntegration] = [
|
|||||||
logo="images/integrations/logos/bitbucket.svg",
|
logo="images/integrations/logos/bitbucket.svg",
|
||||||
display_name="Bitbucket Server",
|
display_name="Bitbucket Server",
|
||||||
stream_name="bitbucket",
|
stream_name="bitbucket",
|
||||||
url_options=[WebhookUrlOption(name="branches", label="", validator=check_string)],
|
url_options=[WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES)],
|
||||||
),
|
),
|
||||||
WebhookIntegration(
|
WebhookIntegration(
|
||||||
"bitbucket2",
|
"bitbucket2",
|
||||||
@@ -433,7 +433,7 @@ WEBHOOK_INTEGRATIONS: list[WebhookIntegration] = [
|
|||||||
logo="images/integrations/logos/bitbucket.svg",
|
logo="images/integrations/logos/bitbucket.svg",
|
||||||
display_name="Bitbucket",
|
display_name="Bitbucket",
|
||||||
stream_name="bitbucket",
|
stream_name="bitbucket",
|
||||||
url_options=[WebhookUrlOption(name="branches", label="", validator=check_string)],
|
url_options=[WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES)],
|
||||||
),
|
),
|
||||||
WebhookIntegration(
|
WebhookIntegration(
|
||||||
"bitbucket",
|
"bitbucket",
|
||||||
@@ -462,7 +462,7 @@ WEBHOOK_INTEGRATIONS: list[WebhookIntegration] = [
|
|||||||
"gitea",
|
"gitea",
|
||||||
["version-control"],
|
["version-control"],
|
||||||
stream_name="commits",
|
stream_name="commits",
|
||||||
url_options=[WebhookUrlOption(name="branches", label="", validator=check_string)],
|
url_options=[WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES)],
|
||||||
),
|
),
|
||||||
WebhookIntegration(
|
WebhookIntegration(
|
||||||
"github",
|
"github",
|
||||||
@@ -471,7 +471,7 @@ WEBHOOK_INTEGRATIONS: list[WebhookIntegration] = [
|
|||||||
function="zerver.webhooks.github.view.api_github_webhook",
|
function="zerver.webhooks.github.view.api_github_webhook",
|
||||||
stream_name="github",
|
stream_name="github",
|
||||||
url_options=[
|
url_options=[
|
||||||
WebhookUrlOption(name="branches", label="", validator=check_string),
|
WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES),
|
||||||
WebhookUrlOption(
|
WebhookUrlOption(
|
||||||
name="ignore_private_repositories",
|
name="ignore_private_repositories",
|
||||||
label="Exclude notifications from private repositories",
|
label="Exclude notifications from private repositories",
|
||||||
@@ -493,14 +493,14 @@ WEBHOOK_INTEGRATIONS: list[WebhookIntegration] = [
|
|||||||
"gitlab",
|
"gitlab",
|
||||||
["version-control"],
|
["version-control"],
|
||||||
display_name="GitLab",
|
display_name="GitLab",
|
||||||
url_options=[WebhookUrlOption(name="branches", label="", validator=check_string)],
|
url_options=[WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES)],
|
||||||
),
|
),
|
||||||
WebhookIntegration("gocd", ["continuous-integration"], display_name="GoCD"),
|
WebhookIntegration("gocd", ["continuous-integration"], display_name="GoCD"),
|
||||||
WebhookIntegration(
|
WebhookIntegration(
|
||||||
"gogs",
|
"gogs",
|
||||||
["version-control"],
|
["version-control"],
|
||||||
stream_name="commits",
|
stream_name="commits",
|
||||||
url_options=[WebhookUrlOption(name="branches", label="", validator=check_string)],
|
url_options=[WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES)],
|
||||||
),
|
),
|
||||||
WebhookIntegration("gosquared", ["marketing"], display_name="GoSquared"),
|
WebhookIntegration("gosquared", ["marketing"], display_name="GoSquared"),
|
||||||
WebhookIntegration("grafana", ["monitoring"]),
|
WebhookIntegration("grafana", ["monitoring"]),
|
||||||
@@ -555,7 +555,7 @@ WEBHOOK_INTEGRATIONS: list[WebhookIntegration] = [
|
|||||||
"rhodecode",
|
"rhodecode",
|
||||||
["version-control"],
|
["version-control"],
|
||||||
display_name="RhodeCode",
|
display_name="RhodeCode",
|
||||||
url_options=[WebhookUrlOption(name="branches", label="", validator=check_string)],
|
url_options=[WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES)],
|
||||||
),
|
),
|
||||||
WebhookIntegration("rundeck", ["deployment"]),
|
WebhookIntegration("rundeck", ["deployment"]),
|
||||||
WebhookIntegration("semaphore", ["continuous-integration", "deployment"]),
|
WebhookIntegration("semaphore", ["continuous-integration", "deployment"]),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import importlib
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from enum import Enum
|
||||||
from typing import Annotated, Any, TypeAlias
|
from typing import Annotated, Any, TypeAlias
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ from zerver.lib.request import RequestNotes
|
|||||||
from zerver.lib.send_email import FromAddress
|
from zerver.lib.send_email import FromAddress
|
||||||
from zerver.lib.timestamp import timestamp_to_datetime
|
from zerver.lib.timestamp import timestamp_to_datetime
|
||||||
from zerver.lib.typed_endpoint import ApiParamConfig, typed_endpoint
|
from zerver.lib.typed_endpoint import ApiParamConfig, typed_endpoint
|
||||||
|
from zerver.lib.validator import check_string
|
||||||
from zerver.models import UserProfile
|
from zerver.models import UserProfile
|
||||||
|
|
||||||
MISSING_EVENT_HEADER_MESSAGE = """\
|
MISSING_EVENT_HEADER_MESSAGE = """\
|
||||||
@@ -54,6 +56,10 @@ SETUP_MESSAGE_USER_PART = " by {user_name}"
|
|||||||
OptionalUserSpecifiedTopicStr: TypeAlias = Annotated[str | None, ApiParamConfig("topic")]
|
OptionalUserSpecifiedTopicStr: TypeAlias = Annotated[str | None, ApiParamConfig("topic")]
|
||||||
|
|
||||||
|
|
||||||
|
class PresetUrlOption(str, Enum):
|
||||||
|
BRANCHES = "branches"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WebhookConfigOption:
|
class WebhookConfigOption:
|
||||||
name: str
|
name: str
|
||||||
@@ -67,6 +73,25 @@ class WebhookUrlOption:
|
|||||||
label: str
|
label: str
|
||||||
validator: Callable[[str, str], str | bool | None]
|
validator: Callable[[str, str], str | bool | None]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def build_preset_config(cls, config: PresetUrlOption) -> "WebhookUrlOption":
|
||||||
|
"""
|
||||||
|
This creates a pre-configured WebhookUrlOption object to be used
|
||||||
|
in various incoming webhook integrations.
|
||||||
|
|
||||||
|
See https://zulip.com/api/incoming-webhooks-walkthrough#webhookurloption-presets
|
||||||
|
for more details on this system and what each option does.
|
||||||
|
"""
|
||||||
|
match config:
|
||||||
|
case PresetUrlOption.BRANCHES:
|
||||||
|
return cls(
|
||||||
|
name=config.value,
|
||||||
|
label="",
|
||||||
|
validator=check_string,
|
||||||
|
)
|
||||||
|
|
||||||
|
raise AssertionError(_("Unknown 'PresetUrlOption': {config}").format(config=config))
|
||||||
|
|
||||||
|
|
||||||
def get_setup_webhook_message(integration: str, user_name: str | None = None) -> str:
|
def get_setup_webhook_message(integration: str, user_name: str | None = None) -> str:
|
||||||
content = SETUP_MESSAGE_TEMPLATE.format(integration=integration)
|
content = SETUP_MESSAGE_TEMPLATE.format(integration=integration)
|
||||||
|
|||||||
Reference in New Issue
Block a user