Handle some JIRA formatting and make it like our own

(imported from commit 1e8369067668b0c375f226c1911814f496637d16)
This commit is contained in:
Leo Franchi
2013-10-01 17:30:12 -04:00
parent 54d83a3a36
commit 028711baef
3 changed files with 322 additions and 7 deletions

View File

@@ -0,0 +1,275 @@
{
"webhookEvent": "jira:issue_updated",
"user": {
"self": "https://zulipp.atlassian.net/rest/api/2/user?username=leo",
"name": "leo",
"emailAddress": "leo@zulip.com",
"avatarUrls": {
"16x16": "https://zulipp.atlassian.net/secure/useravatar?size=xsmall&avatarId=10122",
"24x24": "https://zulipp.atlassian.net/secure/useravatar?size=small&avatarId=10122",
"32x32": "https://zulipp.atlassian.net/secure/useravatar?size=medium&avatarId=10122",
"48x48": "https://zulipp.atlassian.net/secure/useravatar?avatarId=10122"
},
"displayName": "Leonardo Franchi [Administrator]",
"active": true
},
"issue": {
"id": "10100",
"self": "https://zulipp.atlassian.net/rest/api/2/issue/10100",
"key": "TEST-7",
"fields": {
"summary": "Testing of rich text",
"progress": {
"progress": 0,
"total": 0
},
"timetracking": {},
"issuetype": {
"self": "https://zulipp.atlassian.net/rest/api/2/issuetype/2",
"id": "2",
"description": "A new feature of the product, which has yet to be developed.",
"iconUrl": "https://zulipp.atlassian.net/images/icons/issuetypes/newfeature.png",
"name": "New Feature",
"subtask": false
},
"timespent": null,
"reporter": {
"self": "https://zulipp.atlassian.net/rest/api/2/user?username=leo",
"name": "leo",
"emailAddress": "leo@zulip.com",
"avatarUrls": {
"16x16": "https://zulipp.atlassian.net/secure/useravatar?size=xsmall&avatarId=10122",
"24x24": "https://zulipp.atlassian.net/secure/useravatar?size=small&avatarId=10122",
"32x32": "https://zulipp.atlassian.net/secure/useravatar?size=medium&avatarId=10122",
"48x48": "https://zulipp.atlassian.net/secure/useravatar?avatarId=10122"
},
"displayName": "Leonardo Franchi [Administrator]",
"active": true
},
"created": "2013-10-01T16:08:48.446-0400",
"updated": "2013-10-01T16:16:47.821-0400",
"priority": {
"self": "https://zulipp.atlassian.net/rest/api/2/priority/3",
"iconUrl": "https://zulipp.atlassian.net/images/icons/priorities/major.png",
"name": "Major",
"id": "3"
},
"description": "h1. So this is a heading\r\n\r\nAnd some *bold* **and _bold_?\r\n\r\n{quote}\r\nsome stuff goes here\r\n{quote}",
"customfield_10001": null,
"customfield_10002": null,
"customfield_10003": null,
"issuelinks": [],
"customfield_10000": null,
"subtasks": [],
"customfield_10008": null,
"customfield_10007": null,
"status": {
"self": "https://zulipp.atlassian.net/rest/api/2/status/10001",
"description": "",
"iconUrl": "https://zulipp.atlassian.net/images/icons/statuses/open.png",
"name": "To Do",
"id": "10001"
},
"customfield_10006": "7",
"labels": [],
"workratio": -1,
"project": {
"self": "https://zulipp.atlassian.net/rest/api/2/project/10000",
"id": "10000",
"key": "TEST",
"name": "TestProject",
"avatarUrls": {
"16x16": "https://zulipp.atlassian.net/secure/projectavatar?size=xsmall&pid=10000&avatarId=10011",
"24x24": "https://zulipp.atlassian.net/secure/projectavatar?size=small&pid=10000&avatarId=10011",
"32x32": "https://zulipp.atlassian.net/secure/projectavatar?size=medium&pid=10000&avatarId=10011",
"48x48": "https://zulipp.atlassian.net/secure/projectavatar?pid=10000&avatarId=10011"
}
},
"environment": null,
"customfield_10014": null,
"customfield_10015": null,
"lastViewed": "2013-10-01T16:16:48.075-0400",
"aggregateprogress": {
"progress": 0,
"total": 0
},
"customfield_10012": null,
"components": [],
"customfield_10013": null,
"comment": {
"startAt": 0,
"maxResults": 3,
"total": 3,
"comments": [
{
"self": "https://zulipp.atlassian.net/rest/api/2/issue/10100/comment/10000",
"id": "10000",
"author": {
"self": "https://zulipp.atlassian.net/rest/api/2/user?username=leo",
"name": "leo",
"emailAddress": "leo@zulip.com",
"avatarUrls": {
"16x16": "https://zulipp.atlassian.net/secure/useravatar?size=xsmall&avatarId=10122",
"24x24": "https://zulipp.atlassian.net/secure/useravatar?size=small&avatarId=10122",
"32x32": "https://zulipp.atlassian.net/secure/useravatar?size=medium&avatarId=10122",
"48x48": "https://zulipp.atlassian.net/secure/useravatar?avatarId=10122"
},
"displayName": "Leonardo Franchi [Administrator]",
"active": true
},
"body": "Commenting that *this is important* and also _please italicize me_\r\n\r\nh2. Heading this all the way\r\n\r\nsome quote:\r\n\r\n{quote}\r\noh say can you see...\r\n{quote}",
"updateAuthor": {
"self": "https://zulipp.atlassian.net/rest/api/2/user?username=leo",
"name": "leo",
"emailAddress": "leo@zulip.com",
"avatarUrls": {
"16x16": "https://zulipp.atlassian.net/secure/useravatar?size=xsmall&avatarId=10122",
"24x24": "https://zulipp.atlassian.net/secure/useravatar?size=small&avatarId=10122",
"32x32": "https://zulipp.atlassian.net/secure/useravatar?size=medium&avatarId=10122",
"48x48": "https://zulipp.atlassian.net/secure/useravatar?avatarId=10122"
},
"displayName": "Leonardo Franchi [Administrator]",
"active": true
},
"created": "2013-10-01T16:11:07.615-0400",
"updated": "2013-10-01T16:11:07.615-0400"
},
{
"self": "https://zulipp.atlassian.net/rest/api/2/issue/10100/comment/10001",
"id": "10001",
"author": {
"self": "https://zulipp.atlassian.net/rest/api/2/user?username=leo",
"name": "leo",
"emailAddress": "leo@zulip.com",
"avatarUrls": {
"16x16": "https://zulipp.atlassian.net/secure/useravatar?size=xsmall&avatarId=10122",
"24x24": "https://zulipp.atlassian.net/secure/useravatar?size=small&avatarId=10122",
"32x32": "https://zulipp.atlassian.net/secure/useravatar?size=medium&avatarId=10122",
"48x48": "https://zulipp.atlassian.net/secure/useravatar?avatarId=10122"
},
"displayName": "Leonardo Franchi [Administrator]",
"active": true
},
"body": "*Rich Text* comment, with _italicized_ text,\r\n\r\nand including some\r\n\r\n{quote}\r\nquotations by yours truly\r\n{quote}",
"updateAuthor": {
"self": "https://zulipp.atlassian.net/rest/api/2/user?username=leo",
"name": "leo",
"emailAddress": "leo@zulip.com",
"avatarUrls": {
"16x16": "https://zulipp.atlassian.net/secure/useravatar?size=xsmall&avatarId=10122",
"24x24": "https://zulipp.atlassian.net/secure/useravatar?size=small&avatarId=10122",
"32x32": "https://zulipp.atlassian.net/secure/useravatar?size=medium&avatarId=10122",
"48x48": "https://zulipp.atlassian.net/secure/useravatar?avatarId=10122"
},
"displayName": "Leonardo Franchi [Administrator]",
"active": true
},
"created": "2013-10-01T16:16:47.821-0400",
"updated": "2013-10-01T16:16:47.821-0400"
},
{
"self": "https://zulipp.atlassian.net/rest/api/2/issue/10100/comment/10002",
"id": "10002",
"author": {
"self": "https://zulipp.atlassian.net/rest/api/2/user?username=leo",
"name": "leo",
"emailAddress": "leo@zulip.com",
"avatarUrls": {
"16x16": "https://zulipp.atlassian.net/secure/useravatar?size=xsmall&avatarId=10122",
"24x24": "https://zulipp.atlassian.net/secure/useravatar?size=small&avatarId=10122",
"32x32": "https://zulipp.atlassian.net/secure/useravatar?size=medium&avatarId=10122",
"48x48": "https://zulipp.atlassian.net/secure/useravatar?avatarId=10122"
},
"displayName": "Leonardo Franchi [Administrator]",
"active": true
},
"body": "This is a comment that likes to *exercise* a lot of _different_ {{conventions}} that {{jira uses}}.\r\n\r\n{noformat}\r\nthis code is not highlighted, but monospaced\r\n{noformat}\r\n\r\n{code:python}\r\ndef python():\r\n print \"likes to be formatted\"\r\n{code}\r\n\r\n[http://www.google.com] is a bare link, and [Google|http://www.google.com] is given a title.\r\n\r\nThanks!\r\n\r\n{quote}\r\nSomeone said somewhere\r\n{quote}",
"updateAuthor": {
"self": "https://zulipp.atlassian.net/rest/api/2/user?username=leo",
"name": "leo",
"emailAddress": "leo@zulip.com",
"avatarUrls": {
"16x16": "https://zulipp.atlassian.net/secure/useravatar?size=xsmall&avatarId=10122",
"24x24": "https://zulipp.atlassian.net/secure/useravatar?size=small&avatarId=10122",
"32x32": "https://zulipp.atlassian.net/secure/useravatar?size=medium&avatarId=10122",
"48x48": "https://zulipp.atlassian.net/secure/useravatar?avatarId=10122"
},
"displayName": "Leonardo Franchi [Administrator]",
"active": true
},
"created": "2013-10-01T17:03:00.964-0400",
"updated": "2013-10-01T17:03:00.964-0400"
}
]
},
"timeoriginalestimate": null,
"customfield_10017": null,
"customfield_10016": null,
"customfield_10019": null,
"customfield_10018": null,
"votes": {
"self": "https://zulipp.atlassian.net/rest/api/2/issue/TEST-7/votes",
"votes": 0,
"hasVoted": false
},
"fixVersions": [],
"resolution": null,
"resolutiondate": null,
"aggregatetimeoriginalestimate": null,
"duedate": null,
"customfield_10020": null,
"customfield_10021": "Not Started",
"watches": {
"self": "https://zulipp.atlassian.net/rest/api/2/issue/TEST-7/watchers",
"watchCount": 1,
"isWatching": true
},
"worklog": {
"startAt": 0,
"maxResults": 20,
"total": 0,
"worklogs": []
},
"assignee": null,
"attachment": [],
"aggregatetimeestimate": null,
"versions": [],
"timeestimate": null,
"aggregatetimespent": null
}
},
"comment": {
"self": "https://zulipp.atlassian.net/rest/api/2/issue/10100/comment/10002",
"id": "10002",
"author": {
"self": "https://zulipp.atlassian.net/rest/api/2/user?username=leo",
"name": "leo",
"emailAddress": "leo@zulip.com",
"avatarUrls": {
"16x16": "https://zulipp.atlassian.net/secure/useravatar?size=xsmall&avatarId=10122",
"24x24": "https://zulipp.atlassian.net/secure/useravatar?size=small&avatarId=10122",
"32x32": "https://zulipp.atlassian.net/secure/useravatar?size=medium&avatarId=10122",
"48x48": "https://zulipp.atlassian.net/secure/useravatar?avatarId=10122"
},
"displayName": "Leonardo Franchi [Administrator]",
"active": true
},
"body": "This is a comment that likes to *exercise* a lot of _different_ {{conventions}} that {{jira uses}}.\r\n\r\n{noformat}\r\nthis code is not highlighted, but monospaced\r\n{noformat}\r\n\r\n{code:python}\r\ndef python():\r\n print \"likes to be formatted\"\r\n{code}\r\n\r\n[http://www.google.com] is a bare link, and [Google|http://www.google.com] is given a title.\r\n\r\nThanks!\r\n\r\n{quote}\r\nSomeone said somewhere\r\n{quote}",
"updateAuthor": {
"self": "https://zulipp.atlassian.net/rest/api/2/user?username=leo",
"name": "leo",
"emailAddress": "leo@zulip.com",
"avatarUrls": {
"16x16": "https://zulipp.atlassian.net/secure/useravatar?size=xsmall&avatarId=10122",
"24x24": "https://zulipp.atlassian.net/secure/useravatar?size=small&avatarId=10122",
"32x32": "https://zulipp.atlassian.net/secure/useravatar?size=medium&avatarId=10122",
"48x48": "https://zulipp.atlassian.net/secure/useravatar?avatarId=10122"
},
"displayName": "Leonardo Franchi [Administrator]",
"active": true
},
"created": "2013-10-01T17:03:00.964-0400",
"updated": "2013-10-01T17:03:00.964-0400"
},
"timestamp": 1380661380969
}

View File

@@ -3445,9 +3445,13 @@ class JiraHookTests(AuthedTestCase):
self.assertEqual(msg.content, """Leo Franchi **updated** [BUG-15](http://lfranchi.com:8080/browse/BUG-15):
~~~ quote
Adding a comment. Oh, what a comment it is!
~~~""")
""")
def test_commented_markup(self):
msg = self.send_jira_message('commented_markup')
self.assertEqual(msg.subject, "TEST-7: Testing of rich text")
self.assertEqual(msg.content, """Leonardo Franchi [Administrator] **updated** [TEST-7](https://zulipp.atlassian.net/browse/TEST-7):\n\n\nThis is a comment that likes to **exercise** a lot of _different_ `conventions` that `jira uses`.\r\n\r\n~~~\n\r\nthis code is not highlighted, but monospaced\r\n\n~~~\r\n\r\n~~~\n\r\ndef python():\r\n print "likes to be formatted"\r\n\n~~~\r\n\r\n[http://www.google.com](http://www.google.com) is a bare link, and [Google](http://www.google.com) is given a title.\r\n\r\nThanks!\r\n\r\n~~~ quote\n\r\nSomeone said somewhere\r\n\n~~~\n""")
def test_deleted(self):
msg = self.send_jira_message('deleted')
@@ -3469,9 +3473,8 @@ Adding a comment. Oh, what a comment it is!
* Changed status from **Resolved** to **Reopened**
~~~ quote
Re-opened yeah!
~~~""")
""")
def test_resolved(self):
msg = self.send_jira_message('resolved')
@@ -3482,9 +3485,8 @@ Re-opened yeah!
* Changed status from **Open** to **Resolved**
* Changed assignee from **None** to **Leo Franchi**
~~~ quote
Fixed it, finally!
~~~""")
""")
def test_workflow_postfuncion(self):
msg = self.send_jira_message('postfunction_hook')

View File

@@ -17,6 +17,7 @@ import base64
import logging
import re
import ujson
import markdown.inlinepatterns
from functools import wraps
def github_generic_subject(noun, repository, blob):
@@ -173,6 +174,42 @@ def elide_subject(subject):
subject = subject[:57].rstrip() + '...'
return subject
def convert_jira_markup(content):
# Attempt to do some simplistic conversion of JIRA
# formatting to Markdown, for consumption in Zulip
# Jira uses *word* for bold, we use **word**
content = re.sub(r'\*([^\*]+)\*', r'**\1**', content)
# Jira uses {{word}} for monospacing, we use `word`
content = re.sub(r'{{([^\*]+?)}}', r'`\1`', content)
# Starting a line with bq. block quotes that line
content = re.sub(r'bq\. (.*)', r'> \1', content)
# Wrapping a block of code in {quote}stuff{quote} also block-quotes it
quote_re = re.compile(r'{quote}(.*?){quote}', re.DOTALL)
content = re.sub(quote_re, r'~~~ quote\n\1\n~~~', content)
# {noformat}stuff{noformat} blocks are just code blocks with no
# syntax highlighting
noformat_re = re.compile(r'{noformat}(.*?){noformat}', re.DOTALL)
content = re.sub(noformat_re, r'~~~\n\1\n~~~', content)
# Code blocks are delineated by {code[: lang]} {code}
code_re = re.compile(r'{code[^\n]*}(.*?){code}', re.DOTALL)
content = re.sub(code_re, r'~~~\n\1\n~~~', content)
# Links are of form: [https://www.google.com] or [Link Title|https://www.google.com]
# In order to support both forms, we don't match a | in bare links
content = re.sub(r'\[([^\|]+?)\]', r'[\1](\1)', content)
# Full links which have a | are converted into a better markdown link
full_link_re = re.compile(r'\[(?:(?P<title>[^|]+)\|)(?P<url>.*)\]')
content = re.sub(full_link_re, r'[\g<title>](\g<url>)', content)
return content
@csrf_exempt
def api_jira_webhook(request):
try:
@@ -245,7 +282,8 @@ def api_jira_webhook(request):
content += "* Changed %s from **%s** to **%s**\n" % (field, item.get('fromString'), item.get('toString'))
if comment != '':
content += "\n~~~ quote\n%s\n~~~" % (comment,)
comment = convert_jira_markup(comment)
content += "\n%s\n" % (comment,)
elif 'transition' in payload:
from_status = get_in(payload, ['transition', 'from_status'])
to_status = get_in(payload, ['transition', 'to_status'])