Aggregate hours and show in table

This commit is contained in:
Кокос Артем Николаевич
2026-01-20 17:01:35 +07:00
parent 910cc31ecf
commit 7f1018a2d4
4 changed files with 38 additions and 25 deletions

View File

@@ -3,7 +3,7 @@ import argparse
from typing import List, Optional
from redminelib.resources import Issue
from .config import Config
from .client import fetch_issues_by_time_entries
from .client import fetch_issues_with_spent_time
from .formatter import format_compact, format_table
@@ -47,22 +47,22 @@ def main(argv: Optional[List[str]] = None) -> int:
return 1
try:
issues = fetch_issues_by_time_entries(from_date, to_date)
issue_hours = fetch_issues_with_spent_time(from_date, to_date)
except Exception as e:
print(f"❌ Redmine API error: {e}", file=sys.stderr)
return 1
if issues is None:
if issue_hours is None:
print(" No time entries found in the given period.", file=sys.stderr)
return 0
print(f"✅ Total issues: {len(issues)} [{args.date}]")
print(f"✅ Total issues: {len(issue_hours)} [{args.date}]")
try:
if args.compact:
output = format_compact(issues)
output = format_compact(issue_hours)
else:
output = format_table(issues)
output = format_table(issue_hours)
print(output)
except Exception as e:
print(f"❌ Formatting error: {e}", file=sys.stderr)

View File

@@ -1,12 +1,14 @@
from typing import List, Optional, Set
from typing import List, Optional, Dict, Tuple
from redminelib import Redmine
from redminelib.resources import Issue
from .config import Config
def fetch_issues_by_time_entries(from_date: str, to_date: str) -> Optional[List[Issue]]:
def fetch_issues_with_spent_time(from_date: str, to_date: str) -> Optional[List[Tuple[Issue, float]]]:
"""
Fetch unique issues linked to time entries of the current user in given date range.
Fetch unique issues linked to time entries of the current user in given date range,
along with total spent hours per issue.
Returns list of (issue, total_hours) tuples.
"""
redmine = Redmine(
@@ -23,15 +25,19 @@ def fetch_issues_by_time_entries(from_date: str, to_date: str) -> Optional[List[
to_date=to_date
)
issue_ids: Set[int] = set()
# Агрегируем часы по issue.id
spent_time: Dict[int, float] = {}
issue_ids = set()
for entry in time_entries:
if hasattr(entry, 'issue') and entry.issue:
issue_ids.add(entry.issue.id)
if hasattr(entry, 'issue') and entry.issue and hasattr(entry, 'hours'):
iid = entry.issue.id
issue_ids.add(iid)
spent_time[iid] = spent_time.get(iid, 0.0) + float(entry.hours)
if not issue_ids:
return None
# Fetch full issue objects with project/version/status
# Загружаем полные объекты задач
issue_list_str = ','.join(str(i) for i in issue_ids)
issues = redmine.issue.filter(
issue_id=issue_list_str,
@@ -39,4 +45,12 @@ def fetch_issues_by_time_entries(from_date: str, to_date: str) -> Optional[List[
sort='project:asc'
)
return list(issues)
# Сопоставляем задачи с суммарным временем
result = []
for issue in issues:
total_hours = spent_time.get(issue.id, 0.0)
result.append((issue, total_hours))
# Сортируем по проекту (Redmine API уже сортирует, но для надёжности)
result.sort(key=lambda x: str(x[0].project))
return result

View File

@@ -1,4 +1,4 @@
from typing import List
from typing import List, Tuple
from redminelib.resources import Issue
@@ -19,20 +19,19 @@ def get_version(issue: Issue) -> str:
return str(getattr(issue, 'fixed_version', '<N/A>'))
def format_compact(issues: List[Issue]) -> str:
def format_compact(issue_hours: List[Tuple[Issue, float]]) -> str:
lines = []
prev_project = None
prev_version = None
for issue in issues:
for issue, hours in issue_hours:
project = str(issue.project)
version = get_version(issue)
status = str(issue.status)
display_project = project if project != prev_project else ""
display_version = version if (project != prev_project or version != prev_version) else ""
lines.append(f"{display_project} | {display_version} | {issue.id}. {issue.subject} | {status}")
lines.append(f"{display_project} | {display_version} | {issue.id}. {issue.subject} | {status} | {hours:.2f}h")
prev_project = project
prev_version = version
@@ -40,14 +39,14 @@ def format_compact(issues: List[Issue]) -> str:
return "\n".join(lines)
def format_table(issues: List[Issue]) -> str:
def format_table(issue_hours: List[Tuple[Issue, float]]) -> str:
from tabulate import tabulate
rows = [['Проект', 'Версия', 'Задача', 'Статус', 'Затрачено']]
prev_project = None
prev_version = None
for issue in issues:
for issue, hours in issue_hours:
project = str(issue.project)
version = get_version(issue)
status_en = str(issue.status)
@@ -61,7 +60,7 @@ def format_table(issues: List[Issue]) -> str:
display_version,
f"{issue.id}. {issue.subject}",
status_ru,
"" # placeholder for spent time (future extension)
f"{hours:.2f}h"
])
prev_project = project