Compare commits
3 commits
9beae26d0d
...
18508fd549
Author | SHA1 | Date | |
---|---|---|---|
18508fd549 | |||
4ba2eb66a0 | |||
71afacbbcb |
7 changed files with 44 additions and 46 deletions
|
@ -44,10 +44,11 @@ def get_story_transitions(token: str, project_id: int,
|
|||
after: Optional[datetime] = None,
|
||||
before: Optional[datetime] = None) \
|
||||
-> List[Dict[str, Any]]:
|
||||
parameters = {
|
||||
'occurred_after': after.isoformat() if after else None,
|
||||
'occurred_before': before.isoformat() if before else None,
|
||||
}
|
||||
parameters = {}
|
||||
if after:
|
||||
parameters['occurred_after'] = after.isoformat()
|
||||
if before:
|
||||
parameters['occurred_before'] = before.isoformat()
|
||||
|
||||
r = requests.get(
|
||||
f'https://www.pivotaltracker.com/services/v5/projects/{project_id}'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import api.projects
|
||||
from typing import Dict, Any
|
||||
from typing import Dict, Any, Optional
|
||||
from color import Color
|
||||
|
||||
COLOR_TITLE = Color(Color.YELLOW)
|
||||
|
@ -14,15 +14,15 @@ COLOR_ACCEPTED = Color(Color.BRIGHT_GREEN)
|
|||
COLOR_REJECTED = Color(Color.BRIGHT_RED)
|
||||
|
||||
|
||||
def _format_state(state: str) -> str:
|
||||
def _format_state(state: str, header: Optional[bool] = False) -> str:
|
||||
STATES = {
|
||||
'accepted': COLOR_ACCEPTED.format('accepted'),
|
||||
'rejected': COLOR_REJECTED.format('rejected'),
|
||||
'delivered': COLOR_DELIVERED.format('delivered'),
|
||||
'finished': COLOR_FINISHED.format('finished'),
|
||||
'started': COLOR_STARTED.format('started'),
|
||||
'planned': COLOR_PLANNED.format('planned'),
|
||||
'unstarted': COLOR_PLANNED.format('unstarted'),
|
||||
'accepted': COLOR_ACCEPTED.format('Acc' if header else 'accepted'),
|
||||
'rejected': COLOR_REJECTED.format('Rej' if header else 'rejected'),
|
||||
'delivered': COLOR_DELIVERED.format('Del' if header else 'delivered'),
|
||||
'finished': COLOR_FINISHED.format('Fin' if header else 'finished'),
|
||||
'started': COLOR_STARTED.format('Sta' if header else 'started'),
|
||||
'planned': COLOR_PLANNED.format('Pln' if header else 'planned'),
|
||||
'unstarted': COLOR_PLANNED.format('Uns' if header else 'unstarted'),
|
||||
}
|
||||
|
||||
return STATES[state]
|
||||
|
|
|
@ -133,13 +133,13 @@ def __print_blockers(token: str, project_id: int, story_id: int) -> None:
|
|||
print(f' [{resolved}] {desc}')
|
||||
|
||||
|
||||
def stories_info(story: str) -> None:
|
||||
def stories_info(story_b32: str) -> None:
|
||||
try:
|
||||
token = Config['user']['api_token']
|
||||
except KeyError:
|
||||
sys.exit(1)
|
||||
|
||||
story_id = base32.decode(story)
|
||||
story_id = base32.decode(story_b32)
|
||||
story = api.stories.get(token, story_id)
|
||||
|
||||
project_id = story['project_id']
|
||||
|
|
|
@ -30,7 +30,7 @@ def projects(context: click.Context) -> None:
|
|||
table = []
|
||||
for project in sorted(projects_, key=lambda project_: project_['name']):
|
||||
code = base32.encode(project['id'])
|
||||
alias_ = aliases.get(project['id'])
|
||||
alias_ = aliases.get(project['id'], '')
|
||||
table.append((code, project['name'], alias_))
|
||||
|
||||
print(tabulate.tabulate(table, headers=('Code', 'Name', 'Alias')))
|
||||
|
|
|
@ -13,8 +13,8 @@ from config import Config
|
|||
from util import require_login
|
||||
|
||||
from . import COLOR_HEADER, COLOR_PLANNED, COLOR_STARTED, COLOR_FINISHED, \
|
||||
COLOR_DELIVERED, COLOR_ACCEPTED, COLOR_REJECTED, _format_state, \
|
||||
_get_persons
|
||||
COLOR_DELIVERED, COLOR_ACCEPTED, COLOR_REJECTED, _format_state, \
|
||||
_get_persons
|
||||
from ._stories_info import stories_info
|
||||
from .cli import cli
|
||||
|
||||
|
@ -26,8 +26,8 @@ Persons = Dict[int, Dict[str, Any]]
|
|||
Totals = DefaultDict[int, Dict[str, int]]
|
||||
|
||||
|
||||
def __ceil(value: float) -> int:
|
||||
return int(math.ceil(value))
|
||||
def __burndown(color, letter, points):
|
||||
return color.format(letter * int(math.ceil(points)))
|
||||
|
||||
|
||||
def __get_row(item: Tuple[int, Dict[str, int]], persons: Persons,
|
||||
|
@ -37,17 +37,17 @@ def __get_row(item: Tuple[int, Dict[str, int]], persons: Persons,
|
|||
|
||||
estimates = [points[state] for state in STATES]
|
||||
|
||||
progress = '[' + \
|
||||
COLOR_PLANNED.format('P' * __ceil(points['planned'])) + \
|
||||
COLOR_REJECTED.format('R' * __ceil(points['rejected'])) + \
|
||||
COLOR_STARTED.format('S' * __ceil(points['started'])) + \
|
||||
COLOR_FINISHED.format('F' * __ceil(points['finished'])) + \
|
||||
COLOR_DELIVERED.format('D' * __ceil(points['delivered']))
|
||||
|
||||
progress = '['
|
||||
if show_accepted:
|
||||
progress += COLOR_ACCEPTED.format('A' * __ceil(points['accepted']))
|
||||
progress += __burndown(COLOR_ACCEPTED, 'A', points['accepted'])
|
||||
|
||||
progress += ']'
|
||||
progress += \
|
||||
__burndown(COLOR_DELIVERED, 'D', points['delivered']) + \
|
||||
__burndown(COLOR_FINISHED, 'F', points['finished']) + \
|
||||
__burndown(COLOR_STARTED, 'S', points['started']) + \
|
||||
__burndown(COLOR_REJECTED, 'R', points['rejected']) + \
|
||||
__burndown(COLOR_PLANNED, 'P', points['planned']) + \
|
||||
']'
|
||||
|
||||
return name, (*estimates), sum(estimates), progress
|
||||
|
||||
|
@ -96,7 +96,7 @@ def __print_totals(totals: Totals, persons: Persons, show_accepted: bool) \
|
|||
-> None:
|
||||
COLOR_HEADER.print('Point totals:', end='\n\n')
|
||||
|
||||
state_headers = [_format_state(state) for state in STATES]
|
||||
state_headers = [_format_state(state, header=True) for state in STATES]
|
||||
headers = ('Owner', *state_headers, 'Total', 'Progress')
|
||||
|
||||
data = sorted((__get_row(item, persons, show_accepted)
|
||||
|
@ -128,15 +128,15 @@ def __print_burndown(token: str, iteration: Dict[str, Any],
|
|||
continue
|
||||
|
||||
progress = ''
|
||||
|
||||
if show_accepted:
|
||||
progress += '\033[92m' + 'A' * round(counts[0] - accepted_points)
|
||||
|
||||
progress += '\033[38;5;208m' + 'D' * round(counts[1])
|
||||
progress += '\033[94m' + 'F' * round(counts[2])
|
||||
progress += '\033[38;5;226m' + 'S' * round(counts[3])
|
||||
progress += '\033[90m' + 'P' * round(counts[5])
|
||||
progress += '\033[0m'
|
||||
progress += __burndown(COLOR_ACCEPTED, 'A',
|
||||
counts[0] - accepted_points)
|
||||
progress += \
|
||||
__burndown(COLOR_DELIVERED, 'D', counts[1]) + \
|
||||
__burndown(COLOR_FINISHED, 'F', counts[2]) + \
|
||||
__burndown(COLOR_STARTED, 'S', counts[3]) + \
|
||||
__burndown(COLOR_PLANNED, 'P', counts[5]) + \
|
||||
__burndown(COLOR_PLANNED, 'U', counts[6])
|
||||
|
||||
print(f'{date}: {progress}')
|
||||
|
||||
|
@ -174,14 +174,8 @@ def _set_story_state(story: str, state: str) -> None:
|
|||
api.stories.put_story(token, story_id, current_state=state)
|
||||
|
||||
|
||||
def _complete_projects(ctx: click.Context, args: List[str], incomplete: str) \
|
||||
-> List[str]:
|
||||
return [alias for alias in Config['project_aliases']
|
||||
if alias.startswith(incomplete)]
|
||||
|
||||
@cli.command('stories')
|
||||
@click.argument(
|
||||
'project', type=click.STRING, autocompletion=_complete_projects)
|
||||
@click.argument('project', type=click.STRING)
|
||||
@click.argument('story', required=False)
|
||||
@click.argument('action', required=False)
|
||||
@click.option('--scope', default='current')
|
||||
|
|
|
@ -6,3 +6,4 @@ click-fish==0.1.0
|
|||
click==7.0
|
||||
requests==2.18.4
|
||||
tabulate==0.8.2
|
||||
appdirs==1.4.3
|
4
util.py
4
util.py
|
@ -1,5 +1,6 @@
|
|||
import shutil
|
||||
import textwrap
|
||||
from commands.login import login
|
||||
from typing import Any, Callable
|
||||
|
||||
from config import Config
|
||||
|
@ -25,8 +26,9 @@ def print_wrap(text: str, indent: str = '', end: str = '\n') -> None:
|
|||
|
||||
def require_login(function: Callable) -> Callable:
|
||||
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
while 'api_token' not in Config['user']:
|
||||
if 'api_token' not in Config['user']:
|
||||
print('Not logged in. Please use the login command to log in.')
|
||||
return
|
||||
|
||||
return function(*args, **kwargs)
|
||||
|
||||
|
|
Loading…
Reference in a new issue