Browse Source

feat(plugins): adds teamwork plugin (#15856)

Stephen Cefali 5 years ago
parent
commit
72b6c1448f

+ 2 - 0
setup.py

@@ -145,6 +145,7 @@ setup(
             "jira = sentry_plugins.jira",
             "freight = sentry_plugins.freight",
             "sessionstack = sentry_plugins.sessionstack",
+            "teamwork = sentry_plugins.teamwork",
         ],
         "sentry.plugins": [
             "amazon_sqs = sentry_plugins.amazon_sqs.plugin:AmazonSQSPlugin",
@@ -165,6 +166,7 @@ setup(
             "sessionstack = sentry_plugins.sessionstack.plugin:SessionStackPlugin",
             "slack = sentry_plugins.slack.plugin:SlackPlugin",
             "splunk = sentry_plugins.splunk.plugin:SplunkPlugin",
+            "teamwork = sentry_plugins.teamwork.plugin:TeamworkPlugin",
             "victorops = sentry_plugins.victorops.plugin:VictorOpsPlugin",
             "vsts = sentry_plugins.vsts.plugin:VstsPlugin",
         ],

+ 1 - 0
src/sentry_plugins/teamwork/__init__.py

@@ -0,0 +1 @@
+from __future__ import absolute_import

+ 38 - 0
src/sentry_plugins/teamwork/client.py

@@ -0,0 +1,38 @@
+from __future__ import absolute_import
+
+from sentry import http
+from sentry.utils import json
+
+
+class TeamworkClient(object):
+    def __init__(self, base_url, token, timeout=5):
+        self.base_url = base_url
+        self._token = token
+        self._timeout = timeout
+
+    def _request(self, path, method="GET", params=None, data=None):
+        path = path.lstrip("/")
+        url = "%s/%s" % (self.base_url, path)
+
+        if not params:
+            params = {}
+
+        session = http.build_session()
+        resp = getattr(session, method.lower())(
+            url, auth=(self._token, ""), params=params, json=data, timeout=self._timeout
+        )
+        resp.raise_for_status()
+        return json.loads(resp.content)
+
+    def list_projects(self):
+        return self._request(path="/projects.json")["projects"]
+
+    def list_tasklists(self, project_id):
+        return self._request(path="/projects/{0}/tasklists.json".format(project_id))["tasklists"]
+
+    def create_task(self, tasklist_id, **kwargs):
+        return self._request(
+            method="POST",
+            path="/tasklists/{0}/tasks.json".format(tasklist_id),
+            data={"todo-item": kwargs},
+        )["id"]

+ 146 - 0
src/sentry_plugins/teamwork/plugin.py

@@ -0,0 +1,146 @@
+from __future__ import absolute_import
+
+import six
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+from django.http import HttpResponse
+from requests.exceptions import RequestException
+
+
+import sentry
+from sentry.plugins.base import JSONResponse
+from sentry.plugins.bases.issue import IssuePlugin, NewIssueForm
+from sentry.utils.http import absolute_uri
+
+from .client import TeamworkClient
+
+ISSUES_URL = "https://github.com/getsentry/sentry-teamwork/issues"
+
+
+class TeamworkSettingsForm(forms.Form):
+    url = forms.URLField(label=_("Teamwork URL"), help_text=("i.e. http://sentry.teamwork.com"))
+    token = forms.CharField(label=_("Teamwork API Token"))
+
+
+class TeamworkTaskForm(NewIssueForm):
+    title = forms.CharField(
+        label=_("Title"), max_length=200, widget=forms.TextInput(attrs={"class": "span9"})
+    )
+    description = forms.CharField(
+        label=_("Description"), widget=forms.Textarea(attrs={"class": "span9"})
+    )
+    project = forms.ChoiceField(label=_("Project"), choices=())
+    tasklist = forms.ChoiceField(label=_("Task List"), choices=())
+
+    create_issue_template = "sentry_teamwork/create_issue.html"
+
+    def __init__(self, client, data=None, initial=None):
+        super(TeamworkTaskForm, self).__init__(data=data, initial=initial)
+
+        try:
+            project_list = client.list_projects()
+        except RequestException as e:
+            raise forms.ValidationError(_("Error contacting Teamwork API: %s") % six.binary_type(e))
+
+        self.fields["project"].choices = [
+            (six.binary_type(i["id"]), i["name"]) for i in project_list
+        ]
+        self.fields["project"].widget.choices = self.fields["project"].choices
+
+        if self.data.get("project"):
+            try:
+                tasklist_list = client.list_tasklists(data["project"])
+            except RequestException as e:
+                raise forms.ValidationError(
+                    _("Error contacting Teamwork API: %s") % six.binary_type(e)
+                )
+            self.fields["tasklist"].choices = [
+                (six.binary_type(i["id"]), i["name"]) for i in tasklist_list
+            ]
+            self.fields["tasklist"].widget.choices = self.fields["tasklist"].choices
+
+
+class TeamworkPlugin(IssuePlugin):
+    author = "Sentry Team"
+    author_url = "https://github.com/getsentry/sentry"
+    title = _("Teamwork")
+    description = _("Create Teamwork Tasks.")
+    slug = "teamwork"
+
+    resource_links = [
+        (_("Bug Tracker"), ISSUES_URL),
+        (_("Source"), "https://github.com/getsentry/sentry"),
+    ]
+
+    conf_title = title
+    conf_key = slug
+
+    version = sentry.VERSION
+    project_conf_form = TeamworkSettingsForm
+
+    new_issue_form = TeamworkTaskForm
+    create_issue_template = "sentry_teamwork/create_issue.html"
+
+    def _get_group_description(self, request, group, event):
+        """
+        Return group description in markdown-compatible format.
+
+        This overrides an internal method to IssuePlugin.
+        """
+        output = [absolute_uri(group.get_absolute_url())]
+        body = self._get_group_body(request, group, event)
+        if body:
+            output.extend(["", "\n".join("    " + line for line in body.splitlines())])
+        return "\n".join(output)
+
+    def is_configured(self, request, project, **kwargs):
+        return all((self.get_option(key, project) for key in ("url", "token")))
+
+    def get_client(self, project):
+        return TeamworkClient(
+            base_url=self.get_option("url", project), token=self.get_option("token", project)
+        )
+
+    def get_new_issue_form(self, request, group, event, **kwargs):
+        """
+        Return a Form for the "Create new issue" page.
+        """
+        return self.new_issue_form(
+            client=self.get_client(group.project),
+            data=request.POST or None,
+            initial=self.get_initial_form_data(request, group, event),
+        )
+
+    def get_issue_url(self, group, issue_id, **kwargs):
+        url = self.get_option("url", group.project)
+        return "%s/tasks/%s" % (url.rstrip("/"), issue_id)
+
+    def get_new_issue_title(self, **kwargs):
+        return _("Create Teamwork Task")
+
+    def create_issue(self, request, group, form_data, **kwargs):
+        client = self.get_client(group.project)
+        try:
+            task_id = client.create_task(
+                content=form_data["title"],
+                description=form_data["description"],
+                tasklist_id=form_data["tasklist"],
+            )
+        except RequestException as e:
+            raise forms.ValidationError(_("Error creating Teamwork task: %s") % six.binary_type(e))
+
+        return task_id
+
+    def view(self, request, group, **kwargs):
+        op = request.GET.get("op")
+        # TODO(dcramer): add caching
+        if op == "getTaskLists":
+            project_id = request.GET.get("pid")
+            if not project_id:
+                return HttpResponse(status=400)
+
+            client = self.get_client(group.project)
+            task_list = client.list_tasklists(project_id)
+            return JSONResponse([{"id": i["id"], "text": i["name"]} for i in task_list])
+
+        return super(TeamworkPlugin, self).view(request, group, **kwargs)

+ 27 - 0
src/sentry_plugins/teamwork/templates/sentry_teamwork/create_issue.html

@@ -0,0 +1,27 @@
+{% extends "sentry/plugins/bases/issue/create_issue.html" %}
+
+{% block meta %}
+  {{ block.super }}
+  <script>
+    $(function(){
+      $("#id_project").on('change', function(){
+        $('#id_tasklist').select2("readonly", true);
+        $.ajax({
+          url: '?op=getTaskLists&pid=' + $(this).val(),
+          dataType: 'json',
+          success: function(data) {
+            $('#id_tasklist').html('');
+            for (var i=0; i<data.length; i++) {
+              var opt = $("<option/>");
+              opt.text(data[i].text);
+              opt.attr("value", data[i].id);
+              $('#id_tasklist').append(opt);
+            }
+            $('#id_tasklist').select2("readonly", false);
+            $('#id_tasklist').select2();
+          }
+        });
+      }).trigger('change');
+    });
+  </script>
+{% endblock %}