-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add install-create event handlers (#608)
* Add notifications on install create for slack and osticket * Fix: admin UI redirects aren't followed correctly * Formatting * Fix url typo * Move address one-line helper to Building * Gate integrations behind feature flags * Fix warning in flag decorator * Add vars for "Add install-create event handlers" (#617) * add vars * Add new ticket endpoint variable --------- Co-authored-by: Andrew Dickinson <[email protected]> * Add tests for install event hooks * Formatting * Fix failing test * Add slack webhook retries & tests * Revert "Fix: admin UI redirects aren't followed correctly" This reverts commit 41d738b. --------- Co-authored-by: james-otten <[email protected]>
- Loading branch information
1 parent
cc5f067
commit 6b2f203
Showing
14 changed files
with
450 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from django.test import TestCase | ||
|
||
from meshapi.models import Building | ||
|
||
|
||
class TestBuilding(TestCase): | ||
def test_building_address_single_line_str(self): | ||
full_address_building = Building( | ||
street_address="123 Chom Street", | ||
city="Brooklyn", | ||
state="NY", | ||
zip_code="12345", | ||
latitude=0, | ||
longitude=0, | ||
) | ||
self.assertEqual(full_address_building.one_line_complete_address, "123 Chom Street, Brooklyn NY, 12345") | ||
|
||
limited_address_building = Building( | ||
street_address="123 Chom Street", | ||
latitude=0, | ||
longitude=0, | ||
) | ||
self.assertEqual(limited_address_building.one_line_complete_address, "123 Chom Street") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
import json | ||
from unittest.mock import patch | ||
|
||
import requests_mock | ||
from django.test import TestCase | ||
from flags.state import disable_flag, enable_flag | ||
|
||
from meshapi.models import Building, Install, Member | ||
from meshapi.tests.sample_data import sample_building, sample_install, sample_member | ||
|
||
|
||
class TestInstallCreateSignals(TestCase): | ||
def setUp(self): | ||
self.sample_install_copy = sample_install.copy() | ||
self.building_1 = Building(**sample_building) | ||
self.building_1.save() | ||
self.sample_install_copy["building"] = self.building_1 | ||
|
||
self.member = Member(**sample_member) | ||
self.member.save() | ||
self.sample_install_copy["member"] = self.member | ||
|
||
self.maxDiff = None | ||
|
||
@requests_mock.Mocker() | ||
def test_no_events_happen_by_default(self, request_mocker): | ||
install = Install(**self.sample_install_copy) | ||
install.save() | ||
|
||
self.assertEqual(len(request_mocker.request_history), 0) | ||
|
||
@patch( | ||
"meshapi.util.events.join_requests_slack_channel.SLACK_JOIN_REQUESTS_CHANNEL_WEBHOOK_URL", | ||
"http://example.com/test-url", | ||
) | ||
@requests_mock.Mocker() | ||
def test_constructing_install_triggers_slack_message(self, request_mocker): | ||
request_mocker.post("http://example.com/test-url", text="data") | ||
|
||
enable_flag("INTEGRATION_ENABLED_SEND_JOIN_REQUEST_SLACK_MESSAGES") | ||
disable_flag("INTEGRATION_ENABLED_CREATE_OSTICKET_TICKETS") | ||
install = Install(**self.sample_install_copy) | ||
install.save() | ||
|
||
self.assertEqual(len(request_mocker.request_history), 1) | ||
self.assertEqual( | ||
request_mocker.request_history[0].url, | ||
"http://example.com/test-url", | ||
) | ||
self.assertEqual( | ||
json.loads(request_mocker.request_history[0].text), | ||
{ | ||
"text": f"*<https://www.nycmesh.net/map/nodes/{install.install_number}" | ||
f"|3333 Chom St, Brooklyn NY, 11111>*\n" | ||
f"Altitude not found · Roof access · No LoS Data Available" | ||
}, | ||
) | ||
|
||
@patch( | ||
"meshapi.util.events.osticket_creation.OSTICKET_NEW_TICKET_ENDPOINT", | ||
"http://example.com/test-url", | ||
) | ||
@patch( | ||
"meshapi.util.events.osticket_creation.OSTICKET_API_TOKEN", | ||
"mock-token", | ||
) | ||
@requests_mock.Mocker() | ||
def test_constructing_install_triggers_osticket(self, request_mocker): | ||
request_mocker.post("http://example.com/test-url", text="00123456", status_code=201) | ||
|
||
disable_flag("INTEGRATION_ENABLED_SEND_JOIN_REQUEST_SLACK_MESSAGES") | ||
enable_flag("INTEGRATION_ENABLED_CREATE_OSTICKET_TICKETS") | ||
|
||
install = Install(**self.sample_install_copy) | ||
install.save() | ||
|
||
self.assertEqual(len(request_mocker.request_history), 1) | ||
self.assertEqual( | ||
request_mocker.request_history[0].url, | ||
"http://example.com/test-url", | ||
) | ||
self.assertEqual( | ||
json.loads(request_mocker.request_history[0].text), | ||
{ | ||
"node": install.install_number, | ||
"userNode": install.install_number, | ||
"email": "[email protected]", | ||
"name": "John Smith", | ||
"subject": f"NYC Mesh {install.install_number} Rooftop Install", | ||
"message": f"date: 2022-02-27\r\nnode: {install.install_number}\r\nname: John Smith\r\nemail: [email protected]\r\nphone: +1 555-555-5555\r\nlocation: 3333 Chom St, Brooklyn NY, 11111\r\nrooftop: Rooftop install\r\nagree to ncl: True", | ||
"phone": "+1 555-555-5555", | ||
"location": "3333 Chom St, Brooklyn NY, 11111", | ||
"rooftop": "Rooftop install", | ||
"ncl": True, | ||
"ip": "*.*.*.*", | ||
"locale": "en", | ||
}, | ||
) | ||
|
||
install.refresh_from_db() | ||
self.assertEqual(install.ticket_number, "00123456") | ||
|
||
@patch( | ||
"meshapi.util.events.join_requests_slack_channel.SLACK_JOIN_REQUESTS_CHANNEL_WEBHOOK_URL", | ||
"", | ||
) | ||
@patch( | ||
"meshapi.util.events.osticket_creation.OSTICKET_NEW_TICKET_ENDPOINT", | ||
"", | ||
) | ||
@requests_mock.Mocker() | ||
def test_no_events_when_env_variables_unset(self, request_mocker): | ||
enable_flag("INTEGRATION_ENABLED_SEND_JOIN_REQUEST_SLACK_MESSAGES") | ||
enable_flag("INTEGRATION_ENABLED_CREATE_OSTICKET_TICKETS") | ||
|
||
install = Install(**self.sample_install_copy) | ||
install.save() | ||
|
||
self.assertEqual(len(request_mocker.request_history), 0) | ||
|
||
@patch( | ||
"meshapi.util.events.osticket_creation.OSTICKET_NEW_TICKET_ENDPOINT", | ||
"http://example.com/test-url", | ||
) | ||
@patch( | ||
"meshapi.util.events.osticket_creation.OSTICKET_API_TOKEN", | ||
"", | ||
) | ||
@requests_mock.Mocker() | ||
def test_no_osticket_event_when_no_api_token(self, request_mocker): | ||
enable_flag("INTEGRATION_ENABLED_CREATE_OSTICKET_TICKETS") | ||
|
||
install = Install(**self.sample_install_copy) | ||
install.save() | ||
|
||
self.assertEqual(len(request_mocker.request_history), 0) | ||
|
||
@patch( | ||
"meshapi.util.events.join_requests_slack_channel.SLACK_JOIN_REQUESTS_CHANNEL_WEBHOOK_URL", | ||
"http://example.com/test-url", | ||
) | ||
@patch( | ||
"meshapi.util.events.osticket_creation.OSTICKET_NEW_TICKET_ENDPOINT", | ||
"http://example.com/test-url", | ||
) | ||
@patch( | ||
"meshapi.util.events.osticket_creation.OSTICKET_API_TOKEN", | ||
"mock-token", | ||
) | ||
@requests_mock.Mocker() | ||
def test_no_events_for_install_edit(self, request_mocker): | ||
install = Install(**self.sample_install_copy) | ||
install.save() | ||
|
||
enable_flag("INTEGRATION_ENABLED_SEND_JOIN_REQUEST_SLACK_MESSAGES") | ||
enable_flag("INTEGRATION_ENABLED_CREATE_OSTICKET_TICKETS") | ||
|
||
install.notes = "foo" | ||
install.save() | ||
|
||
self.assertEqual(len(request_mocker.request_history), 0) | ||
|
||
@patch( | ||
"meshapi.util.events.join_requests_slack_channel.SLACK_JOIN_REQUESTS_CHANNEL_WEBHOOK_URL", | ||
"http://example.com/test-url-slack", | ||
) | ||
@patch( | ||
"meshapi.util.events.osticket_creation.OSTICKET_NEW_TICKET_ENDPOINT", | ||
"http://example.com/test-url-os-ticket", | ||
) | ||
@patch( | ||
"meshapi.util.events.osticket_creation.OSTICKET_API_TOKEN", | ||
"mock-token", | ||
) | ||
@requests_mock.Mocker() | ||
def test_many_retry_no_crash_on_integration_404(self, request_mocker): | ||
request_mocker.post("http://example.com/test-url-slack", text="Not found", status_code=404) | ||
request_mocker.post("http://example.com/test-url-os-ticket", text="Not found", status_code=404) | ||
|
||
enable_flag("INTEGRATION_ENABLED_SEND_JOIN_REQUEST_SLACK_MESSAGES") | ||
enable_flag("INTEGRATION_ENABLED_CREATE_OSTICKET_TICKETS") | ||
|
||
install = Install(**self.sample_install_copy) | ||
install.save() | ||
|
||
self.assertEqual( | ||
len( | ||
[ | ||
request | ||
for request in request_mocker.request_history | ||
if request.url == "http://example.com/test-url-os-ticket" | ||
] | ||
), | ||
4, | ||
) | ||
self.assertEqual( | ||
len( | ||
[ | ||
request | ||
for request in request_mocker.request_history | ||
if request.url == "http://example.com/test-url-slack" | ||
] | ||
), | ||
4, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from functools import wraps | ||
from typing import Any, Callable | ||
|
||
from flags.state import flag_state | ||
|
||
|
||
def skip_if_flag_disabled(flag_name: str) -> Callable: | ||
""" | ||
Decorator that transforms the annotated function into a noop if the given flag name is disabled | ||
:param flag_name: the flag to check | ||
""" | ||
|
||
def decorator(func: Callable) -> Callable: | ||
def inner(*args: list, **kwargs: dict) -> Any: | ||
enabled = flag_state(flag_name) | ||
|
||
if enabled: | ||
return func(*args, **kwargs) | ||
|
||
return wraps(func)(inner) | ||
|
||
return decorator |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from .join_requests_slack_channel import send_join_request_slack_message | ||
from .osticket_creation import create_os_ticket_for_install |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import logging | ||
import os | ||
import time | ||
|
||
import requests | ||
from django.db.models.base import ModelBase | ||
from django.db.models.signals import post_save | ||
from django.dispatch import receiver | ||
|
||
from meshapi.models import Install | ||
from meshapi.util.django_flag_decorator import skip_if_flag_disabled | ||
|
||
SLACK_JOIN_REQUESTS_CHANNEL_WEBHOOK_URL = os.environ.get("SLACK_JOIN_REQUESTS_CHANNEL_WEBHOOK_URL") | ||
|
||
|
||
@receiver(post_save, sender=Install, dispatch_uid="join_requests_slack_channel") | ||
@skip_if_flag_disabled("INTEGRATION_ENABLED_SEND_JOIN_REQUEST_SLACK_MESSAGES") | ||
def send_join_request_slack_message(sender: ModelBase, instance: Install, created: bool, **kwargs: dict) -> None: | ||
if not created: | ||
return | ||
|
||
install: Install = instance | ||
if not SLACK_JOIN_REQUESTS_CHANNEL_WEBHOOK_URL: | ||
logging.error( | ||
f"Unable to send join request notification for install {str(install)}, did you set the " | ||
f"SLACK_JOIN_REQUESTS_CHANNEL_WEBHOOK_URL environment variable?" | ||
) | ||
return | ||
|
||
building_height = str(int(install.building.altitude)) + "m" if install.building.altitude else "Altitude not found" | ||
roof_access = "Roof access" if install.roof_access else "No roof access" | ||
|
||
attempts = 0 | ||
while attempts < 4: | ||
attempts += 1 | ||
response = requests.post( | ||
SLACK_JOIN_REQUESTS_CHANNEL_WEBHOOK_URL, | ||
json={ | ||
"text": f"*<https://www.nycmesh.net/map/nodes/{install.install_number}" | ||
f"|{install.building.one_line_complete_address}>*\n" | ||
f"{building_height} · {roof_access} · No LoS Data Available" | ||
}, | ||
) | ||
|
||
if response.status_code == 200: | ||
break | ||
|
||
time.sleep(1) | ||
|
||
if response.status_code != 200: | ||
logging.error( | ||
f"Got HTTP {response.status_code} while sending install create notification to " | ||
f"join-requests channel. HTTP response was {response.text}" | ||
) |
Oops, something went wrong.