2018-04-03 10:56:07 +00:00
|
|
|
import re
|
|
|
|
import sys
|
|
|
|
from datetime import datetime
|
|
|
|
from typing import Any, Dict
|
|
|
|
|
2018-07-22 09:45:15 +00:00
|
|
|
import base32_crockford as base32
|
2018-04-03 10:56:07 +00:00
|
|
|
|
|
|
|
import api.stories
|
|
|
|
from config import Config
|
|
|
|
from util import print_wrap
|
|
|
|
from . import (COLOR_HEADER, COLOR_TITLE, COLOR_WHITE, _format_state,
|
|
|
|
_get_persons)
|
|
|
|
|
|
|
|
|
2018-07-22 09:45:15 +00:00
|
|
|
def __print_story(story: Dict[str, Any]) -> None:
|
2018-04-03 10:56:07 +00:00
|
|
|
"""
|
|
|
|
Prints the title, the current state and the estimate of the story, if
|
|
|
|
available.
|
|
|
|
|
|
|
|
TODO: Split up in functions.
|
|
|
|
"""
|
|
|
|
COLOR_TITLE.print(story['name'], end='\n\n')
|
|
|
|
|
|
|
|
if 'current_state' in story:
|
|
|
|
state = _format_state(story['current_state'])
|
2018-07-22 09:45:15 +00:00
|
|
|
COLOR_HEADER.print('State:', state, end='')
|
|
|
|
|
|
|
|
if story['current_state'] == 'accepted':
|
|
|
|
print(f" (at {story['accepted_at']})", end='')
|
|
|
|
|
|
|
|
print(end='\n\n')
|
2018-04-03 10:56:07 +00:00
|
|
|
|
|
|
|
if 'estimate' in story:
|
|
|
|
COLOR_HEADER.print('Estimate: ', end='')
|
|
|
|
print(story['estimate'], 'points', end='')
|
|
|
|
if len(story.get('owner_ids', [])) > 1:
|
|
|
|
points = story['estimate'] / len(story['owner_ids'])
|
|
|
|
print(f' ({points} each)', end='')
|
|
|
|
print(end='\n\n')
|
|
|
|
|
|
|
|
|
|
|
|
def __print_owners(story: Dict[str, Any], persons: Dict[int, Any]) -> None:
|
|
|
|
"""Prints the owners of the story, if available."""
|
|
|
|
if story.get('owner_ids'):
|
|
|
|
COLOR_HEADER.print('Owners:')
|
|
|
|
|
|
|
|
owners = []
|
|
|
|
for owner_id in story['owner_ids']:
|
|
|
|
name = persons[owner_id]['name']
|
|
|
|
initials = persons[owner_id]['initials']
|
|
|
|
owners.append(f' - {name} ({initials})')
|
|
|
|
|
|
|
|
print('\n'.join(owners), end='\n\n')
|
|
|
|
|
|
|
|
|
|
|
|
def __print_description(story: Dict[str, Any]) -> None:
|
|
|
|
"""Prints the description of the story, if available."""
|
|
|
|
COLOR_HEADER.print('Description:')
|
|
|
|
if 'description' in story:
|
|
|
|
description = story['description'].strip()
|
|
|
|
print_wrap(description, indent=' ', end='\n\n')
|
|
|
|
else:
|
|
|
|
print(' (No description)', end='\n\n')
|
|
|
|
|
|
|
|
|
|
|
|
def __print_labels(story: Dict[str, Any]) -> None:
|
|
|
|
"""Prints the labels of the story, if available."""
|
|
|
|
if not story.get('labels'):
|
|
|
|
return
|
|
|
|
|
|
|
|
COLOR_HEADER.print('Labels:')
|
|
|
|
|
|
|
|
template = '\033[97;48;5;22m {} \033[0m'
|
|
|
|
labels = ' '.join(template.format(label['name'])
|
|
|
|
for label in story['labels'])
|
|
|
|
|
|
|
|
print(f' {labels}', end='\n\n')
|
|
|
|
|
|
|
|
|
|
|
|
def __print_tasks(token: str, project_id: int, story_id: int) -> None:
|
|
|
|
"""Prints the tasks of the story, if available."""
|
|
|
|
tasks = api.stories.get_tasks(token, project_id, story_id)
|
|
|
|
|
|
|
|
if tasks:
|
|
|
|
COLOR_HEADER.print('Tasks:')
|
|
|
|
|
|
|
|
for task in tasks:
|
|
|
|
print(end=' ')
|
|
|
|
print('[X]' if task['complete'] else '[ ]', end=' \033[34m')
|
2018-07-22 09:45:15 +00:00
|
|
|
print(base32.encode(task['id']), end=':\033[0m ')
|
2018-04-03 10:56:07 +00:00
|
|
|
print(task['description'])
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
|
|
def __print_comments(token: str, project_id: int, story_id: int,
|
|
|
|
persons: Dict[int, Dict[str, Any]]) -> None:
|
|
|
|
"""Prints the comments on the story, if available."""
|
|
|
|
comments = api.stories.get_comments(token, project_id, story_id)
|
|
|
|
|
|
|
|
if comments:
|
|
|
|
COLOR_HEADER.print('Comments:')
|
|
|
|
|
|
|
|
for comment in comments:
|
|
|
|
text = comment['text'].strip()
|
|
|
|
print_wrap(text, indent=' ')
|
|
|
|
|
|
|
|
person_id = comment['person_id']
|
|
|
|
name = persons[person_id]['name']
|
|
|
|
COLOR_WHITE.print(' -', name, end=' ')
|
|
|
|
|
|
|
|
date = datetime.strptime(comment['created_at'], '%Y-%m-%dT%H:%M:%SZ')
|
|
|
|
date_str = date.strftime('on %a %Y-%m-%d at %H:%M')
|
|
|
|
print(date_str, end='\n\n')
|
|
|
|
|
|
|
|
|
|
|
|
def __print_blockers(token: str, project_id: int, story_id: int) -> None:
|
|
|
|
"""Prints the stories that block this story, if available."""
|
|
|
|
blockers = api.stories.get_blockers(token, project_id, story_id)
|
|
|
|
|
|
|
|
if blockers:
|
|
|
|
COLOR_HEADER.print('Blockers:')
|
|
|
|
|
|
|
|
def blocker_repl(matchgroup: Any) -> str:
|
|
|
|
id = int(matchgroup.group(1))
|
2018-07-22 09:45:15 +00:00
|
|
|
code = base32.encode(id)
|
2018-04-03 10:56:07 +00:00
|
|
|
return COLOR_HEADER.format(code)
|
|
|
|
|
|
|
|
pattern = re.compile(r'#(\d+)')
|
|
|
|
for blocker in blockers:
|
|
|
|
resolved = 'X' if blocker['resolved'] else ' '
|
|
|
|
desc = pattern.sub(blocker_repl, blocker['description'])
|
|
|
|
print(f' [{resolved}] {desc}')
|
|
|
|
|
|
|
|
|
2018-07-22 09:45:15 +00:00
|
|
|
def stories_info(story: str) -> None:
|
2018-04-03 10:56:07 +00:00
|
|
|
try:
|
|
|
|
token = Config['user']['api_token']
|
|
|
|
except KeyError:
|
|
|
|
sys.exit(1)
|
|
|
|
|
2018-07-22 09:45:15 +00:00
|
|
|
story_id = base32.decode(story)
|
2018-04-03 10:56:07 +00:00
|
|
|
story = api.stories.get(token, story_id)
|
|
|
|
|
|
|
|
project_id = story['project_id']
|
|
|
|
persons = _get_persons(token, project_id)
|
|
|
|
|
2018-07-22 09:45:15 +00:00
|
|
|
__print_story(story)
|
2018-04-03 10:56:07 +00:00
|
|
|
__print_owners(story, persons)
|
|
|
|
__print_description(story)
|
|
|
|
__print_labels(story)
|
|
|
|
__print_tasks(token, project_id, story_id)
|
|
|
|
__print_comments(token, project_id, story_id, persons)
|
|
|
|
__print_blockers(token, project_id, story_id)
|