We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
portico/docs: Improve OpenAPI descriptions and titles.
Followups to https://github.com/zulip/zulip/pull/11045.
- [ ] Currently we only use the first paragraph of text for the description. It would be better to concatenate the first few paragraphs, and truncate. Basically it's just using bs.find_all('p') and joining things together in a loop until it gets too long. 
One catch is that is that we probably want to stop at the first non-paragraph block so we don't cross a list or something.
- [ ] Currently, we've just tested our openAPI things with /api and /help. We should extend to the /integrations pages, and also figure out what we want to do for the top-10 major portico pages (especially /features, /why-zulip, and /for/open-source), which could benefit from a similar treatment.

To work on this I would start by skimming the code from #11045. 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `zerver/views/documentation.py`
Content:
```
1 from typing import Any, Dict, Tuple
2 from collections import OrderedDict
3 from django.views.generic import TemplateView
4 from django.conf import settings
5 from django.http import HttpRequest, HttpResponse, HttpResponseNotFound
6 from django.template import loader
7 
8 import os
9 import random
10 import re
11 
12 from zerver.lib.integrations import CATEGORIES, INTEGRATIONS, HubotIntegration, \
13     WebhookIntegration, EmailIntegration
14 from zerver.lib.request import has_request_variables, REQ
15 from zerver.lib.subdomains import get_subdomain
16 from zerver.models import Realm
17 from zerver.templatetags.app_filters import render_markdown_path
18 from zerver.context_processors import zulip_default_context
19 
20 def add_api_uri_context(context: Dict[str, Any], request: HttpRequest) -> None:
21     context.update(zulip_default_context(request))
22 
23     subdomain = get_subdomain(request)
24     if (subdomain != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN
25             or not settings.ROOT_DOMAIN_LANDING_PAGE):
26         display_subdomain = subdomain
27         html_settings_links = True
28     else:
29         display_subdomain = 'yourZulipDomain'
30         html_settings_links = False
31 
32     display_host = Realm.host_for_subdomain(display_subdomain)
33     api_url_scheme_relative = display_host + "/api"
34     api_url = settings.EXTERNAL_URI_SCHEME + api_url_scheme_relative
35     zulip_url = settings.EXTERNAL_URI_SCHEME + display_host
36 
37     context['external_uri_scheme'] = settings.EXTERNAL_URI_SCHEME
38     context['api_url'] = api_url
39     context['api_url_scheme_relative'] = api_url_scheme_relative
40     context['zulip_url'] = zulip_url
41 
42     context["html_settings_links"] = html_settings_links
43     if html_settings_links:
44         settings_html = '<a href="/#settings">Zulip settings page</a>'
45         subscriptions_html = '<a target="_blank" href="/#streams">streams page</a>'
46     else:
47         settings_html = 'Zulip settings page'
48         subscriptions_html = 'streams page'
49     context['settings_html'] = settings_html
50     context['subscriptions_html'] = subscriptions_html
51 
52 class ApiURLView(TemplateView):
53     def get_context_data(self, **kwargs: Any) -> Dict[str, str]:
54         context = super().get_context_data(**kwargs)
55         add_api_uri_context(context, self.request)
56         return context
57 
58 class APIView(ApiURLView):
59     template_name = 'zerver/api.html'
60 
61 
62 class MarkdownDirectoryView(ApiURLView):
63     path_template = ""
64 
65     def get_path(self, article: str) -> Tuple[str, int]:
66         http_status = 200
67         if article == "":
68             article = "index"
69         elif article == "include/sidebar_index":
70             pass
71         elif "/" in article:
72             article = "missing"
73             http_status = 404
74         elif len(article) > 100 or not re.match('^[0-9a-zA-Z_-]+$', article):
75             article = "missing"
76             http_status = 404
77 
78         path = self.path_template % (article,)
79         try:
80             loader.get_template(path)
81             return (path, http_status)
82         except loader.TemplateDoesNotExist:
83             return (self.path_template % ("missing",), 404)
84 
85     def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
86         article = kwargs["article"]
87         context = super().get_context_data()  # type: Dict[str, Any]
88         (context["article"], http_status_ignored) = self.get_path(article)
89 
90         # For disabling the "Back to home" on the homepage
91         context["not_index_page"] = not context["article"].endswith("/index.md")
92         if self.path_template == '/zerver/help/%s.md':
93             context["page_is_help_center"] = True
94             context["doc_root"] = "/help/"
95             (sidebar_index, http_status_ignored) = self.get_path("include/sidebar_index")
96             # We want the sliding/collapsing behavior for /help pages only
97             sidebar_class = "sidebar slide"
98             title_base = "Zulip Help Center"
99         else:
100             context["page_is_api_center"] = True
101             context["doc_root"] = "/api/"
102             (sidebar_index, http_status_ignored) = self.get_path("sidebar_index")
103             sidebar_class = "sidebar"
104             title_base = "Zulip API Documentation"
105 
106         # The following is a somewhat hacky approach to extract titles from articles.
107         # Hack: `context["article"] has a leading `/`, so we use + to add directories.
108         article_path = os.path.join(settings.DEPLOY_ROOT, 'templates') + context["article"]
109         if os.path.exists(article_path):
110             with open(article_path) as article_file:
111                 first_line = article_file.readlines()[0]
112             # Strip the header and then use the first line to get the article title
113             article_title = first_line.strip().lstrip("# ")
114             if context["not_index_page"]:
115                 context["OPEN_GRAPH_TITLE"] = "%s (%s)" % (article_title, title_base)
116             else:
117                 context["OPEN_GRAPH_TITLE"] = title_base
118             self.request.placeholder_open_graph_description = (
119                 "REPLACMENT_OPEN_GRAPH_DESCRIPTION_%s" % (int(2**24 * random.random()),))
120             context["OPEN_GRAPH_DESCRIPTION"] = self.request.placeholder_open_graph_description
121 
122         context["sidebar_index"] = sidebar_index
123         context["sidebar_class"] = sidebar_class
124         # An "article" might require the api_uri_context to be rendered
125         api_uri_context = {}  # type: Dict[str, Any]
126         add_api_uri_context(api_uri_context, self.request)
127         api_uri_context["run_content_validators"] = True
128         context["api_uri_context"] = api_uri_context
129         return context
130 
131     def get(self, request: HttpRequest, article: str="") -> HttpResponse:
132         (path, http_status) = self.get_path(article)
133         result = super().get(self, article=article)
134         if http_status != 200:
135             result.status_code = http_status
136         return result
137 
138 def add_integrations_context(context: Dict[str, Any]) -> None:
139     alphabetical_sorted_categories = OrderedDict(sorted(CATEGORIES.items()))
140     alphabetical_sorted_integration = OrderedDict(sorted(INTEGRATIONS.items()))
141     enabled_integrations_count = len(list(filter(lambda v: v.is_enabled(), INTEGRATIONS.values())))
142     # Subtract 1 so saying "Over X integrations" is correct. Then,
143     # round down to the nearest multiple of 10.
144     integrations_count_display = ((enabled_integrations_count - 1) // 10) * 10
145     context['categories_dict'] = alphabetical_sorted_categories
146     context['integrations_dict'] = alphabetical_sorted_integration
147     context['integrations_count_display'] = integrations_count_display
148 
149 def add_integrations_open_graph_context(context: Dict[str, Any], request: HttpRequest) -> None:
150     path_name = request.path.rstrip('/').split('/')[-1]
151     description = ('Zulip comes with over a hundred native integrations out of the box, '
152                    'and integrates with Zapier, IFTTT, and Hubot to provide hundreds more. '
153                    'Connect the apps you use everyday to Zulip.')
154 
155     if path_name in INTEGRATIONS:
156         integration = INTEGRATIONS[path_name]
157         context['OPEN_GRAPH_TITLE'] = 'Connect {name} to Zulip'.format(name=integration.display_name)
158         context['OPEN_GRAPH_DESCRIPTION'] = description
159 
160     elif path_name in CATEGORIES:
161         category = CATEGORIES[path_name]
162         context['OPEN_GRAPH_TITLE'] = 'Connect your {category} tools to Zulip'.format(category=category)
163         context['OPEN_GRAPH_DESCRIPTION'] = description
164 
165 class IntegrationView(ApiURLView):
166     template_name = 'zerver/integrations/index.html'
167 
168     def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
169         context = super().get_context_data(**kwargs)  # type: Dict[str, Any]
170         add_integrations_context(context)
171         add_integrations_open_graph_context(context, self.request)
172         return context
173 
174 
175 @has_request_variables
176 def integration_doc(request: HttpRequest, integration_name: str=REQ(default=None)) -> HttpResponse:
177     if not request.is_ajax():
178         return HttpResponseNotFound()
179     try:
180         integration = INTEGRATIONS[integration_name]
181     except KeyError:
182         return HttpResponseNotFound()
183 
184     context = {}  # type: Dict[str, Any]
185     add_api_uri_context(context, request)
186 
187     context['integration_name'] = integration.name
188     context['integration_display_name'] = integration.display_name
189     if hasattr(integration, 'stream_name'):
190         context['recommended_stream_name'] = integration.stream_name
191     if isinstance(integration, WebhookIntegration):
192         context['integration_url'] = integration.url[3:]
193     if isinstance(integration, HubotIntegration):
194         context['hubot_docs_url'] = integration.hubot_docs_url
195     if isinstance(integration, EmailIntegration):
196         context['email_gateway_example'] = settings.EMAIL_GATEWAY_EXAMPLE
197 
198     doc_html_str = render_markdown_path(integration.doc, context)
199 
200     return HttpResponse(doc_html_str)
201
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/zerver/views/documentation.py b/zerver/views/documentation.py
--- a/zerver/views/documentation.py
+++ b/zerver/views/documentation.py
@@ -162,6 +162,10 @@
         context['OPEN_GRAPH_TITLE'] = 'Connect your {category} tools to Zulip'.format(category=category)
         context['OPEN_GRAPH_DESCRIPTION'] = description
 
+    elif path_name == 'integrations':
+        context['OPEN_GRAPH_TITLE'] = 'Connect the tools you use to Zulip'
+        context['OPEN_GRAPH_DESCRIPTION'] = description
+
 class IntegrationView(ApiURLView):
     template_name = 'zerver/integrations/index.html'

{"golden_diff": "diff --git a/zerver/views/documentation.py b/zerver/views/documentation.py\n--- a/zerver/views/documentation.py\n+++ b/zerver/views/documentation.py\n@@ -162,6 +162,10 @@\n         context['OPEN_GRAPH_TITLE'] = 'Connect your {category} tools to Zulip'.format(category=category)\n         context['OPEN_GRAPH_DESCRIPTION'] = description\n \n+    elif path_name == 'integrations':\n+        context['OPEN_GRAPH_TITLE'] = 'Connect the tools you use to Zulip'\n+        context['OPEN_GRAPH_DESCRIPTION'] = description\n+\n class IntegrationView(ApiURLView):\n     template_name = 'zerver/integrations/index.html'\n", "issue": "portico/docs: Improve OpenAPI descriptions and titles.\nFollowups to https://github.com/zulip/zulip/pull/11045.\r\n- [ ] Currently we only use the first paragraph of text for the description. It would be better to concatenate the first few paragraphs, and truncate. Basically it's just using bs.find_all('p') and joining things together in a loop until it gets too long. \r\nOne catch is that is that we probably want to stop at the first non-paragraph block so we don't cross a list or something.\r\n- [ ] Currently, we've just tested our openAPI things with /api and /help. We should extend to the /integrations pages, and also figure out what we want to do for the top-10 major portico pages (especially /features, /why-zulip, and /for/open-source), which could benefit from a similar treatment.\r\n\r\nTo work on this I would start by skimming the code from #11045. \n", "before_files": [{"content": "from typing import Any, Dict, Tuple\nfrom collections import OrderedDict\nfrom django.views.generic import TemplateView\nfrom django.conf import settings\nfrom django.http import HttpRequest, HttpResponse, HttpResponseNotFound\nfrom django.template import loader\n\nimport os\nimport random\nimport re\n\nfrom zerver.lib.integrations import CATEGORIES, INTEGRATIONS, HubotIntegration, \\\n    WebhookIntegration, EmailIntegration\nfrom zerver.lib.request import has_request_variables, REQ\nfrom zerver.lib.subdomains import get_subdomain\nfrom zerver.models import Realm\nfrom zerver.templatetags.app_filters import render_markdown_path\nfrom zerver.context_processors import zulip_default_context\n\ndef add_api_uri_context(context: Dict[str, Any], request: HttpRequest) -> None:\n    context.update(zulip_default_context(request))\n\n    subdomain = get_subdomain(request)\n    if (subdomain != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN\n            or not settings.ROOT_DOMAIN_LANDING_PAGE):\n        display_subdomain = subdomain\n        html_settings_links = True\n    else:\n        display_subdomain = 'yourZulipDomain'\n        html_settings_links = False\n\n    display_host = Realm.host_for_subdomain(display_subdomain)\n    api_url_scheme_relative = display_host + \"/api\"\n    api_url = settings.EXTERNAL_URI_SCHEME + api_url_scheme_relative\n    zulip_url = settings.EXTERNAL_URI_SCHEME + display_host\n\n    context['external_uri_scheme'] = settings.EXTERNAL_URI_SCHEME\n    context['api_url'] = api_url\n    context['api_url_scheme_relative'] = api_url_scheme_relative\n    context['zulip_url'] = zulip_url\n\n    context[\"html_settings_links\"] = html_settings_links\n    if html_settings_links:\n        settings_html = '<a href=\"/#settings\">Zulip settings page</a>'\n        subscriptions_html = '<a target=\"_blank\" href=\"/#streams\">streams page</a>'\n    else:\n        settings_html = 'Zulip settings page'\n        subscriptions_html = 'streams page'\n    context['settings_html'] = settings_html\n    context['subscriptions_html'] = subscriptions_html\n\nclass ApiURLView(TemplateView):\n    def get_context_data(self, **kwargs: Any) -> Dict[str, str]:\n        context = super().get_context_data(**kwargs)\n        add_api_uri_context(context, self.request)\n        return context\n\nclass APIView(ApiURLView):\n    template_name = 'zerver/api.html'\n\n\nclass MarkdownDirectoryView(ApiURLView):\n    path_template = \"\"\n\n    def get_path(self, article: str) -> Tuple[str, int]:\n        http_status = 200\n        if article == \"\":\n            article = \"index\"\n        elif article == \"include/sidebar_index\":\n            pass\n        elif \"/\" in article:\n            article = \"missing\"\n            http_status = 404\n        elif len(article) > 100 or not re.match('^[0-9a-zA-Z_-]+$', article):\n            article = \"missing\"\n            http_status = 404\n\n        path = self.path_template % (article,)\n        try:\n            loader.get_template(path)\n            return (path, http_status)\n        except loader.TemplateDoesNotExist:\n            return (self.path_template % (\"missing\",), 404)\n\n    def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:\n        article = kwargs[\"article\"]\n        context = super().get_context_data()  # type: Dict[str, Any]\n        (context[\"article\"], http_status_ignored) = self.get_path(article)\n\n        # For disabling the \"Back to home\" on the homepage\n        context[\"not_index_page\"] = not context[\"article\"].endswith(\"/index.md\")\n        if self.path_template == '/zerver/help/%s.md':\n            context[\"page_is_help_center\"] = True\n            context[\"doc_root\"] = \"/help/\"\n            (sidebar_index, http_status_ignored) = self.get_path(\"include/sidebar_index\")\n            # We want the sliding/collapsing behavior for /help pages only\n            sidebar_class = \"sidebar slide\"\n            title_base = \"Zulip Help Center\"\n        else:\n            context[\"page_is_api_center\"] = True\n            context[\"doc_root\"] = \"/api/\"\n            (sidebar_index, http_status_ignored) = self.get_path(\"sidebar_index\")\n            sidebar_class = \"sidebar\"\n            title_base = \"Zulip API Documentation\"\n\n        # The following is a somewhat hacky approach to extract titles from articles.\n        # Hack: `context[\"article\"] has a leading `/`, so we use + to add directories.\n        article_path = os.path.join(settings.DEPLOY_ROOT, 'templates') + context[\"article\"]\n        if os.path.exists(article_path):\n            with open(article_path) as article_file:\n                first_line = article_file.readlines()[0]\n            # Strip the header and then use the first line to get the article title\n            article_title = first_line.strip().lstrip(\"# \")\n            if context[\"not_index_page\"]:\n                context[\"OPEN_GRAPH_TITLE\"] = \"%s (%s)\" % (article_title, title_base)\n            else:\n                context[\"OPEN_GRAPH_TITLE\"] = title_base\n            self.request.placeholder_open_graph_description = (\n                \"REPLACMENT_OPEN_GRAPH_DESCRIPTION_%s\" % (int(2**24 * random.random()),))\n            context[\"OPEN_GRAPH_DESCRIPTION\"] = self.request.placeholder_open_graph_description\n\n        context[\"sidebar_index\"] = sidebar_index\n        context[\"sidebar_class\"] = sidebar_class\n        # An \"article\" might require the api_uri_context to be rendered\n        api_uri_context = {}  # type: Dict[str, Any]\n        add_api_uri_context(api_uri_context, self.request)\n        api_uri_context[\"run_content_validators\"] = True\n        context[\"api_uri_context\"] = api_uri_context\n        return context\n\n    def get(self, request: HttpRequest, article: str=\"\") -> HttpResponse:\n        (path, http_status) = self.get_path(article)\n        result = super().get(self, article=article)\n        if http_status != 200:\n            result.status_code = http_status\n        return result\n\ndef add_integrations_context(context: Dict[str, Any]) -> None:\n    alphabetical_sorted_categories = OrderedDict(sorted(CATEGORIES.items()))\n    alphabetical_sorted_integration = OrderedDict(sorted(INTEGRATIONS.items()))\n    enabled_integrations_count = len(list(filter(lambda v: v.is_enabled(), INTEGRATIONS.values())))\n    # Subtract 1 so saying \"Over X integrations\" is correct. Then,\n    # round down to the nearest multiple of 10.\n    integrations_count_display = ((enabled_integrations_count - 1) // 10) * 10\n    context['categories_dict'] = alphabetical_sorted_categories\n    context['integrations_dict'] = alphabetical_sorted_integration\n    context['integrations_count_display'] = integrations_count_display\n\ndef add_integrations_open_graph_context(context: Dict[str, Any], request: HttpRequest) -> None:\n    path_name = request.path.rstrip('/').split('/')[-1]\n    description = ('Zulip comes with over a hundred native integrations out of the box, '\n                   'and integrates with Zapier, IFTTT, and Hubot to provide hundreds more. '\n                   'Connect the apps you use everyday to Zulip.')\n\n    if path_name in INTEGRATIONS:\n        integration = INTEGRATIONS[path_name]\n        context['OPEN_GRAPH_TITLE'] = 'Connect {name} to Zulip'.format(name=integration.display_name)\n        context['OPEN_GRAPH_DESCRIPTION'] = description\n\n    elif path_name in CATEGORIES:\n        category = CATEGORIES[path_name]\n        context['OPEN_GRAPH_TITLE'] = 'Connect your {category} tools to Zulip'.format(category=category)\n        context['OPEN_GRAPH_DESCRIPTION'] = description\n\nclass IntegrationView(ApiURLView):\n    template_name = 'zerver/integrations/index.html'\n\n    def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:\n        context = super().get_context_data(**kwargs)  # type: Dict[str, Any]\n        add_integrations_context(context)\n        add_integrations_open_graph_context(context, self.request)\n        return context\n\n\n@has_request_variables\ndef integration_doc(request: HttpRequest, integration_name: str=REQ(default=None)) -> HttpResponse:\n    if not request.is_ajax():\n        return HttpResponseNotFound()\n    try:\n        integration = INTEGRATIONS[integration_name]\n    except KeyError:\n        return HttpResponseNotFound()\n\n    context = {}  # type: Dict[str, Any]\n    add_api_uri_context(context, request)\n\n    context['integration_name'] = integration.name\n    context['integration_display_name'] = integration.display_name\n    if hasattr(integration, 'stream_name'):\n        context['recommended_stream_name'] = integration.stream_name\n    if isinstance(integration, WebhookIntegration):\n        context['integration_url'] = integration.url[3:]\n    if isinstance(integration, HubotIntegration):\n        context['hubot_docs_url'] = integration.hubot_docs_url\n    if isinstance(integration, EmailIntegration):\n        context['email_gateway_example'] = settings.EMAIL_GATEWAY_EXAMPLE\n\n    doc_html_str = render_markdown_path(integration.doc, context)\n\n    return HttpResponse(doc_html_str)\n", "path": "zerver/views/documentation.py"}], "after_files": [{"content": "from typing import Any, Dict, Tuple\nfrom collections import OrderedDict\nfrom django.views.generic import TemplateView\nfrom django.conf import settings\nfrom django.http import HttpRequest, HttpResponse, HttpResponseNotFound\nfrom django.template import loader\n\nimport os\nimport random\nimport re\n\nfrom zerver.lib.integrations import CATEGORIES, INTEGRATIONS, HubotIntegration, \\\n    WebhookIntegration, EmailIntegration\nfrom zerver.lib.request import has_request_variables, REQ\nfrom zerver.lib.subdomains import get_subdomain\nfrom zerver.models import Realm\nfrom zerver.templatetags.app_filters import render_markdown_path\nfrom zerver.context_processors import zulip_default_context\n\ndef add_api_uri_context(context: Dict[str, Any], request: HttpRequest) -> None:\n    context.update(zulip_default_context(request))\n\n    subdomain = get_subdomain(request)\n    if (subdomain != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN\n            or not settings.ROOT_DOMAIN_LANDING_PAGE):\n        display_subdomain = subdomain\n        html_settings_links = True\n    else:\n        display_subdomain = 'yourZulipDomain'\n        html_settings_links = False\n\n    display_host = Realm.host_for_subdomain(display_subdomain)\n    api_url_scheme_relative = display_host + \"/api\"\n    api_url = settings.EXTERNAL_URI_SCHEME + api_url_scheme_relative\n    zulip_url = settings.EXTERNAL_URI_SCHEME + display_host\n\n    context['external_uri_scheme'] = settings.EXTERNAL_URI_SCHEME\n    context['api_url'] = api_url\n    context['api_url_scheme_relative'] = api_url_scheme_relative\n    context['zulip_url'] = zulip_url\n\n    context[\"html_settings_links\"] = html_settings_links\n    if html_settings_links:\n        settings_html = '<a href=\"/#settings\">Zulip settings page</a>'\n        subscriptions_html = '<a target=\"_blank\" href=\"/#streams\">streams page</a>'\n    else:\n        settings_html = 'Zulip settings page'\n        subscriptions_html = 'streams page'\n    context['settings_html'] = settings_html\n    context['subscriptions_html'] = subscriptions_html\n\nclass ApiURLView(TemplateView):\n    def get_context_data(self, **kwargs: Any) -> Dict[str, str]:\n        context = super().get_context_data(**kwargs)\n        add_api_uri_context(context, self.request)\n        return context\n\nclass APIView(ApiURLView):\n    template_name = 'zerver/api.html'\n\n\nclass MarkdownDirectoryView(ApiURLView):\n    path_template = \"\"\n\n    def get_path(self, article: str) -> Tuple[str, int]:\n        http_status = 200\n        if article == \"\":\n            article = \"index\"\n        elif article == \"include/sidebar_index\":\n            pass\n        elif \"/\" in article:\n            article = \"missing\"\n            http_status = 404\n        elif len(article) > 100 or not re.match('^[0-9a-zA-Z_-]+$', article):\n            article = \"missing\"\n            http_status = 404\n\n        path = self.path_template % (article,)\n        try:\n            loader.get_template(path)\n            return (path, http_status)\n        except loader.TemplateDoesNotExist:\n            return (self.path_template % (\"missing\",), 404)\n\n    def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:\n        article = kwargs[\"article\"]\n        context = super().get_context_data()  # type: Dict[str, Any]\n        (context[\"article\"], http_status_ignored) = self.get_path(article)\n\n        # For disabling the \"Back to home\" on the homepage\n        context[\"not_index_page\"] = not context[\"article\"].endswith(\"/index.md\")\n        if self.path_template == '/zerver/help/%s.md':\n            context[\"page_is_help_center\"] = True\n            context[\"doc_root\"] = \"/help/\"\n            (sidebar_index, http_status_ignored) = self.get_path(\"include/sidebar_index\")\n            # We want the sliding/collapsing behavior for /help pages only\n            sidebar_class = \"sidebar slide\"\n            title_base = \"Zulip Help Center\"\n        else:\n            context[\"page_is_api_center\"] = True\n            context[\"doc_root\"] = \"/api/\"\n            (sidebar_index, http_status_ignored) = self.get_path(\"sidebar_index\")\n            sidebar_class = \"sidebar\"\n            title_base = \"Zulip API Documentation\"\n\n        # The following is a somewhat hacky approach to extract titles from articles.\n        # Hack: `context[\"article\"] has a leading `/`, so we use + to add directories.\n        article_path = os.path.join(settings.DEPLOY_ROOT, 'templates') + context[\"article\"]\n        if os.path.exists(article_path):\n            with open(article_path) as article_file:\n                first_line = article_file.readlines()[0]\n            # Strip the header and then use the first line to get the article title\n            article_title = first_line.strip().lstrip(\"# \")\n            if context[\"not_index_page\"]:\n                context[\"OPEN_GRAPH_TITLE\"] = \"%s (%s)\" % (article_title, title_base)\n            else:\n                context[\"OPEN_GRAPH_TITLE\"] = title_base\n            self.request.placeholder_open_graph_description = (\n                \"REPLACMENT_OPEN_GRAPH_DESCRIPTION_%s\" % (int(2**24 * random.random()),))\n            context[\"OPEN_GRAPH_DESCRIPTION\"] = self.request.placeholder_open_graph_description\n\n        context[\"sidebar_index\"] = sidebar_index\n        context[\"sidebar_class\"] = sidebar_class\n        # An \"article\" might require the api_uri_context to be rendered\n        api_uri_context = {}  # type: Dict[str, Any]\n        add_api_uri_context(api_uri_context, self.request)\n        api_uri_context[\"run_content_validators\"] = True\n        context[\"api_uri_context\"] = api_uri_context\n        return context\n\n    def get(self, request: HttpRequest, article: str=\"\") -> HttpResponse:\n        (path, http_status) = self.get_path(article)\n        result = super().get(self, article=article)\n        if http_status != 200:\n            result.status_code = http_status\n        return result\n\ndef add_integrations_context(context: Dict[str, Any]) -> None:\n    alphabetical_sorted_categories = OrderedDict(sorted(CATEGORIES.items()))\n    alphabetical_sorted_integration = OrderedDict(sorted(INTEGRATIONS.items()))\n    enabled_integrations_count = len(list(filter(lambda v: v.is_enabled(), INTEGRATIONS.values())))\n    # Subtract 1 so saying \"Over X integrations\" is correct. Then,\n    # round down to the nearest multiple of 10.\n    integrations_count_display = ((enabled_integrations_count - 1) // 10) * 10\n    context['categories_dict'] = alphabetical_sorted_categories\n    context['integrations_dict'] = alphabetical_sorted_integration\n    context['integrations_count_display'] = integrations_count_display\n\ndef add_integrations_open_graph_context(context: Dict[str, Any], request: HttpRequest) -> None:\n    path_name = request.path.rstrip('/').split('/')[-1]\n    description = ('Zulip comes with over a hundred native integrations out of the box, '\n                   'and integrates with Zapier, IFTTT, and Hubot to provide hundreds more. '\n                   'Connect the apps you use everyday to Zulip.')\n\n    if path_name in INTEGRATIONS:\n        integration = INTEGRATIONS[path_name]\n        context['OPEN_GRAPH_TITLE'] = 'Connect {name} to Zulip'.format(name=integration.display_name)\n        context['OPEN_GRAPH_DESCRIPTION'] = description\n\n    elif path_name in CATEGORIES:\n        category = CATEGORIES[path_name]\n        context['OPEN_GRAPH_TITLE'] = 'Connect your {category} tools to Zulip'.format(category=category)\n        context['OPEN_GRAPH_DESCRIPTION'] = description\n\n    elif path_name == 'integrations':\n        context['OPEN_GRAPH_TITLE'] = 'Connect the tools you use to Zulip'\n        context['OPEN_GRAPH_DESCRIPTION'] = description\n\nclass IntegrationView(ApiURLView):\n    template_name = 'zerver/integrations/index.html'\n\n    def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:\n        context = super().get_context_data(**kwargs)  # type: Dict[str, Any]\n        add_integrations_context(context)\n        add_integrations_open_graph_context(context, self.request)\n        return context\n\n\n@has_request_variables\ndef integration_doc(request: HttpRequest, integration_name: str=REQ(default=None)) -> HttpResponse:\n    if not request.is_ajax():\n        return HttpResponseNotFound()\n    try:\n        integration = INTEGRATIONS[integration_name]\n    except KeyError:\n        return HttpResponseNotFound()\n\n    context = {}  # type: Dict[str, Any]\n    add_api_uri_context(context, request)\n\n    context['integration_name'] = integration.name\n    context['integration_display_name'] = integration.display_name\n    if hasattr(integration, 'stream_name'):\n        context['recommended_stream_name'] = integration.stream_name\n    if isinstance(integration, WebhookIntegration):\n        context['integration_url'] = integration.url[3:]\n    if isinstance(integration, HubotIntegration):\n        context['hubot_docs_url'] = integration.hubot_docs_url\n    if isinstance(integration, EmailIntegration):\n        context['email_gateway_example'] = settings.EMAIL_GATEWAY_EXAMPLE\n\n    doc_html_str = render_markdown_path(integration.doc, context)\n\n    return HttpResponse(doc_html_str)\n", "path": "zerver/views/documentation.py"}]}