├── .gitignore ├── MANIFEST.in ├── README.rst ├── sentry_phabricator ├── __init__.py ├── models.py └── plugin.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info/ 3 | /dist 4 | /build -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include setup.py README.rst MANIFEST.in LICENSE 2 | recursive-include sentry_phabricator/templates * 3 | global-exclude *~ 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | sentry-phabricator 2 | ================== 3 | 4 | An extension for Sentry which integrates with Phabricator. Specifically, it allows you to easily create 5 | Maniphest tasks from events within Sentry. 6 | 7 | 8 | Install 9 | ------- 10 | 11 | Install the package via ``pip``:: 12 | 13 | pip install sentry-phabricator 14 | 15 | Configuration 16 | ------------- 17 | 18 | Create a user within your Phabricator install (a system agent). This user will 19 | be creating tickets on your behalf via Sentry. 20 | 21 | Go to your project's configuration page (Projects -> [Project]) and select the 22 | Phabricator tab. Enter the required credentials and click save changes. 23 | 24 | You'll now see a new action on groups which allows quick creation of Maniphest 25 | tasks. -------------------------------------------------------------------------------- /sentry_phabricator/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | sentry_phabricator 3 | ~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2011 by the Sentry Team, 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-phabricator').version 12 | except Exception, e: 13 | VERSION = 'unknown' 14 | -------------------------------------------------------------------------------- /sentry_phabricator/models.py: -------------------------------------------------------------------------------- 1 | """ 2 | sentry_phabricator.models 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2011 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | -------------------------------------------------------------------------------- /sentry_phabricator/plugin.py: -------------------------------------------------------------------------------- 1 | """ 2 | sentry_phabricator.plugin 3 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | :copyright: (c) 2011 by the Sentry Team, see AUTHORS for more details. 6 | :license: BSD, see LICENSE for more details. 7 | """ 8 | 9 | from django import forms 10 | from django.utils.translation import ugettext_lazy as _ 11 | 12 | from sentry.plugins.bases.issue import IssuePlugin 13 | 14 | import httplib 15 | import json 16 | import phabricator 17 | import sentry_phabricator 18 | import urlparse 19 | 20 | 21 | class PhabricatorOptionsForm(forms.Form): 22 | host = forms.URLField(help_text=_("e.g. http://secure.phabricator.org")) 23 | token = forms.CharField( 24 | widget=forms.TextInput(attrs={'class': 'span9'}), 25 | help_text='You may generate a Conduit API Token from your account settings in Phabricator.', 26 | required=False) 27 | username = forms.CharField( 28 | widget=forms.TextInput(attrs={'class': 'span9'}), 29 | help_text='For token-based authentication you do not need to fill in username.', 30 | required=False) 31 | certificate = forms.CharField( 32 | widget=forms.Textarea(attrs={'class': 'span9'}), 33 | help_text='For token-based authentication you do not need to fill in certificate.', 34 | required=False) 35 | projectPHIDs = forms.CharField( 36 | widget=forms.Textarea(attrs={'class': 'span9'}), 37 | help_text='Project PHIDs, use Conduit API /api/project.query to find appropriate values. e.g. ["PHID-PROJ-1", "PHID-PROJ-2"]', 38 | required=False) 39 | 40 | def clean(self): 41 | config = self.cleaned_data 42 | if not config.get('host'): 43 | raise forms.ValidationError('Missing required host configuration value') 44 | if not (config.get('token') or (config.get('username') and config.get('certificate'))): 45 | raise forms.ValidationError('Missing required authentication configuration value') 46 | projectPHIDs = config.get('projectPHIDs') 47 | if projectPHIDs: 48 | try: 49 | json.loads(projectPHIDs) 50 | except ValueError: 51 | raise forms.ValidationError('projectPHIDs field must be a valid JSON if present') 52 | api = phabricator.Phabricator( 53 | host=urlparse.urljoin(config['host'], 'api/'), 54 | username=config['username'], 55 | certificate=config['certificate'], 56 | token=config['token'], 57 | ) 58 | try: 59 | api.user.whoami() 60 | except phabricator.APIError, e: 61 | raise forms.ValidationError('%s %s' % (e.code, e.message)) 62 | except httplib.HTTPException, e: 63 | raise forms.ValidationError('Unable to reach Phabricator host: %s' % (e,)) 64 | except Exception, e: 65 | raise forms.ValidationError('Unhandled error from Phabricator: %s' % (e,)) 66 | return config 67 | 68 | 69 | class PhabricatorPlugin(IssuePlugin): 70 | author = 'DISQUS' 71 | author_url = 'https://github.com/getsentry/sentry-phabricator' 72 | version = sentry_phabricator.VERSION 73 | description = "Integrate Phabricator issue tracking by linking a user account to a project." 74 | resource_links = [ 75 | ('Bug Tracker', 'https://github.com/getsentry/sentry-phabricator/issues'), 76 | ('Source', 'https://github.com/getsentry/sentry-phabricator'), 77 | ] 78 | 79 | slug = 'phabricator' 80 | title = _('Phabricator') 81 | conf_title = 'Phabricator' 82 | conf_key = 'phabricator' 83 | project_conf_form = PhabricatorOptionsForm 84 | 85 | def get_api(self, project): 86 | # check all options are set 87 | return phabricator.Phabricator( 88 | host=urlparse.urljoin(self.get_option('host', project), 'api/'), 89 | username=self.get_option('username', project), 90 | certificate=self.get_option('certificate', project), 91 | token=self.get_option('token', project), 92 | ) 93 | 94 | def is_configured(self, project, **kwargs): 95 | if not self.get_option('host', project): 96 | return False 97 | if self.get_option('token', project): 98 | return True 99 | if self.get_option('username', project) and self.get_option('certificate', project): 100 | return True 101 | return False 102 | 103 | def get_new_issue_title(self, **kwargs): 104 | return 'Create Maniphest Task' 105 | 106 | def create_issue(self, group, form_data, **kwargs): 107 | api = self.get_api(group.project) 108 | try: 109 | phids = self.get_option('projectPHIDs', group.project) 110 | if phids: 111 | phids = json.loads(phids) 112 | data = api.maniphest.createtask( 113 | title=form_data['title'].encode('utf-8'), 114 | description=form_data['description'].encode('utf-8'), 115 | projectPHIDs=phids, 116 | ) 117 | except phabricator.APIError, e: 118 | raise forms.ValidationError('%s %s' % (e.code, e.message)) 119 | except httplib.HTTPException, e: 120 | raise forms.ValidationError('Unable to reach Phabricator host: %s' % (e.message,)) 121 | 122 | return data['id'] 123 | 124 | def get_issue_url(self, group, issue_id, **kwargs): 125 | host = self.get_option('host', group.project) 126 | return urlparse.urljoin(host, 'T%s' % issue_id) 127 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | sentry-phabricator 4 | ================== 5 | 6 | An extension for Sentry which integrates with Phabricator. Specifically, it 7 | allows you to easily create Maniphest tasks from events within Sentry. 8 | 9 | :copyright: (c) 2016 by the Sentry Team, 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 | ] 17 | 18 | install_requires = [ 19 | 'sentry>=8', 20 | 'phabricator>=0.6.0,<1.0', 21 | ] 22 | 23 | setup( 24 | name='sentry-phabricator', 25 | version='0.8.0.dev0', 26 | author='David Cramer', 27 | author_email='dcramer@gmail.com', 28 | url='http://github.com/getsentry/sentry-phabricator', 29 | description='A Sentry extension which integrates with Phabricator.', 30 | long_description=__doc__, 31 | license='BSD', 32 | packages=find_packages(exclude=['tests']), 33 | zip_safe=False, 34 | install_requires=install_requires, 35 | tests_require=tests_require, 36 | extras_require={'test': tests_require}, 37 | test_suite='runtests.runtests', 38 | include_package_data=True, 39 | entry_points={ 40 | 'sentry.apps': [ 41 | 'phabricator = sentry_phabricator', 42 | ], 43 | 'sentry.plugins': [ 44 | 'phabricator = sentry_phabricator.plugin:PhabricatorPlugin' 45 | ], 46 | }, 47 | classifiers=[ 48 | 'Framework :: Django', 49 | 'Intended Audience :: Developers', 50 | 'Intended Audience :: System Administrators', 51 | 'Operating System :: OS Independent', 52 | 'Topic :: Software Development' 53 | ], 54 | ) 55 | --------------------------------------------------------------------------------