mirror of
https://github.com/zulip/zulip.git
synced 2025-10-22 20:42:14 +00:00
Temporarily exclude Git from `test_integration_doc_endpoints`, until the doc is updated. The screenshot for this integration is added separately since this adds a new screenshot for an integration whose doc is in the python-zulip-api repo. Since this breaks the sync between the docs and the screenshots, an exclusion is added to make the tests pass.
939 lines
37 KiB
Python
939 lines
37 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
|
|
from zerver.webhooks import fixtureless_integrations
|
|
|
|
"""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,
|
|
"video-calling": gettext_lazy("Video calling"),
|
|
"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"),
|
|
}
|
|
|
|
# Can also be computed from INTEGRATIONS by removing entries from
|
|
# WEBHOOK_INTEGRATIONS and NO_SCREENSHOT_CONFIG, but defined explicitly to
|
|
# avoid circular dependency
|
|
FIXTURELESS_INTEGRATIONS_WITH_SCREENSHOTS: list[str] = [
|
|
"asana",
|
|
"capistrano",
|
|
"codebase",
|
|
"discourse",
|
|
"git",
|
|
"github-actions",
|
|
"google-calendar",
|
|
"jenkins",
|
|
"jira-plugin",
|
|
"mastodon",
|
|
"mercurial",
|
|
"nagios",
|
|
"notion",
|
|
"openshift",
|
|
"perforce",
|
|
"puppet",
|
|
"redmine",
|
|
"rss",
|
|
"svn",
|
|
"trac",
|
|
]
|
|
FIXTURELESS_SCREENSHOT_CONTENT: dict[str, list[fixtureless_integrations.ScreenshotContent]] = {
|
|
key: [getattr(fixtureless_integrations, key.upper().replace("-", "_"))]
|
|
for key in FIXTURELESS_INTEGRATIONS_WITH_SCREENSHOTS
|
|
}
|
|
|
|
|
|
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.{dir_name}.view.api_{dir_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 dir_name is None:
|
|
dir_name = self.name
|
|
self.dir_name = dir_name
|
|
|
|
if function is None:
|
|
function = self.DEFAULT_FUNCTION_PATH.format(dir_name=dir_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
|
|
|
|
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",
|
|
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",
|
|
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"], 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", ["video-calling", "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",
|
|
stream_name="github-actions updates",
|
|
),
|
|
"hubot": Integration("hubot", ["meta-integration", "bots"]),
|
|
"jenkins": Integration("jenkins", ["continuous-integration"]),
|
|
"jitsi": Integration("jitsi", ["video-calling", "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", ["video-calling", "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 webhook integrations that don't have automated screenshots here
|
|
NO_SCREENSHOT_WEBHOOKS = (
|
|
# FIXME: fixture's goal.losedate needs to be modified dynamically
|
|
{"beeminder"}
|
|
# Meta integrations - Docs won't have a screenshot
|
|
| {"ifttt", "slack_incoming", "zapier"}
|
|
)
|
|
|
|
hubot_integration_names = {integration.name for integration in HUBOT_INTEGRATIONS}
|
|
|
|
# Add fixtureless integrations that don't have automated screenshots here
|
|
NO_SCREENSHOT_CONFIG = (
|
|
# Outgoing integrations - Docs won't have a screenshot
|
|
{"email", "onyx"}
|
|
# Video call integrations - Docs won't have a screenshot
|
|
| {"big-blue-button", "jitsi", "zoom"}
|
|
# Integrations that require screenshots of message threads - support is yet to be added
|
|
| {
|
|
"errbot",
|
|
"github_detail",
|
|
"hubot",
|
|
"irc",
|
|
# Also requires a screenshot on the Matrix side of the bridge
|
|
"matrix",
|
|
"xkcd",
|
|
}
|
|
| {
|
|
# Doc doesn't have a screenshot
|
|
"giphy",
|
|
# the integration is planned to be removed
|
|
"twitter",
|
|
}
|
|
| NO_SCREENSHOT_WEBHOOKS
|
|
| hubot_integration_names
|
|
)
|
|
|
|
|
|
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]] = {}
|
|
for integration, screenshots_contents in FIXTURELESS_SCREENSHOT_CONTENT.items():
|
|
FIXTURELESS_SCREENSHOT_CONFIG[integration] = [
|
|
FixturelessScreenshotConfig(screenshot_content["content"], screenshot_content["topic"])
|
|
for screenshot_content in screenshots_contents
|
|
]
|
|
|
|
FIXTURELESS_SCREENSHOT_CONFIG_OPTIONAL_FIELDS = {
|
|
"mercurial": {"image_dir": "hg"},
|
|
"jenkins": {"image_name": "004.png"},
|
|
"google-calendar": {"image_name": "003.png", "image_dir": "google/calendar"},
|
|
}
|
|
|
|
for integration, fields in FIXTURELESS_SCREENSHOT_CONFIG_OPTIONAL_FIELDS.items():
|
|
assert integration in FIXTURELESS_SCREENSHOT_CONFIG
|
|
for field_name, value in fields.items():
|
|
# Assume a single screenshot config for each integration
|
|
setattr(FIXTURELESS_SCREENSHOT_CONFIG[integration][0], field_name, value)
|
|
|
|
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
|