Browse Source

Experimental Zulip integration. Fixes #202

pull/342/head
Pēteris Caune 5 years ago
parent
commit
f352efdd5f
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
21 changed files with 439 additions and 6 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +32
    -0
      hc/api/models.py
  3. +18
    -0
      hc/api/tests/test_notify.py
  4. +31
    -1
      hc/api/transports.py
  5. +14
    -0
      hc/front/forms.py
  6. +80
    -0
      hc/front/tests/test_add_zulip.py
  7. +1
    -0
      hc/front/urls.py
  8. +20
    -0
      hc/front/views.py
  9. +4
    -0
      static/css/channels.css
  10. +9
    -5
      static/css/icomoon.css
  11. BIN
      static/fonts/icomoon.eot
  12. +1
    -0
      static/fonts/icomoon.svg
  13. BIN
      static/fonts/icomoon.ttf
  14. BIN
      static/fonts/icomoon.woff
  15. BIN
      static/img/integrations/setup_zulip_1.png
  16. BIN
      static/img/integrations/setup_zulip_2.png
  17. BIN
      static/img/integrations/setup_zulip_3.png
  18. BIN
      static/img/integrations/zulip.png
  19. +17
    -0
      static/js/add_zulip.js
  20. +16
    -0
      templates/front/channels.html
  21. +195
    -0
      templates/integrations/add_zulip.html

+ 1
- 0
CHANGELOG.md View File

@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
- Don't store user's current project in DB, put it explicitly in page URLs (#336)
- API reference in Markdown
- Use Selectize.js for entering tags (#324)
- Zulip integration (#202)
### Bug Fixes
- The "render_docs" command checks if markdown and pygments is installed (#329)


+ 32
- 0
hc/api/models.py View File

@ -48,6 +48,7 @@ CHANNEL_KINDS = (
("mattermost", "Mattermost"),
("msteams", "Microsoft Teams"),
("shell", "Shell Command"),
("zulip", "Zulip"),
)
PO_PRIORITIES = {-2: "lowest", -1: "low", 0: "normal", 1: "high", 2: "emergency"}
@ -359,6 +360,11 @@ class Channel(models.Model):
return "Slack %s" % self.slack_channel
elif self.kind == "telegram":
return "Telegram %s" % self.telegram_name
elif self.kind == "zulip":
if self.zulip_type == "stream":
return "Zulip stream %s" % self.zulip_to
if self.zulip_type == "private":
return "Zulip user %s" % self.zulip_to
return self.get_kind_display()
@ -429,6 +435,8 @@ class Channel(models.Model):
return transports.MsTeams(self)
elif self.kind == "shell":
return transports.Shell(self)
elif self.kind == "zulip":
return transports.Zulip(self)
else:
raise NotImplementedError("Unknown channel kind: %s" % self.kind)
@ -674,6 +682,30 @@ class Channel(models.Model):
doc = json.loads(self.value)
return doc["region"]
@property
def zulip_bot_email(self):
assert self.kind == "zulip"
doc = json.loads(self.value)
return doc["bot_email"]
@property
def zulip_api_key(self):
assert self.kind == "zulip"
doc = json.loads(self.value)
return doc["api_key"]
@property
def zulip_type(self):
assert self.kind == "zulip"
doc = json.loads(self.value)
return doc["mtype"]
@property
def zulip_to(self):
assert self.kind == "zulip"
doc = json.loads(self.value)
return doc["to"]
class Notification(models.Model):
class Meta:


+ 18
- 0
hc/api/tests/test_notify.py View File

@ -798,3 +798,21 @@ class NotifyTestCase(BaseTestCase):
n = Notification.objects.get()
self.assertEqual(n.error, "Shell commands are not enabled")
@patch("hc.api.transports.requests.request")
def test_zulip(self, mock_post):
definition = {
"bot_email": "[email protected]",
"api_key": "fake-key",
"mtype": "stream",
"to": "general",
}
self._setup_data("zulip", json.dumps(definition))
mock_post.return_value.status_code = 200
self.channel.notify(self.check)
assert Notification.objects.count() == 1
args, kwargs = mock_post.call_args
payload = kwargs["data"]
self.assertIn("DOWN", payload["topic"])

+ 31
- 1
hc/api/transports.py View File

@ -140,6 +140,10 @@ class Shell(Transport):
class HttpTransport(Transport):
@classmethod
def get_error(cls, r):
return "Received status code %d" % r.status_code
@classmethod
def _request(cls, method, url, **kwargs):
try:
@ -152,7 +156,7 @@ class HttpTransport(Transport):
r = requests.request(method, url, **options)
if r.status_code not in (200, 201, 202, 204):
return "Received status code %d" % r.status_code
return cls.get_error(r)
except requests.exceptions.Timeout:
# Well, we tried
return "Connection timed out"
@ -538,3 +542,29 @@ class MsTeams(HttpTransport):
text = tmpl("msteams_message.json", check=check)
payload = json.loads(text)
return self.post(self.channel.value, json=payload)
class Zulip(HttpTransport):
@classmethod
def get_error(cls, r):
try:
doc = r.json()
if "msg" in doc:
return doc["msg"]
except ValueError:
pass
return super().get_error(r)
def notify(self, check):
_, domain = self.channel.zulip_bot_email.split("@")
url = "https://%s/api/v1/messages" % domain
auth = (self.channel.zulip_bot_email, self.channel.zulip_api_key)
data = {
"type": self.channel.zulip_type,
"to": self.channel.zulip_to,
"topic": tmpl("zulip_topic.html", check=check),
"content": tmpl("zulip_content.html", check=check),
}
return self.post(url, data=data, auth=auth)

+ 14
- 0
hc/front/forms.py View File

@ -221,3 +221,17 @@ class AddAppriseForm(forms.Form):
class AddPdForm(forms.Form):
error_css_class = "has-error"
value = forms.CharField(max_length=32)
ZULIP_TARGETS = (("stream", "Stream"), ("private", "Private"))
class AddZulipForm(forms.Form):
error_css_class = "has-error"
bot_email = forms.EmailField(max_length=100)
api_key = forms.CharField(max_length=50)
mtype = forms.ChoiceField(choices=ZULIP_TARGETS)
to = forms.CharField(max_length=100)
def get_value(self):
return json.dumps(dict(self.cleaned_data), sort_keys=True)

+ 80
- 0
hc/front/tests/test_add_zulip.py View File

@ -0,0 +1,80 @@
from hc.api.models import Channel
from hc.test import BaseTestCase
class AddZulipTestCase(BaseTestCase):
def setUp(self):
super(AddZulipTestCase, self).setUp()
self.url = "/projects/%s/add_zulip/" % self.project.code
def test_instructions_work(self):
self.client.login(username="[email protected]", password="password")
r = self.client.get(self.url)
self.assertContains(r, "open-source group chat app")
def test_it_works(self):
form = {
"bot_email": "[email protected]",
"api_key": "fake-key",
"mtype": "stream",
"to": "general",
}
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, form)
self.assertRedirects(r, self.channels_url)
c = Channel.objects.get()
self.assertEqual(c.kind, "zulip")
self.assertEqual(c.zulip_bot_email, "[email protected]")
self.assertEqual(c.zulip_api_key, "fake-key")
self.assertEqual(c.zulip_type, "stream")
self.assertEqual(c.zulip_to, "general")
def test_it_rejects_bad_email(self):
form = {
"bot_email": "not@an@email",
"api_key": "fake-key",
"mtype": "stream",
"to": "general",
}
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, form)
self.assertContains(r, "Enter a valid email address.")
def test_it_rejects_missing_api_key(self):
form = {
"bot_email": "[email protected]",
"api_key": "",
"mtype": "stream",
"to": "general",
}
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, form)
self.assertContains(r, "This field is required.")
def test_it_rejects_bad_mtype(self):
form = {
"bot_email": "[email protected]",
"api_key": "fake-key",
"mtype": "this-should-not-work",
"to": "general",
}
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, form)
self.assertEqual(r.status_code, 200)
def test_it_rejects_missing_stream_name(self):
form = {
"bot_email": "[email protected]",
"api_key": "fake-key",
"mtype": "stream",
"to": "",
}
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, form)
self.assertContains(r, "This field is required.")

+ 1
- 0
hc/front/urls.py View File

@ -75,6 +75,7 @@ project_urls = [
path("add_victorops/", views.add_victorops, name="hc-add-victorops"),
path("add_webhook/", views.add_webhook, name="hc-add-webhook"),
path("add_whatsapp/", views.add_whatsapp, name="hc-add-whatsapp"),
path("add_zulip/", views.add_zulip, name="hc-add-zulip"),
path("badges/", views.badges, name="hc-badges"),
path("checks/", views.my_checks, name="hc-checks"),
path("checks/add/", views.add_check, name="hc-add-check"),


+ 20
- 0
hc/front/views.py View File

@ -1353,6 +1353,26 @@ def add_victorops(request, code):
return render(request, "integrations/add_victorops.html", ctx)
@login_required
def add_zulip(request, code):
project = _get_project_for_user(request, code)
if request.method == "POST":
form = forms.AddZulipForm(request.POST)
if form.is_valid():
channel = Channel(project=project, kind="zulip")
channel.value = form.get_value()
channel.save()
channel.assign_all_checks()
return redirect("hc-p-channels", project.code)
else:
form = forms.AddZulipForm()
ctx = {"page": "channels", "project": project, "form": form}
return render(request, "integrations/add_zulip.html", ctx)
@csrf_exempt
@require_POST
def telegram_bot(request):


+ 4
- 0
static/css/channels.css View File

@ -226,6 +226,10 @@ table.channels-table > tbody > tr > th {
animation: marker-ripple 1.2s ease-out infinite;
}
.ai-step p {
margin-left: 80px;
}
@keyframes marker-ripple {
0%, 35% {
transform: scale(0);


+ 9
- 5
static/css/icomoon.css View File

@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
src: url('../fonts/icomoon.eot?pl16ut');
src: url('../fonts/icomoon.eot?pl16ut#iefix') format('embedded-opentype'),
url('../fonts/icomoon.ttf?pl16ut') format('truetype'),
url('../fonts/icomoon.woff?pl16ut') format('woff'),
url('../fonts/icomoon.svg?pl16ut#icomoon') format('svg');
src: url('../fonts/icomoon.eot?agj6xa');
src: url('../fonts/icomoon.eot?agj6xa#iefix') format('embedded-opentype'),
url('../fonts/icomoon.ttf?agj6xa') format('truetype'),
url('../fonts/icomoon.woff?agj6xa') format('woff'),
url('../fonts/icomoon.svg?agj6xa#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
@ -24,6 +24,10 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-zulip:before {
content: "\e918";
color: #1e9459;
}
.icon-pd:before {
content: "\e90b";
color: #04ac38;


BIN
static/fonts/icomoon.eot View File


+ 1
- 0
static/fonts/icomoon.svg View File

@ -42,4 +42,5 @@
<glyph unicode="&#xe915;" glyph-name="apprise" horiz-adv-x="1103" d="M419.207-63.539c-36.821 10.251-62.633 68.381-78.184 96.84s-44.871 79.948-44.871 79.948l144.118 77.477c0 0 60.549-101.414 89.638-152.536 19.503-48.548-37.228-71.026-70.145-90.61-12.11-6.879-26.274-13.39-40.556-11.119zM139.125 137.497c-83.563 6.246-150.932 89.762-137.383 173.161 0.044 28.578 33.377 106.495 61.177 57.277 41.786-74.223 86.086-147.054 127.101-221.634-9.907-13.558-36.039-7.416-50.895-8.805zM256.767 178.268c-51.040 82.94-97.903 168.519-147.818 252.248 31.046 22.803 61.092 39.433 87.762 60.464 113.646 71.464 237.133 203.369 288.762 347.602 13.484 45.244 66.37 79.001 93.522 38.262 100.485-174.847 203.317-348.42 302.511-523.936 17.51-66.627-63.993-53.787-103.86-44.62-133.333 17.402-276.261 7.503-394.63-61.032-41.186-22.873-80.753-48.963-122.811-70.028l-3.438 1.038zM1008.674 488.667c-59.824 20.665 2.515 73.201 14.237 107.157 44.133 94.328 5.38 215.539-83.422 269.141-47.146 29.856-104.57 37.992-159.139 29.894-49.006 8.783-26.794 61.723 19.937 63.521 135.186 15.694 273.035-84.419 296.526-219.010 18.169-86.287-5.187-184.47-69.789-246.399-5.822-2.236-11.938-5.013-18.349-4.303zM874.499 536.119c-56.018 26.015 12.996 72.844 8.156 111.868 9.085 66.073-58.288 124.609-122.441 110.005-37.378 8.906-34.985 58.261 13.385 63.11 100.043 8.227 190.553-92.3 170.885-191.055-6.546-34.584-27.598-94.615-69.985-93.926z" />
<glyph unicode="&#xe916;" glyph-name="msteams" horiz-adv-x="1082" d="M46.124 716.056c-25.473 0-46.124-20.65-46.124-46.124v-461.34c0-25.473 20.65-46.124 46.124-46.124h461.34c25.473 0 46.124 20.65 46.124 46.124v461.34c0 25.473-20.65 46.124-46.124 46.124zM155.407 589.183h242.773v-48.716h-92.223v-251.128h-58.755v251.128h-91.795zM875.863 565.077c22.27-4.283 38.848-24.124 38.305-47.545v-290.357c1.163-49.999-10.679-97.28-32.435-138.607 7.763-1.047 15.685-1.59 23.734-1.589h0.831c97.044 0 175.713 78.669 175.713 175.713v254.575c0 26.405-21.405 47.809-47.809 47.809zM1056.849 728.637c0-62.537-50.697-113.234-113.234-113.234s-113.234 50.697-113.234 113.234c0 62.537 50.697 113.234 113.234 113.234s113.234-50.697 113.234-113.234zM591.332 960c-90.332 0-163.56-73.229-163.56-163.56 0 0 0 0 0-0.001v0c0.070-13.428 1.748-26.431 4.85-38.871l-0.238 1.126h125.481c25.392-0.096 45.952-20.656 46.049-46.048v-79.234c84.737 6.734 150.952 77.144 150.978 163.024v0.002c0 0 0 0 0 0 0 90.332-73.228 163.56-163.56 163.56 0 0 0 0 0 0v0zM433.549 753.505c0.349-1.523 0.451-1.91 0.554-2.296l-0.259 1.142c-0.103 0.383-0.195 0.77-0.295 1.154zM445.484 722.433c0.5-1.032 0.572-1.168 0.643-1.303l-0.429 0.89c-0.071 0.138-0.144 0.275-0.214 0.413zM453.43 708.54c0.573-0.936 0.756-1.218 0.939-1.5l-0.386 0.634c-0.186 0.288-0.368 0.578-0.553 0.867zM462.734 695.437c0.587-0.757 0.913-1.165 1.241-1.573l-0.246 0.316c-0.334 0.416-0.664 0.836-0.995 1.256zM473.304 683.28c0.541-0.563 1.035-1.070 1.532-1.573l-0.017 0.017c-0.508 0.515-1.014 1.034-1.515 1.556zM484.912 672.322c0.495-0.431 1.21-1.036 1.93-1.635l0.266-0.215c-0.738 0.61-1.47 1.227-2.197 1.85zM497.604 662.494c0.406-0.305 1.331-0.945 2.263-1.576l0.577-0.368c-0.955 0.638-1.9 1.287-2.841 1.944zM511.254 653.922c0.322-0.22 1.457-0.849 2.601-1.465l0.87-0.428c-1.165 0.617-2.322 1.249-3.471 1.893zM525.667 646.743c0.317-0.191 1.683-0.778 3.058-1.347l1.085-0.398c-1.39 0.564-2.772 1.144-4.144 1.745zM540.6 641.060c0.485-0.213 2.136-0.735 3.798-1.232l1.158-0.297c-1.663 0.484-3.314 0.994-4.955 1.528zM557.738 636.421c-0.543 0.086 0.017-0.041 0.579-0.165l1.085-0.201c-0.556 0.117-1.11 0.242-1.664 0.366zM603.455 633.357c-0.696-0.041-1.385-0.082-2.071-0.121 1.133 0.057 1.851 0.1 2.568 0.148l-0.498-0.027zM602.36 565.077v-406.887c-0.125-18.659-11.432-35.421-28.686-42.525-5.493-2.324-11.397-3.522-17.362-3.523h-233.326c41.443-101.046 139.606-173.299 255.765-176.142 156.566 3.832 280.436 133.761 276.794 290.331v290.357c0.604 26.091-20.034 47.743-46.125 48.389z" />
<glyph unicode="&#xe917;" glyph-name="shell" d="M109.229 960c-60.512 0-109.229-48.717-109.229-109.229v-805.546c0-60.512 48.717-109.223 109.229-109.223h805.546c60.512 0 109.223 48.712 109.223 109.223v805.546c0 60.512-48.712 109.229-109.223 109.229h-805.546zM293.258 784.868h56.588v-76.746c21.22-2.829 40.317-8.607 57.293-17.33s31.36-20.161 43.149-34.308c12.025-13.911 21.217-30.412 27.583-49.51 6.602-18.862 9.905-40.082 9.905-63.66h-98.32c0 28.529-6.485 50.1-19.452 64.718-12.968 14.854-30.533 22.28-52.696 22.28-12.025 0-22.515-1.649-31.474-4.95-8.724-3.065-15.916-7.544-21.575-13.439-5.659-5.659-9.904-12.377-12.733-20.158-2.594-7.781-3.892-16.271-3.892-25.466s1.298-17.446 3.892-24.755c2.829-7.073 7.425-13.675 13.791-19.805 6.602-6.13 15.209-12.024 25.819-17.683 10.61-5.423 23.813-10.966 39.61-16.625 23.813-8.96 45.39-18.269 64.724-27.936 19.334-9.431 35.835-20.517 49.51-33.249 13.911-12.496 24.524-27.115 31.833-43.855 7.545-16.504 11.316-36.070 11.316-58.704 0-20.748-3.421-39.495-10.258-56.235-6.838-16.504-16.62-30.766-29.352-42.791s-28.058-21.696-45.977-29.005c-17.919-7.073-37.964-11.669-60.127-13.791v-69.315h-56.229v69.315c-20.041 1.886-39.495 6.248-58.357 13.086-18.862 7.073-35.603 17.213-50.221 30.416-14.382 13.204-25.931 29.827-34.655 49.868-8.724 20.277-13.086 44.561-13.086 72.854h98.673c0-16.976 2.474-31.243 7.425-42.796 4.951-11.317 11.319-20.393 19.1-27.23 8.016-6.602 17.092-11.315 27.23-14.144s20.512-4.244 31.122-4.244c25.228 0 44.326 5.894 57.293 17.683s19.452 26.992 19.452 45.619c0 9.903-1.532 18.627-4.597 26.172-2.829 7.781-7.542 14.856-14.144 21.222-6.366 6.366-14.856 12.143-25.466 17.33-10.374 5.423-22.987 10.726-37.841 15.914-23.813 8.488-45.507 17.446-65.076 26.877s-36.31 20.395-50.221 32.891c-13.675 12.496-24.282 26.998-31.827 43.502-7.545 16.74-11.316 36.545-11.316 59.415 0 20.041 3.415 38.314 10.253 54.818 6.838 16.74 16.509 31.241 29.005 43.502s27.583 22.16 45.266 29.705c17.683 7.545 37.371 12.38 59.063 14.502v76.040zM571.594 187.88h322.544v-76.746h-322.544v76.746z" />
<glyph unicode="&#xe918;" glyph-name="zulip" d="M512.002 960c-0.001 0-0.001 0-0.001 0-282.77 0-512.001-229.23-512.001-512.001 0-0.001 0-0.001 0-0.002v0.001c0.002-282.769 229.231-511.998 512.001-511.998 0.001 0 0.001 0 0.002 0h-0.001c0.001 0 0.001 0 0.001 0 282.769 0 512 229.229 512.001 511.999v0c0 0.001 0 0.001 0 0.001 0 282.77-229.23 512.001-512.001 512.001-0.001 0-0.001 0-0.002 0h0.001zM284.075 714.286h394.776l58.098-119.923-268.809-280.809h209.963l63.314-131.839h-397.006l-61.825 131.839 276.205 279.32h-219.596z" />
</font></defs></svg>

BIN
static/fonts/icomoon.ttf View File


BIN
static/fonts/icomoon.woff View File


BIN
static/img/integrations/setup_zulip_1.png View File

Before After
Width: 317  |  Height: 223  |  Size: 28 KiB

BIN
static/img/integrations/setup_zulip_2.png View File

Before After
Width: 464  |  Height: 393  |  Size: 25 KiB

BIN
static/img/integrations/setup_zulip_3.png View File

Before After
Width: 464  |  Height: 300  |  Size: 22 KiB

BIN
static/img/integrations/zulip.png View File

Before After
Width: 128  |  Height: 128  |  Size: 2.9 KiB

+ 17
- 0
static/js/add_zulip.js View File

@ -0,0 +1,17 @@
$(function() {
function updateForm() {
var mType = $('input[name=mtype]:checked').val();
if (mType == "stream") {
$("#z-to-label").text("Stream Name");
$("#z-to-help").text('Example: "general"');
}
if (mType == "private") {
$("#z-to-label").text("User's Email");
$("#z-to-help").text('Example: "[email protected]"');
}
}
// Update form labels when user clicks on radio buttons
$('input[type=radio][name=mtype]').change(updateForm);
});

+ 16
- 0
templates/front/channels.html View File

@ -82,6 +82,13 @@
{% if ch.whatsapp_notify_up and not ch.whatsapp_notify_down %}
(up only)
{% endif %}
{% elif ch.kind == "zulip" %}
Zulip
{% if ch.zulip_type == "stream" %}
stream <span>{{ ch.zulip_to}}</span>
{% elif ch.zulip_type == "private" %}
user <span>{{ ch.zulip_to}}</span>
{% endif %}
{% else %}
{{ ch.get_kind_display }}
{% endif %}
@ -380,6 +387,15 @@
</li>
{% endif %}
<li>
<img src="{% static 'img/integrations/zulip.png' %}"
class="icon" alt="Zulip icon" />
<h2>Zulip</h2>
<p>Open-source group chat.</p>
<a href="{% url 'hc-add-zulip' project.code %}" class="btn btn-primary">Add Integration</a>
</li>
<li class="link-to-github">
<img src="{% static 'img/integrations/missing.png' %}"
class="icon" alt="Suggest New Integration" />


+ 195
- 0
templates/integrations/add_zulip.html View File

@ -0,0 +1,195 @@
{% extends "base.html" %}
{% load compress humanize static hc_extras %}
{% block title %}Zulip Integration for {% site_name %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<h1>Zulip</h1>
<p>
<a href="https://zulipchat.com/">Zulip</a> is an open-source group chat app
with an email threading model. If you use or plan on using Zulip,
you can can integrate it
with your {% site_name %} account in few simple steps.
</p>
<h2>Setup Guide</h2>
<div class="row ai-step">
<div class="col-sm-6">
<span class="step-no"></span>
<p>
Log into your Zulip account,
click on the <strong>gear icon</strong> in the upper right corner,
and select <strong>Settings</strong>.
</p>
</div>
<div class="col-sm-6">
<img
class="ai-guide-screenshot"
alt="Screenshot"
src="{% static 'img/integrations/setup_zulip_1.png' %}">
</div>
</div>
<div class="row ai-step">
<div class="col-sm-6">
<span class="step-no"></span>
<p>
Got to <strong>Your bots › Add a new bot </strong> and fill
out the fields.
</p>
<p>
For <strong>Bot Type</strong>,
select "Incoming webhook". You can choose your own preferred values
for bot's name and email.
</p>
<p>
For the profile picture, feel free to use the {% site_name %} logo:
</p>
<p>
<img src="{% static 'img/logo.png'%}" alt="{% site_name %} logo">
</p>
<p>
After you have filled out the values,
click on <strong>Create Bot</strong>.
</p>
</div>
<div class="col-sm-6">
<img
class="ai-guide-screenshot"
alt="Screenshot"
src="{% static 'img/integrations/setup_zulip_2.png' %}">
</div>
</div>
<div class="row ai-step">
<div class="col-sm-6">
<span class="step-no"></span>
<p>
Copy the displayed bot's credentials into the form below.
Also specify the stream or the private user you want {% site_name %}
to post notifications to.
</p>
<p>
Save the integration and you are done!
</p>
</div>
<div class="col-sm-6">
<img
class="ai-guide-screenshot"
alt="Screenshot"
src="{% static 'img/integrations/setup_zulip_3.png' %}">
</div>
</div>
<h2>Integration Settings</h2>
<form method="post" class="form-horizontal">
{% csrf_token %}
<div class="form-group {{ form.bot_email.css_classes }}">
<label for="bot-email" class="col-sm-2 control-label">Bot Email</label>
<div class="col-sm-4">
<input
id="bot-email"
type="text"
class="form-control"
name="bot_email"
value="{{ form.bot_email.value|default:"" }}">
<div class="help-block">
{% if form.bot_email.errors %}
{{ form.bot_email.errors|join:"" }}
{% else %}
Example: [email protected]
{% endif %}
</div>
</div>
</div>
<div class="form-group {{ form.api_key.css_classes }}">
<label for="api-key" class="col-sm-2 control-label">API Key</label>
<div class="col-sm-4">
<input
id="api-key"
type="text"
class="form-control"
name="api_key"
value="{{ form.api_key.value|default:"" }}">
{% if form.api_key.errors %}
<div class="help-block">
{{ form.api_key.errors|join:"" }}
</div>
{% endif %}
</div>
</div>
<div id="z-mtype-group" class="form-group {{ form.mtype.css_classes }}">
<label class="col-sm-2 control-label">Post To</label>
<div class="col-sm-4">
<label class="radio-container">
<input
type="radio"
name="mtype"
value="stream"
{% if form.mtype.value != "private" %} checked {% endif %}>
<span class="radiomark"></span>
Stream
</label>
<label class="radio-container">
<input
type="radio"
name="mtype"
value="private"
{% if form.mtype.value == "private" %} checked {% endif %}>
<span class="radiomark"></span>
Private user
</label>
</div>
</div>
<div class="form-group {{ form.to.css_classes }}">
<label id="z-to-label" for="z-to" class="col-sm-2 control-label">
{% if form.mtype.value == "private" %}
User's Email
{% else %}
Stream Name
{% endif %}
</label>
<div class="col-sm-4">
<input
id="z-to"
type="text"
class="form-control"
name="to"
value="{{ form.to.value|default:"" }}">
<div id="z-to-help" class="help-block">
{% if form.to.errors %}
{{ form.to.errors|join:"" }}
{% else %}
Example: "{% if form.mtype.value == "private" %}[email protected]{% else %}general{% endif%}""
{% endif %}
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary">Save Integration</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}
{% block scripts %}
{% compress js %}
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/add_zulip.js' %}"></script>
{% endcompress %}
{% endblock %}

Loading…
Cancel
Save