mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 04:52:12 +00:00
This refactors the `ignore_private_repositories` URL option to be a preset because it can be used by other "version-control" integrations.
867 lines
34 KiB
Python
867 lines
34 KiB
Python
import os
|
|
from collections.abc import Callable, Sequence
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, TypeAlias
|
|
|
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
|
from django.http import HttpRequest, HttpResponseBase
|
|
from django.urls import URLPattern, path
|
|
from django.utils.module_loading import import_string
|
|
from django.utils.translation import gettext_lazy
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
from django_stubs_ext import StrPromise
|
|
|
|
from zerver.lib.storage import static_path
|
|
from zerver.lib.validator import check_bool
|
|
from zerver.lib.webhooks.common import PresetUrlOption, WebhookConfigOption, WebhookUrlOption
|
|
|
|
"""This module declares all of the (documented) integrations available
|
|
in the Zulip server. The Integration class is used as part of
|
|
generating the documentation on the /integrations/ page, while the
|
|
WebhookIntegration class is also used to generate the URLs in
|
|
`zproject/urls.py` for webhook integrations.
|
|
|
|
To add a new non-webhook integration, add code to the INTEGRATIONS
|
|
dictionary below.
|
|
|
|
To add a new webhook integration, declare a WebhookIntegration in the
|
|
WEBHOOK_INTEGRATIONS list below (it will be automatically added to
|
|
INTEGRATIONS).
|
|
|
|
To add a new integration category, add to either the CATEGORIES or
|
|
META_CATEGORY dicts below. The META_CATEGORY dict is for categories
|
|
that do not describe types of tools (e.g., bots or frameworks).
|
|
|
|
Over time, we expect this registry to grow additional convenience
|
|
features for writing and configuring integrations efficiently.
|
|
"""
|
|
|
|
OptionValidator: TypeAlias = Callable[[str, str], str | bool | None]
|
|
|
|
META_CATEGORY: dict[str, StrPromise] = {
|
|
"meta-integration": gettext_lazy("Integration frameworks"),
|
|
"bots": gettext_lazy("Interactive bots"),
|
|
}
|
|
|
|
CATEGORIES: dict[str, StrPromise] = {
|
|
**META_CATEGORY,
|
|
"continuous-integration": gettext_lazy("Continuous integration"),
|
|
"customer-support": gettext_lazy("Customer support"),
|
|
"deployment": gettext_lazy("Deployment"),
|
|
"entertainment": gettext_lazy("Entertainment"),
|
|
"communication": gettext_lazy("Communication"),
|
|
"financial": gettext_lazy("Financial"),
|
|
"hr": gettext_lazy("Human resources"),
|
|
"marketing": gettext_lazy("Marketing"),
|
|
"misc": gettext_lazy("Miscellaneous"),
|
|
"monitoring": gettext_lazy("Monitoring"),
|
|
"project-management": gettext_lazy("Project management"),
|
|
"productivity": gettext_lazy("Productivity"),
|
|
"version-control": gettext_lazy("Version control"),
|
|
}
|
|
|
|
|
|
class Integration:
|
|
DEFAULT_LOGO_STATIC_PATH_PNG = "images/integrations/logos/{name}.png"
|
|
DEFAULT_LOGO_STATIC_PATH_SVG = "images/integrations/logos/{name}.svg"
|
|
DEFAULT_BOT_AVATAR_PATH = "images/integrations/bot_avatars/{name}.png"
|
|
DEFAULT_DOC_PATH = "zerver/integrations/{name}.md"
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
categories: list[str],
|
|
client_name: str | None = None,
|
|
logo: str | None = None,
|
|
secondary_line_text: str | None = None,
|
|
display_name: str | None = None,
|
|
doc: str | None = None,
|
|
stream_name: str | None = None,
|
|
legacy: bool = False,
|
|
config_options: Sequence[WebhookConfigOption] = [],
|
|
url_options: Sequence[WebhookUrlOption] = [],
|
|
) -> None:
|
|
self.name = name
|
|
self.client_name = client_name if client_name is not None else name
|
|
self.secondary_line_text = secondary_line_text
|
|
self.legacy = legacy
|
|
self.doc = doc
|
|
self.url_options = url_options
|
|
|
|
# Note: Currently only incoming webhook type bots use this list for
|
|
# defining how the bot's BotConfigData should be. Embedded bots follow
|
|
# a different approach.
|
|
self.config_options = config_options
|
|
|
|
for category in categories:
|
|
if category not in CATEGORIES:
|
|
raise KeyError( # nocoverage
|
|
"INTEGRATIONS: "
|
|
+ name
|
|
+ " - category '"
|
|
+ category
|
|
+ "' is not a key in CATEGORIES.",
|
|
)
|
|
self.categories = [CATEGORIES[c] for c in categories]
|
|
|
|
self.logo_path = logo if logo is not None else self.get_logo_path()
|
|
# TODO: Enforce that all integrations have logo_url with an assertion.
|
|
self.logo_url = self.get_logo_url()
|
|
|
|
if display_name is None:
|
|
display_name = name.title()
|
|
self.display_name = display_name
|
|
|
|
if doc is None:
|
|
doc = self.DEFAULT_DOC_PATH.format(name=self.name)
|
|
self.doc = doc
|
|
|
|
if stream_name is None:
|
|
stream_name = self.name
|
|
self.stream_name = stream_name
|
|
|
|
def is_enabled(self) -> bool:
|
|
return True
|
|
|
|
def get_logo_path(self) -> str | None:
|
|
logo_file_path_svg = self.DEFAULT_LOGO_STATIC_PATH_SVG.format(name=self.name)
|
|
logo_file_path_png = self.DEFAULT_LOGO_STATIC_PATH_PNG.format(name=self.name)
|
|
if os.path.isfile(static_path(logo_file_path_svg)):
|
|
return logo_file_path_svg
|
|
elif os.path.isfile(static_path(logo_file_path_png)):
|
|
return logo_file_path_png
|
|
|
|
return None
|
|
|
|
def get_bot_avatar_path(self) -> str | None:
|
|
if self.logo_path is not None:
|
|
name = os.path.splitext(os.path.basename(self.logo_path))[0]
|
|
return self.DEFAULT_BOT_AVATAR_PATH.format(name=name)
|
|
|
|
return None
|
|
|
|
def get_logo_url(self) -> str | None:
|
|
if self.logo_path is not None:
|
|
return staticfiles_storage.url(self.logo_path)
|
|
|
|
return None
|
|
|
|
def get_translated_categories(self) -> list[str]:
|
|
return [str(category) for category in self.categories]
|
|
|
|
|
|
class BotIntegration(Integration):
|
|
DEFAULT_LOGO_STATIC_PATH_PNG = "generated/bots/{name}/logo.png"
|
|
DEFAULT_LOGO_STATIC_PATH_SVG = "generated/bots/{name}/logo.svg"
|
|
ZULIP_LOGO_STATIC_PATH_PNG = "images/logo/zulip-icon-128x128.png"
|
|
DEFAULT_DOC_PATH = "{name}/doc.md"
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
categories: list[str],
|
|
logo: str | None = None,
|
|
secondary_line_text: str | None = None,
|
|
display_name: str | None = None,
|
|
doc: str | None = None,
|
|
) -> None:
|
|
super().__init__(
|
|
name,
|
|
client_name=name,
|
|
categories=categories,
|
|
secondary_line_text=secondary_line_text,
|
|
)
|
|
|
|
if logo is None:
|
|
self.logo_url = self.get_logo_url()
|
|
if self.logo_url is None:
|
|
# TODO: Add a test for this by initializing one in a test.
|
|
logo = staticfiles_storage.url(self.ZULIP_LOGO_STATIC_PATH_PNG) # nocoverage
|
|
else:
|
|
self.logo_url = staticfiles_storage.url(logo)
|
|
|
|
if display_name is None:
|
|
display_name = f"{name.title()} Bot" # nocoverage
|
|
else:
|
|
display_name = f"{display_name} Bot"
|
|
self.display_name = display_name
|
|
|
|
if doc is None:
|
|
doc = self.DEFAULT_DOC_PATH.format(name=name)
|
|
self.doc = doc
|
|
|
|
|
|
class PythonAPIIntegration(Integration):
|
|
DEFAULT_DOC_PATH = "{directory_name}/doc.md"
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
categories: list[str],
|
|
client_name: str | None = None,
|
|
logo: str | None = None,
|
|
secondary_line_text: str | None = None,
|
|
display_name: str | None = None,
|
|
directory_name: str | None = None,
|
|
doc: str | None = None,
|
|
stream_name: str | None = None,
|
|
legacy: bool = False,
|
|
) -> None:
|
|
if directory_name is None:
|
|
directory_name = name
|
|
self.directory_name = directory_name
|
|
|
|
# Assign before super(), to use self.directory_name instead of self.name
|
|
if doc is None:
|
|
doc = self.DEFAULT_DOC_PATH.format(directory_name=self.directory_name)
|
|
self.doc = doc
|
|
|
|
super().__init__(
|
|
name,
|
|
categories,
|
|
client_name=client_name,
|
|
logo=logo,
|
|
secondary_line_text=secondary_line_text,
|
|
display_name=display_name,
|
|
doc=doc,
|
|
stream_name=stream_name,
|
|
legacy=legacy,
|
|
)
|
|
|
|
|
|
class WebhookIntegration(Integration):
|
|
DEFAULT_FUNCTION_PATH = "zerver.webhooks.{name}.view.api_{name}_webhook"
|
|
DEFAULT_URL = "api/v1/external/{name}"
|
|
DEFAULT_CLIENT_NAME = "Zulip{name}Webhook"
|
|
DEFAULT_DOC_PATH = "{name}/doc.md"
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
categories: list[str],
|
|
client_name: str | None = None,
|
|
logo: str | None = None,
|
|
secondary_line_text: str | None = None,
|
|
function: str | None = None,
|
|
url: str | None = None,
|
|
display_name: str | None = None,
|
|
doc: str | None = None,
|
|
stream_name: str | None = None,
|
|
legacy: bool = False,
|
|
config_options: Sequence[WebhookConfigOption] = [],
|
|
url_options: Sequence[WebhookUrlOption] = [],
|
|
dir_name: str | None = None,
|
|
) -> None:
|
|
if client_name is None:
|
|
client_name = self.DEFAULT_CLIENT_NAME.format(name=name.title())
|
|
super().__init__(
|
|
name,
|
|
categories,
|
|
client_name=client_name,
|
|
logo=logo,
|
|
secondary_line_text=secondary_line_text,
|
|
display_name=display_name,
|
|
stream_name=stream_name,
|
|
legacy=legacy,
|
|
config_options=config_options,
|
|
url_options=url_options,
|
|
)
|
|
|
|
if function is None:
|
|
function = self.DEFAULT_FUNCTION_PATH.format(name=name)
|
|
self.function_name = function
|
|
|
|
if url is None:
|
|
url = self.DEFAULT_URL.format(name=name)
|
|
self.url = url
|
|
|
|
if doc is None:
|
|
doc = self.DEFAULT_DOC_PATH.format(name=name)
|
|
self.doc = doc
|
|
|
|
if dir_name is None:
|
|
dir_name = self.name
|
|
self.dir_name = dir_name
|
|
|
|
def get_function(self) -> Callable[[HttpRequest], HttpResponseBase]:
|
|
return import_string(self.function_name)
|
|
|
|
@csrf_exempt
|
|
def view(self, request: HttpRequest) -> HttpResponseBase:
|
|
# Lazily load the real view function to improve startup performance.
|
|
function = self.get_function()
|
|
assert function.csrf_exempt # type: ignore[attr-defined] # ensure the above @csrf_exempt is justified
|
|
return function(request)
|
|
|
|
@property
|
|
def url_object(self) -> URLPattern:
|
|
return path(self.url, self.view)
|
|
|
|
|
|
def split_fixture_path(path: str) -> tuple[str, str]:
|
|
path, fixture_name = os.path.split(path)
|
|
fixture_name, _ = os.path.splitext(fixture_name)
|
|
integration_name = os.path.split(os.path.dirname(path))[-1]
|
|
return integration_name, fixture_name
|
|
|
|
|
|
@dataclass
|
|
class WebhookScreenshotConfig:
|
|
fixture_name: str
|
|
image_name: str = "001.png"
|
|
image_dir: str | None = None
|
|
bot_name: str | None = None
|
|
payload_as_query_param: bool = False
|
|
payload_param_name: str = "payload"
|
|
extra_params: dict[str, str] = field(default_factory=dict)
|
|
use_basic_auth: bool = False
|
|
custom_headers: dict[str, str] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass
|
|
class FixturelessScreenshotConfig:
|
|
message: str
|
|
topic: str
|
|
channel: str | None = None
|
|
image_name: str = "001.png"
|
|
image_dir: str | None = None
|
|
bot_name: str | None = None
|
|
|
|
|
|
def get_fixture_path(
|
|
integration: WebhookIntegration, screenshot_config: WebhookScreenshotConfig
|
|
) -> str:
|
|
fixture_dir = os.path.join("zerver", "webhooks", integration.dir_name, "fixtures")
|
|
fixture_path = os.path.join(fixture_dir, screenshot_config.fixture_name)
|
|
return fixture_path
|
|
|
|
|
|
def get_image_path(
|
|
integration: Integration,
|
|
screenshot_config: WebhookScreenshotConfig | FixturelessScreenshotConfig,
|
|
) -> str:
|
|
image_dir = screenshot_config.image_dir or integration.name
|
|
image_name = screenshot_config.image_name
|
|
image_path = os.path.join("static/images/integrations", image_dir, image_name)
|
|
return image_path
|
|
|
|
|
|
class HubotIntegration(Integration):
|
|
GIT_URL_TEMPLATE = "https://github.com/hubot-archive/hubot-{}"
|
|
SECONDARY_LINE_TEXT = "(Hubot script)"
|
|
DOC_PATH = "zerver/integrations/hubot_common.md"
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
categories: list[str],
|
|
display_name: str | None = None,
|
|
logo: str | None = None,
|
|
git_url: str | None = None,
|
|
legacy: bool = False,
|
|
) -> None:
|
|
if git_url is None:
|
|
git_url = self.GIT_URL_TEMPLATE.format(name)
|
|
self.hubot_docs_url = git_url
|
|
|
|
super().__init__(
|
|
name,
|
|
categories,
|
|
logo=logo,
|
|
secondary_line_text=self.SECONDARY_LINE_TEXT,
|
|
display_name=display_name,
|
|
doc=self.DOC_PATH,
|
|
legacy=legacy,
|
|
)
|
|
|
|
|
|
class EmbeddedBotIntegration(Integration):
|
|
"""
|
|
This class acts as a registry for bots verified as safe
|
|
and valid such that these are capable of being deployed on the server.
|
|
"""
|
|
|
|
DEFAULT_CLIENT_NAME = "Zulip{name}EmbeddedBot"
|
|
|
|
def __init__(self, name: str, *args: Any, **kwargs: Any) -> None:
|
|
assert kwargs.get("client_name") is None
|
|
kwargs["client_name"] = self.DEFAULT_CLIENT_NAME.format(name=name.title())
|
|
super().__init__(name, *args, **kwargs)
|
|
|
|
|
|
EMBEDDED_BOTS: list[EmbeddedBotIntegration] = [
|
|
EmbeddedBotIntegration("converter", []),
|
|
EmbeddedBotIntegration("encrypt", []),
|
|
EmbeddedBotIntegration("helloworld", []),
|
|
EmbeddedBotIntegration("virtual_fs", []),
|
|
EmbeddedBotIntegration("giphy", []),
|
|
EmbeddedBotIntegration("followup", []),
|
|
]
|
|
|
|
WEBHOOK_INTEGRATIONS: list[WebhookIntegration] = [
|
|
WebhookIntegration("airbrake", ["monitoring"]),
|
|
WebhookIntegration("airbyte", ["monitoring"]),
|
|
WebhookIntegration(
|
|
"alertmanager",
|
|
["monitoring"],
|
|
display_name="Prometheus Alertmanager",
|
|
logo="images/integrations/logos/prometheus.svg",
|
|
),
|
|
WebhookIntegration("ansibletower", ["deployment"], display_name="Ansible Tower"),
|
|
WebhookIntegration("appfollow", ["customer-support"], display_name="AppFollow"),
|
|
WebhookIntegration("appveyor", ["continuous-integration"], display_name="AppVeyor"),
|
|
WebhookIntegration(
|
|
"azuredevops",
|
|
["version-control"],
|
|
display_name="AzureDevOps",
|
|
url_options=[WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES)],
|
|
),
|
|
WebhookIntegration("beanstalk", ["version-control"], stream_name="commits"),
|
|
WebhookIntegration("basecamp", ["project-management"]),
|
|
WebhookIntegration("beeminder", ["misc"], display_name="Beeminder"),
|
|
WebhookIntegration(
|
|
"bitbucket3",
|
|
["version-control"],
|
|
logo="images/integrations/logos/bitbucket.svg",
|
|
display_name="Bitbucket Server",
|
|
stream_name="bitbucket",
|
|
url_options=[WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES)],
|
|
),
|
|
WebhookIntegration(
|
|
"bitbucket2",
|
|
["version-control"],
|
|
logo="images/integrations/logos/bitbucket.svg",
|
|
display_name="Bitbucket",
|
|
stream_name="bitbucket",
|
|
url_options=[WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES)],
|
|
),
|
|
WebhookIntegration(
|
|
"bitbucket",
|
|
["version-control"],
|
|
display_name="Bitbucket",
|
|
secondary_line_text="(Enterprise)",
|
|
stream_name="commits",
|
|
legacy=True,
|
|
),
|
|
WebhookIntegration("buildbot", ["continuous-integration"]),
|
|
WebhookIntegration("canarytoken", ["monitoring"], display_name="Thinkst Canarytokens"),
|
|
WebhookIntegration("circleci", ["continuous-integration"], display_name="CircleCI"),
|
|
WebhookIntegration("clubhouse", ["project-management"]),
|
|
WebhookIntegration("codeship", ["continuous-integration", "deployment"]),
|
|
WebhookIntegration("crashlytics", ["monitoring"]),
|
|
WebhookIntegration("dialogflow", ["customer-support"]),
|
|
WebhookIntegration("delighted", ["customer-support", "marketing"]),
|
|
WebhookIntegration("dropbox", ["productivity"]),
|
|
WebhookIntegration("errbit", ["monitoring"]),
|
|
WebhookIntegration("flock", ["customer-support"]),
|
|
WebhookIntegration("freshdesk", ["customer-support"]),
|
|
WebhookIntegration("freshping", ["monitoring"]),
|
|
WebhookIntegration("freshstatus", ["monitoring", "customer-support"]),
|
|
WebhookIntegration("front", ["customer-support"]),
|
|
WebhookIntegration(
|
|
"gitea",
|
|
["version-control"],
|
|
stream_name="commits",
|
|
url_options=[WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES)],
|
|
),
|
|
WebhookIntegration(
|
|
"github",
|
|
["version-control"],
|
|
display_name="GitHub",
|
|
function="zerver.webhooks.github.view.api_github_webhook",
|
|
stream_name="github",
|
|
url_options=[
|
|
WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES),
|
|
WebhookUrlOption.build_preset_config(PresetUrlOption.IGNORE_PRIVATE_REPOSITORIES),
|
|
],
|
|
),
|
|
WebhookIntegration(
|
|
"githubsponsors",
|
|
["financial"],
|
|
display_name="GitHub Sponsors",
|
|
logo="images/integrations/logos/github.svg",
|
|
dir_name="github",
|
|
function="zerver.webhooks.github.view.api_github_webhook",
|
|
doc="github/githubsponsors.md",
|
|
stream_name="github",
|
|
),
|
|
WebhookIntegration(
|
|
"gitlab",
|
|
["version-control"],
|
|
display_name="GitLab",
|
|
url_options=[WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES)],
|
|
),
|
|
WebhookIntegration("gocd", ["continuous-integration"], display_name="GoCD"),
|
|
WebhookIntegration(
|
|
"gogs",
|
|
["version-control"],
|
|
stream_name="commits",
|
|
url_options=[WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES)],
|
|
),
|
|
WebhookIntegration("gosquared", ["marketing"], display_name="GoSquared"),
|
|
WebhookIntegration("grafana", ["monitoring"]),
|
|
WebhookIntegration("greenhouse", ["hr"]),
|
|
WebhookIntegration("groove", ["customer-support"]),
|
|
WebhookIntegration("harbor", ["deployment", "productivity"]),
|
|
WebhookIntegration("hellosign", ["productivity", "hr"], display_name="HelloSign"),
|
|
WebhookIntegration("helloworld", ["misc"], display_name="Hello World"),
|
|
WebhookIntegration("heroku", ["deployment"]),
|
|
WebhookIntegration("homeassistant", ["misc"], display_name="Home Assistant"),
|
|
WebhookIntegration(
|
|
"ifttt",
|
|
["meta-integration"],
|
|
function="zerver.webhooks.ifttt.view.api_iftt_app_webhook",
|
|
display_name="IFTTT",
|
|
),
|
|
WebhookIntegration("insping", ["monitoring"]),
|
|
WebhookIntegration("intercom", ["customer-support"]),
|
|
# Avoid collision with jira-plugin's doc "jira/doc.md".
|
|
WebhookIntegration("jira", ["project-management"], doc="jira/jira-doc.md"),
|
|
WebhookIntegration("jotform", ["misc"]),
|
|
WebhookIntegration("json", ["misc"], display_name="JSON formatter"),
|
|
WebhookIntegration("librato", ["monitoring"]),
|
|
WebhookIntegration("lidarr", ["entertainment"]),
|
|
WebhookIntegration("linear", ["project-management"]),
|
|
WebhookIntegration("mention", ["marketing"]),
|
|
WebhookIntegration("netlify", ["continuous-integration", "deployment"]),
|
|
WebhookIntegration("newrelic", ["monitoring"], display_name="New Relic"),
|
|
WebhookIntegration("opencollective", ["financial"], display_name="Open Collective"),
|
|
WebhookIntegration("openproject", ["project-management"], display_name="OpenProject"),
|
|
WebhookIntegration("opensearch", ["monitoring"], display_name="OpenSearch"),
|
|
WebhookIntegration(
|
|
"opsgenie",
|
|
["meta-integration", "monitoring"],
|
|
url_options=[
|
|
WebhookUrlOption(
|
|
name="eu_region",
|
|
label="Use Opsgenie's European service region",
|
|
validator=check_bool,
|
|
)
|
|
],
|
|
),
|
|
WebhookIntegration("pagerduty", ["monitoring"], display_name="PagerDuty"),
|
|
WebhookIntegration("papertrail", ["monitoring"]),
|
|
WebhookIntegration("patreon", ["financial"]),
|
|
WebhookIntegration("pingdom", ["monitoring"]),
|
|
WebhookIntegration("pivotal", ["project-management"], display_name="Pivotal Tracker"),
|
|
WebhookIntegration("radarr", ["entertainment"]),
|
|
WebhookIntegration("raygun", ["monitoring"]),
|
|
WebhookIntegration("reviewboard", ["version-control"], display_name="Review Board"),
|
|
WebhookIntegration(
|
|
"rhodecode",
|
|
["version-control"],
|
|
display_name="RhodeCode",
|
|
url_options=[WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES)],
|
|
),
|
|
WebhookIntegration("rundeck", ["deployment"]),
|
|
WebhookIntegration("semaphore", ["continuous-integration", "deployment"]),
|
|
WebhookIntegration("sentry", ["monitoring"]),
|
|
WebhookIntegration(
|
|
"slack_incoming",
|
|
["communication", "meta-integration"],
|
|
display_name="Slack-compatible webhook",
|
|
logo="images/integrations/logos/slack.svg",
|
|
),
|
|
WebhookIntegration("slack", ["communication"]),
|
|
WebhookIntegration("sonarqube", ["continuous-integration"], display_name="SonarQube"),
|
|
WebhookIntegration("sonarr", ["entertainment"]),
|
|
WebhookIntegration("splunk", ["monitoring"]),
|
|
WebhookIntegration("statuspage", ["customer-support"]),
|
|
WebhookIntegration("stripe", ["financial"]),
|
|
WebhookIntegration("taiga", ["project-management"]),
|
|
WebhookIntegration("teamcity", ["continuous-integration"]),
|
|
WebhookIntegration("thinkst", ["monitoring"]),
|
|
WebhookIntegration("transifex", ["misc"]),
|
|
WebhookIntegration("travis", ["continuous-integration"], display_name="Travis CI"),
|
|
WebhookIntegration("trello", ["project-management"]),
|
|
WebhookIntegration("updown", ["monitoring"]),
|
|
WebhookIntegration("uptimerobot", ["monitoring"], display_name="UptimeRobot"),
|
|
WebhookIntegration("wekan", ["productivity"]),
|
|
WebhookIntegration("wordpress", ["marketing"], display_name="WordPress"),
|
|
WebhookIntegration("zapier", ["meta-integration"]),
|
|
WebhookIntegration("zendesk", ["customer-support"]),
|
|
WebhookIntegration("zabbix", ["monitoring"]),
|
|
]
|
|
|
|
INTEGRATIONS: dict[str, Integration] = {
|
|
"asana": Integration("asana", ["project-management"]),
|
|
"big-blue-button": Integration(
|
|
"big-blue-button", ["communication"], display_name="BigBlueButton"
|
|
),
|
|
"capistrano": Integration("capistrano", ["deployment"], display_name="Capistrano"),
|
|
"discourse": Integration("discourse", ["communication"]),
|
|
"email": Integration("email", ["communication"]),
|
|
"errbot": Integration("errbot", ["meta-integration", "bots"]),
|
|
"giphy": Integration("giphy", ["misc"], display_name="GIPHY"),
|
|
"github-actions": Integration(
|
|
"github-actions", ["continuous-integration"], display_name="GitHub Actions"
|
|
),
|
|
"hubot": Integration("hubot", ["meta-integration", "bots"]),
|
|
"jenkins": Integration("jenkins", ["continuous-integration"]),
|
|
"jitsi": Integration("jitsi", ["communication"], display_name="Jitsi Meet"),
|
|
"mastodon": Integration("mastodon", ["communication"]),
|
|
"notion": Integration("notion", ["productivity"]),
|
|
"onyx": Integration("onyx", ["productivity"], logo="images/integrations/logos/onyx.png"),
|
|
"puppet": Integration("puppet", ["deployment"]),
|
|
"redmine": Integration("redmine", ["project-management"]),
|
|
"zoom": Integration("zoom", ["communication"]),
|
|
}
|
|
|
|
PYTHON_API_INTEGRATIONS: list[PythonAPIIntegration] = [
|
|
PythonAPIIntegration("codebase", ["version-control"]),
|
|
PythonAPIIntegration("git", ["version-control"], stream_name="commits"),
|
|
PythonAPIIntegration(
|
|
"google-calendar", ["productivity"], display_name="Google Calendar", directory_name="google"
|
|
),
|
|
PythonAPIIntegration(
|
|
"irc", ["communication"], display_name="IRC", directory_name="bridge_with_irc"
|
|
),
|
|
PythonAPIIntegration(
|
|
"jira-plugin",
|
|
["project-management"],
|
|
logo="images/integrations/logos/jira.svg",
|
|
secondary_line_text="(locally installed)",
|
|
display_name="Jira",
|
|
directory_name="jira",
|
|
stream_name="jira",
|
|
legacy=True,
|
|
),
|
|
PythonAPIIntegration("matrix", ["communication"], directory_name="bridge_with_matrix"),
|
|
PythonAPIIntegration(
|
|
"mercurial",
|
|
["version-control"],
|
|
display_name="Mercurial (hg)",
|
|
stream_name="commits",
|
|
directory_name="hg",
|
|
),
|
|
PythonAPIIntegration("nagios", ["monitoring"]),
|
|
PythonAPIIntegration(
|
|
"openshift", ["deployment"], display_name="OpenShift", stream_name="deployments"
|
|
),
|
|
PythonAPIIntegration("perforce", ["version-control"]),
|
|
PythonAPIIntegration("rss", ["communication"], display_name="RSS"),
|
|
PythonAPIIntegration("svn", ["version-control"], display_name="Subversion"),
|
|
PythonAPIIntegration("trac", ["project-management"]),
|
|
PythonAPIIntegration(
|
|
"twitter",
|
|
["customer-support", "marketing"],
|
|
# _ needed to get around adblock plus
|
|
logo="images/integrations/logos/twitte_r.svg",
|
|
),
|
|
]
|
|
|
|
BOT_INTEGRATIONS: list[BotIntegration] = [
|
|
BotIntegration("github_detail", ["version-control", "bots"], display_name="GitHub Detail"),
|
|
BotIntegration(
|
|
"xkcd", ["bots", "misc"], display_name="xkcd", logo="images/integrations/logos/xkcd.png"
|
|
),
|
|
]
|
|
|
|
HUBOT_INTEGRATIONS: list[HubotIntegration] = [
|
|
HubotIntegration("assembla", ["version-control", "project-management"]),
|
|
HubotIntegration("bonusly", ["hr"]),
|
|
HubotIntegration("chartbeat", ["marketing"]),
|
|
HubotIntegration("darksky", ["misc"], display_name="Dark Sky"),
|
|
HubotIntegration(
|
|
"instagram",
|
|
["misc"],
|
|
# _ needed to get around adblock plus
|
|
logo="images/integrations/logos/instagra_m.svg",
|
|
),
|
|
HubotIntegration("mailchimp", ["communication", "marketing"]),
|
|
HubotIntegration("google-translate", ["misc"], display_name="Google Translate"),
|
|
HubotIntegration(
|
|
"youtube",
|
|
["misc"],
|
|
display_name="YouTube",
|
|
# _ needed to get around adblock plus
|
|
logo="images/integrations/logos/youtub_e.svg",
|
|
),
|
|
]
|
|
|
|
for python_api_integration in PYTHON_API_INTEGRATIONS:
|
|
INTEGRATIONS[python_api_integration.name] = python_api_integration
|
|
|
|
for hubot_integration in HUBOT_INTEGRATIONS:
|
|
INTEGRATIONS[hubot_integration.name] = hubot_integration
|
|
|
|
for webhook_integration in WEBHOOK_INTEGRATIONS:
|
|
INTEGRATIONS[webhook_integration.name] = webhook_integration
|
|
|
|
for bot_integration in BOT_INTEGRATIONS:
|
|
INTEGRATIONS[bot_integration.name] = bot_integration
|
|
|
|
# Add integrations that don't have automated screenshots here
|
|
NO_SCREENSHOT_WEBHOOKS = {
|
|
"beeminder", # FIXME: fixture's goal.losedate needs to be modified dynamically
|
|
"ifttt", # Docs don't have a screenshot
|
|
"slack_incoming", # Docs don't have a screenshot
|
|
"zapier", # Docs don't have a screenshot
|
|
}
|
|
|
|
|
|
WEBHOOK_SCREENSHOT_CONFIG: dict[str, list[WebhookScreenshotConfig]] = {
|
|
"airbrake": [WebhookScreenshotConfig("error_message.json")],
|
|
"airbyte": [WebhookScreenshotConfig("airbyte_job_payload_success.json")],
|
|
"alertmanager": [
|
|
WebhookScreenshotConfig("alert.json", extra_params={"name": "topic", "desc": "description"})
|
|
],
|
|
"ansibletower": [WebhookScreenshotConfig("job_successful_multiple_hosts.json")],
|
|
"appfollow": [WebhookScreenshotConfig("review.json")],
|
|
"appveyor": [WebhookScreenshotConfig("appveyor_build_success.json")],
|
|
"azuredevops": [WebhookScreenshotConfig("code_push.json")],
|
|
"basecamp": [WebhookScreenshotConfig("doc_active.json")],
|
|
"beanstalk": [
|
|
WebhookScreenshotConfig(
|
|
"git_multiple.json", use_basic_auth=True, payload_as_query_param=True
|
|
)
|
|
],
|
|
# 'beeminder': [WebhookScreenshotConfig('derail_worried.json')],
|
|
"bitbucket": [
|
|
WebhookScreenshotConfig(
|
|
"push.json", "002.png", use_basic_auth=True, payload_as_query_param=True
|
|
)
|
|
],
|
|
"bitbucket2": [
|
|
WebhookScreenshotConfig(
|
|
"issue_created.json", "003.png", "bitbucket", bot_name="Bitbucket Bot"
|
|
)
|
|
],
|
|
"bitbucket3": [
|
|
WebhookScreenshotConfig(
|
|
"repo_push_update_single_branch.json",
|
|
"004.png",
|
|
"bitbucket",
|
|
bot_name="Bitbucket Server Bot",
|
|
)
|
|
],
|
|
"buildbot": [WebhookScreenshotConfig("started.json")],
|
|
"canarytoken": [WebhookScreenshotConfig("canarytoken_real.json")],
|
|
"circleci": [WebhookScreenshotConfig("github_job_completed.json")],
|
|
"clubhouse": [WebhookScreenshotConfig("story_create.json")],
|
|
"codeship": [WebhookScreenshotConfig("error_build.json")],
|
|
"crashlytics": [WebhookScreenshotConfig("issue_message.json")],
|
|
"delighted": [WebhookScreenshotConfig("survey_response_updated_promoter.json")],
|
|
"dialogflow": [
|
|
WebhookScreenshotConfig("weather_app.json", extra_params={"email": "iago@zulip.com"})
|
|
],
|
|
"dropbox": [WebhookScreenshotConfig("file_updated.json")],
|
|
"errbit": [WebhookScreenshotConfig("error_message.json")],
|
|
"flock": [WebhookScreenshotConfig("messages.json")],
|
|
"freshdesk": [
|
|
WebhookScreenshotConfig("ticket_created.json", image_name="004.png", use_basic_auth=True)
|
|
],
|
|
"freshping": [WebhookScreenshotConfig("freshping_check_unreachable.json")],
|
|
"freshstatus": [WebhookScreenshotConfig("freshstatus_incident_open.json")],
|
|
"front": [WebhookScreenshotConfig("inbound_message.json")],
|
|
"gitea": [WebhookScreenshotConfig("pull_request__merged.json")],
|
|
"github": [WebhookScreenshotConfig("push__1_commit.json")],
|
|
"githubsponsors": [WebhookScreenshotConfig("created.json")],
|
|
"gitlab": [WebhookScreenshotConfig("push_hook__push_local_branch_without_commits.json")],
|
|
"gocd": [WebhookScreenshotConfig("pipeline_with_mixed_job_result.json")],
|
|
"gogs": [WebhookScreenshotConfig("pull_request__opened.json")],
|
|
"gosquared": [WebhookScreenshotConfig("traffic_spike.json")],
|
|
"grafana": [WebhookScreenshotConfig("alert_values_v11.json")],
|
|
"greenhouse": [WebhookScreenshotConfig("candidate_stage_change.json")],
|
|
"groove": [WebhookScreenshotConfig("ticket_started.json")],
|
|
"harbor": [WebhookScreenshotConfig("scanning_completed.json")],
|
|
"hellosign": [
|
|
WebhookScreenshotConfig(
|
|
"signatures_signed_by_one_signatory.json",
|
|
payload_as_query_param=True,
|
|
payload_param_name="json",
|
|
)
|
|
],
|
|
"helloworld": [WebhookScreenshotConfig("hello.json")],
|
|
"heroku": [WebhookScreenshotConfig("deploy.txt")],
|
|
"homeassistant": [WebhookScreenshotConfig("reqwithtitle.json")],
|
|
"insping": [WebhookScreenshotConfig("website_state_available.json")],
|
|
"intercom": [WebhookScreenshotConfig("conversation_admin_replied.json")],
|
|
"jira": [WebhookScreenshotConfig("created_v1.json")],
|
|
"jotform": [WebhookScreenshotConfig("screenshot_response.multipart")],
|
|
"json": [WebhookScreenshotConfig("json_github_push__1_commit.json")],
|
|
"librato": [
|
|
WebhookScreenshotConfig("three_conditions_alert.json", payload_as_query_param=True)
|
|
],
|
|
"lidarr": [WebhookScreenshotConfig("lidarr_album_grabbed.json")],
|
|
"linear": [WebhookScreenshotConfig("issue_create_complex.json")],
|
|
"mention": [WebhookScreenshotConfig("webfeeds.json")],
|
|
"netlify": [WebhookScreenshotConfig("deploy_building.json")],
|
|
"newrelic": [WebhookScreenshotConfig("incident_activated_new_default_payload.json")],
|
|
"opencollective": [WebhookScreenshotConfig("one_time_donation.json")],
|
|
"openproject": [WebhookScreenshotConfig("project_created__without_parent.json")],
|
|
"opensearch": [WebhookScreenshotConfig("example_template.txt")],
|
|
"opsgenie": [WebhookScreenshotConfig("addrecipient.json")],
|
|
"pagerduty": [WebhookScreenshotConfig("trigger_v2.json")],
|
|
"papertrail": [WebhookScreenshotConfig("short_post.json", payload_as_query_param=True)],
|
|
"patreon": [WebhookScreenshotConfig("members_pledge_create.json")],
|
|
"pingdom": [WebhookScreenshotConfig("http_up_to_down.json")],
|
|
"pivotal": [WebhookScreenshotConfig("v5_type_changed.json")],
|
|
"radarr": [WebhookScreenshotConfig("radarr_movie_grabbed.json")],
|
|
"raygun": [WebhookScreenshotConfig("new_error.json")],
|
|
"reviewboard": [WebhookScreenshotConfig("review_request_published.json")],
|
|
"rhodecode": [WebhookScreenshotConfig("push.json")],
|
|
"rundeck": [WebhookScreenshotConfig("start.json")],
|
|
"semaphore": [WebhookScreenshotConfig("pull_request.json")],
|
|
"sentry": [WebhookScreenshotConfig("event_for_exception_python.json")],
|
|
"slack": [WebhookScreenshotConfig("message_with_normal_text.json")],
|
|
"sonarqube": [WebhookScreenshotConfig("error.json")],
|
|
"sonarr": [WebhookScreenshotConfig("sonarr_episode_grabbed.json")],
|
|
"splunk": [WebhookScreenshotConfig("search_one_result.json")],
|
|
"statuspage": [WebhookScreenshotConfig("incident_created.json")],
|
|
"stripe": [WebhookScreenshotConfig("charge_succeeded__card.json")],
|
|
"taiga": [WebhookScreenshotConfig("userstory_changed_status.json")],
|
|
"teamcity": [WebhookScreenshotConfig("success.json")],
|
|
"thinkst": [WebhookScreenshotConfig("canary_consolidated_port_scan.json")],
|
|
"transifex": [
|
|
WebhookScreenshotConfig(
|
|
"",
|
|
extra_params={
|
|
"project": "Zulip Mobile",
|
|
"language": "en",
|
|
"resource": "file",
|
|
"event": "review_completed",
|
|
"reviewed": "100",
|
|
},
|
|
)
|
|
],
|
|
"travis": [WebhookScreenshotConfig("build.json", payload_as_query_param=True)],
|
|
"trello": [WebhookScreenshotConfig("adding_comment_to_card.json")],
|
|
"updown": [WebhookScreenshotConfig("check_multiple_events.json")],
|
|
"uptimerobot": [WebhookScreenshotConfig("uptimerobot_monitor_up.json")],
|
|
"wekan": [WebhookScreenshotConfig("add_comment.json")],
|
|
"wordpress": [WebhookScreenshotConfig("publish_post.txt", "wordpress_post_created.png")],
|
|
"zabbix": [WebhookScreenshotConfig("zabbix_alert.json")],
|
|
"zendesk": [
|
|
WebhookScreenshotConfig(
|
|
"",
|
|
use_basic_auth=True,
|
|
extra_params={
|
|
"ticket_title": "Hardware Ecosystem Compatibility Inquiry",
|
|
"ticket_id": "4837",
|
|
"message": "Hi, I am planning to purchase the X5000 smartphone and want to ensure compatibility with my existing devices - WDX10 wireless earbuds and Z600 smartwatch. Are there any known issues?",
|
|
},
|
|
)
|
|
],
|
|
}
|
|
|
|
FIXTURELESS_SCREENSHOT_CONFIG: dict[str, list[FixturelessScreenshotConfig]] = {}
|
|
|
|
DOC_SCREENSHOT_CONFIG: dict[
|
|
str, list[WebhookScreenshotConfig] | list[FixturelessScreenshotConfig]
|
|
] = {
|
|
**WEBHOOK_SCREENSHOT_CONFIG,
|
|
**FIXTURELESS_SCREENSHOT_CONFIG,
|
|
}
|
|
|
|
|
|
def get_all_event_types_for_integration(integration: Integration) -> list[str] | None:
|
|
integration = INTEGRATIONS[integration.name]
|
|
if isinstance(integration, WebhookIntegration):
|
|
if integration.name == "githubsponsors":
|
|
return import_string("zerver.webhooks.github.view.SPONSORS_EVENT_TYPES")
|
|
function = integration.get_function()
|
|
if hasattr(function, "_all_event_types"):
|
|
return function._all_event_types
|
|
return None
|