├── .gitignore ├── .travis.yml ├── MANIFEST.in ├── Makefile ├── README.rst ├── conftest.py ├── sentry_hipchat ├── __init__.py └── models.py ├── setup.cfg ├── setup.py └── tests ├── __init__.py └── hipchat_test.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info/ 3 | /dist 4 | /build -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | install: 5 | - time make develop 6 | script: 7 | - py.test tests/ --cov sentry --cov-report term-missing 8 | notifications: 9 | irc: 10 | channels: "irc.freenode.org#sentry" 11 | on_success: change 12 | on_failure: change 13 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include setup.py README.rst MANIFEST.in LICENSE 2 | recursive-include sentry_hipchat/templates * 3 | global-exclude *~ 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | develop: 2 | pip install -e . 3 | pip install "file://`pwd`#egg=sentry-hipchat[test]" 4 | 5 | test: develop 6 | py.test 7 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | sentry-hipchat 2 | ============== 3 | 4 | An extension for Sentry which integrates with Hipchat. 5 | It will send issues notification to Hipchat. 6 | 7 | Install 8 | ------- 9 | 10 | Install the package via ``pip``:: 11 | 12 | pip install sentry-hipchat 13 | 14 | Configuration 15 | ------------- 16 | 17 | Go to your project's configuration page (Projects -> [Project]) and select the 18 | Hipchat tab. Enter the required credentials and click save changes. 19 | 20 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def pytest_configure(config): 5 | from django.conf import settings 6 | 7 | os.environ['DJANGO_SETTINGS_MODULE'] = 'sentry.conf.server' 8 | settings.DATABASES['default'].update({ 9 | 'ENGINE': 'django.db.backends.sqlite3', 10 | 'NAME': ':memory:', 11 | }) 12 | -------------------------------------------------------------------------------- /sentry_hipchat/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | sentry_hipchat 3 | ~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2011 by Linovia, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | try: 10 | VERSION = __import__('pkg_resources') \ 11 | .get_distribution('sentry_hipchat').version 12 | except Exception, e: 13 | VERSION = 'unknown' 14 | -------------------------------------------------------------------------------- /sentry_hipchat/models.py: -------------------------------------------------------------------------------- 1 | """ 2 | sentry_hipchat.models 3 | ~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2011 by Linovia, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from django import forms 10 | from django.conf import settings 11 | from django.utils.html import escape 12 | 13 | from sentry.plugins.bases.notify import NotifyPlugin 14 | 15 | import sentry_hipchat 16 | 17 | import urllib 18 | import urllib2 19 | import json 20 | import logging 21 | 22 | 23 | COLORS = { 24 | 'ALERT': 'red', 25 | 'ERROR': 'red', 26 | 'WARNING': 'yellow', 27 | 'INFO': 'green', 28 | 'DEBUG': 'purple', 29 | } 30 | 31 | DEFAULT_ENDPOINT = "https://api.hipchat.com/v1/rooms/message" 32 | 33 | 34 | class HipchatOptionsForm(forms.Form): 35 | token = forms.CharField(help_text="Your hipchat API v1 token.") 36 | room = forms.CharField(help_text="Room name or ID.") 37 | notify = forms.BooleanField(help_text='Notify message in chat window.', required=False) 38 | include_project_name = forms.BooleanField(help_text='Include project name in message.', required=False) 39 | endpoint = forms.CharField(help_text="Custom API endpoint to send notifications to.", required=False, 40 | widget=forms.TextInput(attrs={'placeholder': DEFAULT_ENDPOINT})) 41 | 42 | 43 | class HipchatMessage(NotifyPlugin): 44 | author = 'Xavier Ordoquy' 45 | author_url = 'https://github.com/linovia/sentry-hipchat' 46 | version = sentry_hipchat.VERSION 47 | description = "Event notification to Hipchat." 48 | resource_links = [ 49 | ('Bug Tracker', 'https://github.com/linovia/sentry-hipchat/issues'), 50 | ('Source', 'https://github.com/linovia/sentry-hipchat'), 51 | ] 52 | slug = 'hipchat' 53 | title = 'Hipchat' 54 | conf_title = title 55 | conf_key = 'hipchat' 56 | project_conf_form = HipchatOptionsForm 57 | timeout = getattr(settings, 'SENTRY_HIPCHAT_TIMEOUT', 3) 58 | 59 | def is_configured(self, project): 60 | return all((self.get_option(k, project) for k in ('room', 'token'))) 61 | 62 | def on_alert(self, alert, **kwargs): 63 | project = alert.project 64 | token = self.get_option('token', project) 65 | room = self.get_option('room', project) 66 | notify = self.get_option('notify', project) or False 67 | include_project_name = self.get_option('include_project_name', project) or False 68 | endpoint = self.get_option('endpoint', project) or DEFAULT_ENDPOINT 69 | 70 | if token and room: 71 | self.send_payload( 72 | endpoint=endpoint, 73 | token=token, 74 | room=room, 75 | message='[ALERT]%(project_name)s %(message)s %(link)s' % { 76 | 'project_name': (' %s' % escape(project.name)) if include_project_name else '', 77 | 'message': escape(alert.message), 78 | 'link': alert.get_absolute_url(), 79 | }, 80 | notify=notify, 81 | color=COLORS['ALERT'], 82 | ) 83 | 84 | def notify_users(self, group, event, fail_silently=False): 85 | project = event.project 86 | token = self.get_option('token', project) 87 | room = self.get_option('room', project) 88 | notify = self.get_option('notify', project) or False 89 | include_project_name = self.get_option('include_project_name', project) or False 90 | level = group.get_level_display().upper() 91 | link = group.get_absolute_url() 92 | endpoint = self.get_option('endpoint', project) or DEFAULT_ENDPOINT 93 | 94 | if token and room: 95 | self.send_payload( 96 | endpoint=endpoint, 97 | token=token, 98 | room=room, 99 | message='[%(level)s]%(project_name)s %(message)s [view]' % { 100 | 'level': escape(level), 101 | 'project_name': (' %s' % escape(project.name)).encode('utf-8') if include_project_name else '', 102 | 'message': escape(event.error()), 103 | 'link': escape(link), 104 | }, 105 | notify=notify, 106 | color=COLORS.get(level, 'purple'), 107 | ) 108 | 109 | def send_payload(self, endpoint, token, room, message, notify, color='red'): 110 | values = { 111 | 'auth_token': token, 112 | 'room_id': room.encode('utf-8'), 113 | 'from': 'Sentry', 114 | 'message': message.encode('utf-8'), 115 | 'notify': int(notify), 116 | 'color': color, 117 | } 118 | data = urllib.urlencode(values) 119 | request = urllib2.Request(endpoint, data) 120 | response = urllib2.urlopen(request, timeout=self.timeout) 121 | raw_response_data = response.read() 122 | response_data = json.loads(raw_response_data) 123 | if 'status' not in response_data: 124 | logger = logging.getLogger('sentry.plugins.hipchat') 125 | logger.error('Unexpected response') 126 | if response_data['status'] != 'sent': 127 | logger = logging.getLogger('sentry.plugins.hipchat') 128 | logger.error('Event was not sent to hipchat') 129 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts=--tb=short 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | sentry-hipchat 4 | ============== 5 | 6 | An extension for Sentry which integrates with Hipchat. It will forwards 7 | notifications to an hipchat room. 8 | 9 | :copyright: (c) 2011 by the Linovia, see AUTHORS for more details. 10 | :license: BSD, see LICENSE for more details. 11 | """ 12 | from setuptools import setup, find_packages 13 | 14 | 15 | tests_require = [ 16 | 'pytest', 17 | 'mock', 18 | ] 19 | 20 | install_requires = [ 21 | 'sentry>=6.0.0', 22 | ] 23 | 24 | setup( 25 | name='sentry-hipchat', 26 | version='0.6.0', 27 | author='Xavier Ordoquy', 28 | author_email='xordoquy@linovia.com', 29 | url='http://github.com/linovia/sentry-hipchat', 30 | description='A Sentry extension which integrates with Hipchat.', 31 | long_description=__doc__, 32 | license='BSD', 33 | packages=find_packages(exclude=['tests']), 34 | zip_safe=False, 35 | install_requires=install_requires, 36 | tests_require=tests_require, 37 | extras_require={'test': tests_require}, 38 | test_suite='runtests.runtests', 39 | include_package_data=True, 40 | entry_points={ 41 | 'sentry.apps': [ 42 | 'sentry_hipchat = sentry_hipchat ', 43 | ], 44 | 'sentry.plugins': [ 45 | 'hipchat = sentry_hipchat.models:HipchatMessage', 46 | ], 47 | }, 48 | classifiers=[ 49 | 'Framework :: Django', 50 | 'Intended Audience :: Developers', 51 | 'Intended Audience :: System Administrators', 52 | 'Operating System :: OS Independent', 53 | 'Topic :: Software Development' 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linovia/sentry-hipchat/d0ca57b36b77ba960ade32952774ac5660a1076b/tests/__init__.py -------------------------------------------------------------------------------- /tests/hipchat_test.py: -------------------------------------------------------------------------------- 1 | from mock import Mock, call 2 | import pytest 3 | 4 | from sentry_hipchat.models import HipchatMessage 5 | from django.conf import settings 6 | 7 | 8 | class PayLoadTest(object): 9 | pass 10 | 11 | 12 | DEFAULT_PLUGIN_CONFIGURATION = { 13 | 'token': 'abcdefghijklmn', 14 | 'room': 'test', 15 | 'notify': False, 16 | 'include_project_name': True, 17 | 'new_only': False, 18 | 'name': 'test project' 19 | } 20 | 21 | 22 | @pytest.fixture 23 | def event(): 24 | result = Mock() 25 | for k, v in DEFAULT_PLUGIN_CONFIGURATION.items(): 26 | setattr(result.project, k, v) 27 | 28 | result.message = 'An error has occured' 29 | result.error.return_value = result.message 30 | 31 | return result 32 | 33 | 34 | @pytest.fixture 35 | def plugin(): 36 | def get_option(k, d): 37 | return getattr(d, k) 38 | 39 | plugin = HipchatMessage() 40 | plugin.get_option = get_option 41 | 42 | plugin.get_url = Mock() 43 | plugin.get_url.return_value = 'http://localhost/someurl' 44 | 45 | plugin.send_payload = Mock() 46 | plugin.send_payload.return_value = None 47 | 48 | return plugin 49 | 50 | 51 | class TestPostProcess(object): 52 | def test_notify_users(self, event, plugin): 53 | """ 54 | Make sure known messages aren't sent again if the new_only option is on 55 | """ 56 | group = Mock() 57 | group.id = 1 58 | group.project.slug = "demo" 59 | group.get_absolute_url.return_value = 'http://localhost/demo/group/1/' 60 | group.get_level_display.return_value = 'ERROR' 61 | 62 | plugin.notify_users(group, event) 63 | 64 | assert plugin.send_payload.mock_calls == [ 65 | call('abcdefghijklmn', 'test', 66 | '[ERROR] test project An error has occured [view]', 67 | False, color='red') 68 | ] 69 | --------------------------------------------------------------------------------