Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/jira enhancement #200

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions mdk/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,28 @@
http://github.com/FMCorz/mdk
"""

def add_color_to_log_record(fn):
def new(*args):
from .ansi_escape_codes import AnsiCodes
import logging
levelno = args[1].levelno
if levelno >= logging.CRITICAL:
color = AnsiCodes['RED']
elif levelno == logging.ERROR:
color = AnsiCodes['RED']
elif levelno == logging.WARNING:
color = AnsiCodes['YELLOW']
elif levelno == logging.INFO:
color = AnsiCodes['GREEN']
elif levelno == logging.DEBUG:
color = AnsiCodes['GRAY']
else:
color = AnsiCodes['RESET']
args[1].color = color
args[1].color_reset = AnsiCodes['RESET']
return fn(*args)
return new

def main():

import sys
Expand All @@ -43,8 +65,10 @@ def main():
except AttributeError:
debuglevel = logging.INFO

# Set logging levels.
logging.basicConfig(format='%(message)s', level=debuglevel)
FORMAT = C.get('logging.format')
# Set logging format and levels.
logging.basicConfig(format=FORMAT, level=debuglevel)
logging.StreamHandler.emit = add_color_to_log_record(logging.StreamHandler.emit)
logging.getLogger('requests').setLevel(logging.WARNING) # Reset logging level of 'requests' module.
logging.getLogger('keyring.backend').setLevel(logging.WARNING)

Expand Down
12 changes: 12 additions & 0 deletions mdk/ansi_escape_codes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
AnsiCodes = {
'RED': '\033[31m',
'MAGENTA': '\u001b[35m',
'YELLOW': '\033[33m',
'GRAY': '\033[90m',
'BLUE': '\033[94m',
'CYAN': '\033[96m',
'GREEN': '\033[92m',
'RESET': '\033[0m',
'BOLD': '\033[1m',
'UNDERLINE': '\033[4m',
}
130 changes: 117 additions & 13 deletions mdk/commands/tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,72 @@
from ..command import Command
from ..jira import Jira
from ..tools import parseBranch, getText
from ..config import Conf
from ..ansi_escape_codes import AnsiCodes

def highlightJiraText(text):
C = Conf()
emojimap = C.get('tracker.emoji')
flags = re.IGNORECASE

for key in emojimap:
value = emojimap[key]
text = text.replace(key, value)
# {{monospaced}}
text = re.sub(r'\{\{(.*?)\}\}', AnsiCodes['GREEN'] + r'\1' + AnsiCodes['RESET'], text, flags=flags)
# {quote}
text = re.sub(r'\{quote\}(.*?)\{quote\}', AnsiCodes['GRAY'] + r'\1' + AnsiCodes['RESET'], text, flags=flags)
# {color:red}
text = re.sub(
r'\{color:(\w+)\}(.*?)\{color\}',
lambda matchobj: "{0}{1}{2}".format(
AnsiCodes.get(matchobj.group(1).strip().upper(), AnsiCodes['YELLOW']),
matchobj.group(2),
AnsiCodes['RESET'],
),
text,
flags=flags,
)
# *text*
text = re.sub(r'\*(.*?)\*', AnsiCodes['CYAN'] + r'\1' + AnsiCodes['RESET'], text, flags=flags)
# !https://image.com/image!: remove
text = re.sub(r'\!(.*?)\!', '', text, flags=re.IGNORECASE)
# mention
text = re.sub(r'\[\~(.*?)\]', AnsiCodes['RED'] + r'~\1' + AnsiCodes['RESET'], text, flags=flags)
# list
text = re.sub(r'^\s*([\#\-\*])\s*(.*)', AnsiCodes['GREEN'] + r'\1' + AnsiCodes['RESET'] + r' \2', text, flags=flags)
# header
text = re.sub(r'^\s*h([1-7])\.\s*(.*)', AnsiCodes['MAGENTA'] + r'h\1.' + AnsiCodes['RESET'] + r' \2', text, flags=flags)
# mdl
text = re.sub(r'mdl\-(\d+)', AnsiCodes['BLUE'] + r'MDL-\1' + AnsiCodes['RESET'], text, flags=flags)
return text


class TrackerCommand(Command):

_arguments = [
(
['--comments'],
{
'action': 'store_true',
'help': 'include comments'
}
),
(
['--number'],
{
'action': 'store',
'default': 5,
'help': 'number of comments to fetch'
}
),
(
['--no-colors'],
{
'action': 'store_true',
'help': 'Don\'t use colors'
}
),
(
['-t', '--testing'],
{
Expand Down Expand Up @@ -95,8 +156,9 @@ def run(self, args):
if not issue or not re.match('(MDL|mdl)?(-|_)?[1-9]+', issue):
raise Exception('Invalid or unknown issue number')

self.Jira = Jira()

self.mdl = 'MDL-' + re.sub(r'(MDL|mdl)(-|_)?', '', issue)
self.Jira = Jira()

if args.addlabels:
if 'triaged' in args.addlabels:
Expand All @@ -119,6 +181,7 @@ def run(self, args):
self.info(args)

def info(self, args):
self.no_colors = args.no_colors
"""Display classic information about an issue"""
issue = self.Jira.getIssue(self.mdl)

Expand All @@ -128,39 +191,80 @@ def info(self, args):
resolutiondate = ''
if issue['fields'].get('resolutiondate') != None:
resolutiondate = datetime.strftime(Jira.parseDate(issue['fields'].get('resolutiondate')), '%Y-%m-%d %H:%M')
print('-' * 72)
self.printSeparator()
for l in textwrap.wrap(title, 68, initial_indent=' ', subsequent_indent=' '):
print(l)
print(' {0} - {1} - {2}'.format(issue['fields']['issuetype']['name'], issue['fields']['priority']['name'], 'https://tracker.moodle.org/browse/' + issue['key']))
status = '{0} {1} {2}'.format(issue['fields']['status']['name'], resolution, resolutiondate).strip()
print(' {0}'.format(status))

print('-' * 72)
components = '{0}: {1}'.format('Components', ', '.join([c['name'] for c in issue['fields']['components']]))
self.printSeparator()
label = 'Components'
if not self.no_colors:
label = AnsiCodes['YELLOW'] + label + AnsiCodes['RESET']
components = '{0}: {1}'.format(label, ', '.join([c['name'] for c in issue['fields']['components']]))
for l in textwrap.wrap(components, 68, initial_indent=' ', subsequent_indent=' '):
print(l)
if issue['fields']['labels']:
labels = '{0}: {1}'.format('Labels', ', '.join(issue['fields']['labels']))
label = 'Labels'
if not self.no_colors:
label = AnsiCodes['YELLOW'] + label + AnsiCodes['RESET']
labels = '{0}: {1}'.format(label, ', '.join(issue['fields']['labels']))
for l in textwrap.wrap(labels, 68, initial_indent=' ', subsequent_indent=' '):
print(l)

vw = '[ V: %d - W: %d ]' % (issue['fields']['votes']['votes'], issue['fields']['watches']['watchCount'])
print('{0:->70}--'.format(vw))
print('{0:<20}: {1} ({2}) on {3}'.format('Reporter', issue['fields']['reporter']['displayName'], issue['fields']['reporter']['name'], created))
self.printField('{0} ({1}) on {2}', 'Reporter', issue['fields']['reporter']['displayName'], issue['fields']['reporter']['name'], created)

if issue['fields'].get('assignee') != None:
print('{0:<20}: {1} ({2})'.format('Assignee', issue['fields']['assignee']['displayName'], issue['fields']['assignee']['name']))
self.printField('{0} ({1})', 'Assignee', issue['fields']['assignee']['displayName'], issue['fields']['assignee']['name'])
if issue['named'].get('Peer reviewer'):
print('{0:<20}: {1} ({2})'.format('Peer reviewer', issue['named']['Peer reviewer']['displayName'], issue['named']['Peer reviewer']['name']))
self.printField('{0} ({1})', 'Peer reviewer', issue['named']['Peer reviewer']['displayName'], issue['named']['Peer reviewer']['name'])
if issue['named'].get('Integrator'):
print('{0:<20}: {1} ({2})'.format('Integrator', issue['named']['Integrator']['displayName'], issue['named']['Integrator']['name']))
self.printField('{0} ({1})', 'Integrator', issue['named']['Integrator']['displayName'], issue['named']['Integrator']['name'])
if issue['named'].get('Tester'):
print('{0:<20}: {1} ({2})'.format('Tester', issue['named']['Tester']['displayName'], issue['named']['Tester']['name']))
self.printField('{0} ({1})', 'Tester', issue['named']['Tester']['displayName'], issue['named']['Tester']['name'])

if args.comments:
comments = self.Jira.getIssueComments(self.mdl, maxResults=args.number)
self.printSeparator()
print('Comments:')
for comment in comments:
self.printComment(comment, hideCommenters=['cibot'])

if args.testing and issue['named'].get('Testing Instructions'):
print('-' * 72)
self.printSeparator()
print('Testing instructions:')
for l in issue['named'].get('Testing Instructions').split('\r\n'):
print(' ' + l)
self.printTestingInstructions(issue['named'].get('Testing Instructions'))
self.printSeparator()

def printSeparator(self):
print('-' * 72)

def printField(self, template, label, *args):
padding = 20
if not self.no_colors:
padding = 28
label = AnsiCodes['YELLOW'] + label + AnsiCodes['RESET']
print(label.ljust(padding) + ' : ' + template.format(*args))

def printTestingInstructions(self, text):
lines = text.split('\r\n')
for l in lines:
padding = ' '
if not self.no_colors:
l = highlightJiraText(l)
print(padding + l)

def printComment(self, comment, hideCommenters=[]):
author = '[%s]' % (comment['author']['displayName'])
print('{0:->70}--'.format(author))
print()
lines = comment['body'].replace('\n\n', '\r\n').replace('\r\n', '\n').split('\n')
for l in lines:
for wrapped in textwrap.wrap(l, 68):
text = wrapped
if not self.no_colors:
text = highlightJiraText(wrapped)
print(' ' + text)
21 changes: 21 additions & 0 deletions mdk/config-dist.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,24 @@

// The information for integrating MDK with Jira
"tracker": {
"emoji": {
":)": "😀",
":-)": "😀",
":(": "🙁",
":P": "😛",
":D": "😁",
";)": "😉",
"(y)": "👍",
"(n)": "👎",
"(!)": "⚠",
"(?)": "❓",
"(i)": "ℹ",
"(+)": "➕",
"(-)": "➖",
"(/)": "✅",
"(*)": "⭐",
"(x)": "❌"
},
"url": "https://tracker.moodle.org/",
"username": false,
"fieldnames" : {
Expand Down Expand Up @@ -281,6 +299,9 @@

// Debug level of MDK. 'debug', 'info', 'warning', 'error' or 'critical'.
"debug": "info",
"logging": {
"format": "%(color)s> %(message)s%(color_reset)s"
},

// When enabled, MDK will try to be smart to identify the parent commit of the current branch. Instead of
// using the stable branch it will match the branch name with an issue number and compare it with the commit
Expand Down
14 changes: 14 additions & 0 deletions mdk/jira.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,20 @@ def getAttachments(self, key):
}
return attachments

def getIssueComments(self, key, maxResults=5):
querystring = {'orderBy': '-created', 'maxResults': maxResults}
resp = self.request('issue/%s/comment' % (str(key)), params=querystring)

if resp['status'] == 404:
raise JiraIssueNotFoundException('Issue could not be found.')
elif not resp['status'] == 200:
raise JiraException('The tracker is not available.')

comments = resp['data']['comments']
comments.reverse()

return comments

def getIssue(self, key, fields='*all,-comment'):
"""Load the issue info from the jira server using a rest api call.

Expand Down