Skip to content

Commit

Permalink
Merge branch 'master' of github.com:StackStorm-Exchange/stackstorm-jira
Browse files Browse the repository at this point in the history
  • Loading branch information
Kami committed Jan 23, 2019
2 parents 635c0fb + db9596a commit 7ce6784
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 5 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
@@ -1,5 +1,9 @@
# Change Log

## 0.9.0

- Add new ``jira.issues_tracker_for_apiv2`` sensor

## 0.8.1

- Version bump to fix tagging issue, no code changes
Expand Down
5 changes: 4 additions & 1 deletion pack.yaml
Expand Up @@ -6,6 +6,9 @@ keywords:
- issues
- ticket management
- project management
version: 0.8.1
version: 0.9.0
python_versions:
- "2"
- "3"
author : StackStorm, Inc.
email : info@stackstorm.com
8 changes: 4 additions & 4 deletions requirements.txt
@@ -1,4 +1,4 @@
jira
pyyaml
cryptography
pyjwt
jira==2.0.0
pyyaml==3.13
cryptography==2.4.1
pyjwt==1.6.4
113 changes: 113 additions & 0 deletions sensors/jira_sensor_for_apiv2.py
@@ -0,0 +1,113 @@
# See ./requirements.txt for requirements.
import os

from jira.client import JIRA

from st2reactor.sensor.base import PollingSensor


class JIRASensorForAPIv2(PollingSensor):
'''
Sensor will monitor for any new projects created in JIRA and
emit trigger instance when one is created.
'''
def __init__(self, sensor_service, config=None, poll_interval=5):
super(JIRASensorForAPIv2, self).__init__(sensor_service=sensor_service,
config=config,
poll_interval=poll_interval)

self._jira_url = None
# The Consumer Key created while setting up the "Incoming Authentication" in
# JIRA for the Application Link.
self._consumer_key = u''
self._rsa_key = None
self._jira_client = None
self._access_token = u''
self._access_secret = u''
self._projects_available = None
self._poll_interval = 30
self._project = None
self._issues_in_project = None
self._jql_query = None
self._trigger_name = 'issues_tracker_for_apiv2'
self._trigger_pack = 'jira'
self._trigger_ref = '.'.join([self._trigger_pack, self._trigger_name])

def _read_cert(self, file_path):
with open(file_path) as f:
return f.read()

def setup(self):
self._jira_url = self._config['url']
auth_method = self._config['auth_method']

if auth_method == 'oauth':
rsa_cert_file = self._config['rsa_cert_file']
if not os.path.exists(rsa_cert_file):
raise Exception('Cert file for JIRA OAuth not found at %s.' % rsa_cert_file)
self._rsa_key = self._read_cert(rsa_cert_file)
self._poll_interval = self._config.get('poll_interval', self._poll_interval)
oauth_creds = {
'access_token': self._config['oauth_token'],
'access_token_secret': self._config['oauth_secret'],
'consumer_key': self._config['consumer_key'],
'key_cert': self._rsa_key
}

self._jira_client = JIRA(options={'server': self._jira_url},
oauth=oauth_creds)
elif auth_method == 'basic':
basic_creds = (self._config['username'], self._config['password'])
self._jira_client = JIRA(options={'server': self._jira_url},
basic_auth=basic_creds)

else:
msg = ('You must set auth_method to either "oauth"',
'or "basic" your jira.yaml config file.')
raise Exception(msg)

if self._projects_available is None:
self._projects_available = set()
for proj in self._jira_client.projects():
self._projects_available.add(proj.key)
self._project = self._config.get('project', None)
if not self._project or self._project not in self._projects_available:
raise Exception('Invalid project (%s) to track.' % self._project)
self._jql_query = 'project=%s' % self._project
all_issues = self._jira_client.search_issues(self._jql_query, maxResults=None)
self._issues_in_project = {issue.key: issue for issue in all_issues}

def poll(self):
self._detect_new_issues()

def cleanup(self):
pass

def add_trigger(self, trigger):
pass

def update_trigger(self, trigger):
pass

def remove_trigger(self, trigger):
pass

def _detect_new_issues(self):
new_issues = self._jira_client.search_issues(self._jql_query, maxResults=50, startAt=0)

for issue in new_issues:
if issue.key not in self._issues_in_project:
self._dispatch_issues_trigger(issue)
self._issues_in_project[issue.key] = issue

def _dispatch_issues_trigger(self, issue):
trigger = self._trigger_ref
payload = {}
payload['project'] = self._project
payload['id'] = issue.id
payload['expand'] = issue.raw.get('expand', '')
payload['issue_key'] = issue.key
payload['issue_url'] = issue.self
payload['issue_browse_url'] = self._jira_url + '/browse/' + issue.key
payload['fields'] = issue.raw.get('fields', {})
self._sensor_service.dispatch(trigger, payload)
26 changes: 26 additions & 0 deletions sensors/jira_sensor_for_apiv2.yaml
@@ -0,0 +1,26 @@
---
class_name: "JIRASensorForAPIv2"
entry_point: "jira_sensor_for_apiv2.py"
description: "Sensor which monitors JIRA for new tickets"
poll_interval: 30
trigger_types:
-
name: "issues_tracker_for_apiv2"
description: "Trigger which indicates that a new issue has been created"
payload_schema:
type: "object"
properties:
project:
type: "string"
id:
type: "string"
expand:
type: "string"
issue_key:
type: "string"
issue_url:
type: "string"
issue_browse_url:
type: "string"
fields:
type: "object"
93 changes: 93 additions & 0 deletions tests/test_sensor_jira_sensor_for_apiv2.py
@@ -0,0 +1,93 @@
import mock

from st2tests.base import BaseSensorTestCase

from jira_sensor_for_apiv2 import JIRASensorForAPIv2

JIRA_URL = "http://jira.hoge.com/"
PROJECT_NAME = "PROJECT"
ISSUE_ID = "112"
ISSUE_SELF = "http://jira.hoge.com/rest/api/2/issue/" + ISSUE_ID
ISSUE_KEY = "ISSUEKEY-1"

ISSUE = {
"project": PROJECT_NAME,
"id": ISSUE_ID,
"expand": "html,editmeta,changelog",
"fields": {
"assignee": None,
"creator": {
"displayName": "user01@test",
"name": "user-name"
},
"issuetype": {
"name": "task"
},
"components": [
{
"id": "421203",
"name": "test_components"
}
]
}
}

MOCK_ISSUE_RAW = ISSUE.copy()
MOCK_ISSUE_RAW["key"] = ISSUE_KEY
MOCK_ISSUE_RAW["self"] = ISSUE_SELF

PAYLOAD = ISSUE.copy()
PAYLOAD["issue_key"] = ISSUE_KEY
PAYLOAD["issue_url"] = ISSUE_SELF
PAYLOAD["issue_browse_url"] = JIRA_URL + '/browse/' + ISSUE_KEY

TRIGGER = {
"trace_context": None,
"trigger": "jira.issues_tracker_for_apiv2",
"payload": PAYLOAD
}


class JIRASensorForAPIv2TestCase(BaseSensorTestCase):
maxDiff = None
sensor_cls = JIRASensorForAPIv2

def test_poll(self):
sensor = self.get_sensor_instance()
sensor._jira_client = mock.Mock()
sensor._jira_client.search_issues.return_value = []
sensor._issues_in_project = {}

# no issues
sensor.poll()
self.assertEqual(self.get_dispatched_triggers(), [])

# 1 new issue
issue = mock.Mock()
issue.raw = MOCK_ISSUE_RAW
issue.id = ISSUE_ID
issue.self = ISSUE_SELF
issue.key = ISSUE_KEY

sensor._project = PROJECT_NAME
sensor._jira_url = JIRA_URL
sensor._jira_client.search_issues.return_value = [issue]

sensor.poll()
payload = self.get_dispatched_triggers()[0]['payload']
self.assertEqual(payload, PAYLOAD)

# 1 new issue
issue = mock.Mock()
issue.raw = MOCK_ISSUE_RAW
issue.id = ISSUE_ID
issue.self = ISSUE_SELF
issue.key = ISSUE_KEY

sensor._project = PROJECT_NAME
sensor._jira_url = JIRA_URL
sensor._jira_client.search_issues.return_value = [issue]

sensor.poll()
payload = self.get_dispatched_triggers()[0]['payload']
self.assertEqual(payload, PAYLOAD)

0 comments on commit 7ce6784

Please sign in to comment.