Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
MSGoodman committed Apr 18, 2020
0 parents commit d685d59
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
settings.py
.idea/
47 changes: 47 additions & 0 deletions discord.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import urllib3
import json

from settings import CONFIG


class Payload:
def __init__(self, username, event):
self.username = username
self.embeds = []
self.event = event
self.data = {}

if event.is_handled():
self.embeds.append(make_embed(event.title(), event.url(), event.description(), CONFIG["colorNotification"], event.change_details()))
self.content = make_content(event.title(), event.url(), event.description(), event.change_details())
else:
warning = f"Warning: `{event.event_name}` webhook event is not handled in Jira-to-Discord AWS Lambda"
self.embeds.append(make_embed(warning, '', '', CONFIG["colorWarning"], "Auto-generated by AWS lambda function"))
self.content = make_content(warning, '', '', "Auto-generated by AWS lambda function")

self.__set_data()

def __set_data(self):
self.data = {'username': self.username}
if CONFIG["useEmbed"]:
self.data['embeds'] = self.embeds
if CONFIG["useContent"]:
self.data['content'] = self.content


def make_embed(title, url, description, color, footer_text):
return {
"title": title,
"url": url,
"description": description,
"color": color,
"footer": {
"text": footer_text
}
}

def make_content(title, url, description, footer_text):
content = f"**[{title}]({url})**\n"
if description is not None: content += f"> {description}\n"
content += footer_text
return content
111 changes: 111 additions & 0 deletions jira.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from settings import CONFIG


class JiraEvent:
def __init__(self, root_json):
self.raw_data = root_json
self.event_name = self.get_event_name()
self.target = self.event_name.split(" ")[0]
self.verb = self.event_name.split(" ", 1)[1]

self.user = None
self.comment = None
self.ticket = None
self.changelog = None

self.__populate_conditional_fields()

def get_event_name(self):
# If this is an Issue Event, get the more specific name
if 'issue_event_type_name' in self.raw_data:
raw_name = self.raw_data['issue_event_type_name']

# If this is a generic event, figure out what it should actually be called
if raw_name == 'issue_generic' and 'changelog' in self.raw_data:
raw_name = 'issue_status_changed'
else:
raw_name = self.raw_data['webhookEvent']

# Now make it pretty
return raw_name.replace("_", " ").title()

def is_handled(self):
return self.event_name in CONFIG["handledEvents"]

def get_actor(self):
if self.user:
return self.user
if self.comment:
return self.comment.update_author
elif self.ticket:
return self.ticket.assignee

def title(self):
return f'{self.ticket.key}: {self.ticket.name}'

def url(self):
if self.target in ['Issue', 'Comment']:
return f'{CONFIG["jiraRootUrl"]}{self.ticket.key}'
else:
return ''

def description(self):
if self.target == 'Issue':
# Issue created shouldn't have a description, the title is enough
if self.verb == 'Created':
return None
else:
return f'{self.changelog.changed_field.title()} changed from {self.changelog.from_value} to {self.changelog.to_value}'

if self.target == 'Comment':
# Strikethrough if the comment was deleted
if self.verb == 'Deleted':
return f'~~{self.comment.body}~~'
else:
return self.comment.body

def change_details(self):
return f'{self.target} {self.verb} by {self.get_actor().name}'

def __populate_conditional_fields(self):
if 'issue' in self.raw_data:
self.ticket = JiraTicket(self.raw_data['issue'])
if 'comment' in self.raw_data:
self.comment = JiraComment(self.raw_data['comment'])
if 'changelog' in self.raw_data:
self.changelog = JiraChangelog(self.raw_data['changelog'])
if 'user' in self.raw_data:
self.user = JiraUser(self.raw_data['user'])


class JiraChangelog:
def __init__(self, changelog_json):
self.raw_data = changelog_json
self.changed_field = changelog_json['items'][0]['field']
self.from_value = changelog_json['items'][0]['fromString']
self.to_value = changelog_json['items'][0]['toString']


class JiraTicket:
def __init__(self, ticket_json):
self.raw_data = ticket_json
self.key = ticket_json['key']
self.name = ticket_json['fields']['summary']
self.type = ticket_json['fields']['issuetype']['name']
self.assignee = JiraUser(ticket_json['fields']['assignee'])
self.priority = ticket_json['fields']['priority']['name']
self.status = ticket_json['fields']['status']['name']


class JiraComment:
def __init__(self, comment_json):
self.raw_data = comment_json
self.author = JiraUser(comment_json['author'])
self.update_author = JiraUser(comment_json['updateAuthor'])
self.body = comment_json['body']


class JiraUser:
def __init__(self, user_json):
self.raw_data = user_json
self.name = user_json['displayName']
26 changes: 26 additions & 0 deletions lambda_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import json
import urllib3

from discord import Payload
from jira import JiraEvent
from settings import CONFIG


def lambda_handler(event):
http = urllib3.PoolManager()
e = JiraEvent(event)
payload = Payload('JIRA', e)
url = CONFIG["discordWarningWebhookUrl"] if not e.is_handled() else CONFIG["discordWebhookUrl"]

r = http.request(
'POST',
'' if CONFIG["doNotSend"] else url,
headers={"Content-type": "application/json"},
body=json.dumps(payload.data).encode("utf-8"))

return {
"isBase64Encoded": False,
"statusCode": r.status,
"headers": {"headerName": "headerValue"},
"body": r.data.decode("utf-8")
}
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
An AWS lambda function that handles requests generated from Jira events, puts them into a shape Discord webhooks can understand, and forwards them to a Discord server.
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
urllib3
requests==2.23.0
# pytest==5.4.1 # Uncomment this if you want to run tests
18 changes: 18 additions & 0 deletions settings_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
CONFIG = {
'jiraRootUrl': "", # The root url of your Jira organization, e.g. https://developerorganizations.atlassian.net/browse
'discordWarningWebhookUrl': "", # The webhook url for integration failures (should be things not yet implemented), e.g. https://discordapp.com/api/webhooks/999999999999999999/ABCdefghijklmonpqrstu-vwxyz12345667810111213141516171819202122212400
'discordWebhookUrl': "", # The webhook url for jira notifications, e.g. https://discordapp.com/api/webhooks/999999999999999999/ABCdefghijklmonpqrstu-vwxyz12345667810111213141516171819202122212400
'colorNotification': 4245067, # The color you want the embeds for notifications to be
'colorWarning': 12268107, # The color you want the embeds for warnings to be
'useEmbed': True, # If you would like notifications to show up as embeds
'useContent': False, # If you would like notifications to show up as content (basically a normal post); this looks worse but if helpful because embeds don't show up if your personal settings disable link unfolding for some reason
'doNotSend': False, # If you don't want anything to actually send to the webhook, just makes it easier to turn it off for whatever reason if you don't want to disable the webhook in Jira
# These are the events I've set up for this to handle so far
'handledEvents': ['Issue Created',
'Issue Updated',
'Issue Assigned',
'Issue Status Changed',
'Comment Created',
'Comment Updated',
'Comment Deleted']
}
43 changes: 43 additions & 0 deletions test/test_lambda.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import lambda_function
import json

ISSUE_CREATED = """ Copy the raw json sent from Jira here """
ISSUE_ASSIGNED = """ Copy the raw json sent from Jira here """
ISSUE_STATUS_CHANGED = """ Copy the raw json sent from Jira here """
ISSUE_LINK_CREATED = """ Copy the raw json sent from Jira here """
COMMENT_CREATED = """ Copy the raw json sent from Jira here """
COMMENT_UPDATED = """ Copy the raw json sent from Jira here """
COMMENT_DELETED = """ Copy the raw json sent from Jira here """

ACTUALLY_HIT_DISCORD = True

def escape_chars(string):
return string.replace('\r','\\r').replace('\n','\\n')

def json_string_to_obj(string):
return json.loads(escape_chars(string))

def test_lambda_issue_created():
lambda_function.lambda_handler(json_string_to_obj(ISSUE_CREATED))
x = 0

def test_lambda_issue_assigned():
lambda_function.lambda_handler(json_string_to_obj(ISSUE_ASSIGNED))
x = 0

def test_lambda_comment_created():
lambda_function.lambda_handler(json_string_to_obj(COMMENT_CREATED))
x = 0

def test_lambda_comment_updated():
lambda_function.lambda_handler(json_string_to_obj(COMMENT_UPDATED))
x = 0

def test_lambda_comment_deleted():
lambda_function.lambda_handler(json_string_to_obj(COMMENT_DELETED))
x = 0

def test_lambda_status_changed():
lambda_function.lambda_handler(json_string_to_obj(ISSUE_STATUS_CHANGED))
x = 0

0 comments on commit d685d59

Please sign in to comment.