feat(activity): add --by-activity flag to break down spent time by activity type
- Load time entry activity names from Redmine enumeration. - Aggregate hours per issue and per activity in fetch_issues_with_spent_time. - Add --by-activity CLI flag and propagate it through report builder, summary, and all formatters (console, CSV, HTML, JSON, ODT). - Keep backward-compatible 2-tuple input in build_grouped_report. - Bump version to 1.8.0. Closes #41
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from typing import Dict, List, Tuple, cast
|
||||
from typing import Dict, List, Optional, Tuple, cast
|
||||
|
||||
from redminelib.resources import Issue
|
||||
|
||||
@@ -22,9 +22,19 @@ STATUS_TRANSLATION = {
|
||||
}
|
||||
|
||||
|
||||
def _format_activities(activities: Dict[str, float]) -> str:
|
||||
"""Форматирует разбивку по активностям в многострочный текст."""
|
||||
if not activities:
|
||||
return ""
|
||||
# Сортируем по убыванию часов, затем по алфавиту для стабильности
|
||||
items = sorted(activities.items(), key=lambda x: (-x[1], x[0]))
|
||||
return "\n".join(f"{hours_to_human(hours)} {name}" for name, hours in items)
|
||||
|
||||
|
||||
def build_grouped_report(
|
||||
issue_hours: List[Tuple[Issue, float]],
|
||||
issue_hours: List[Tuple[Issue, float, Optional[Dict[str, float]]]],
|
||||
fill_time: bool = True,
|
||||
by_activity: bool = False,
|
||||
) -> List[ReportRow]:
|
||||
"""
|
||||
Преобразует список задач с затраченным временем в плоский список строк отчёта,
|
||||
@@ -41,12 +51,20 @@ def build_grouped_report(
|
||||
prev_project: str = ""
|
||||
prev_version: str = ""
|
||||
|
||||
for issue, hours in issue_hours:
|
||||
for issue, hours, *rest in issue_hours:
|
||||
activities: Optional[Dict[str, float]] = rest[0] if rest else None
|
||||
project = str(issue.project)
|
||||
version = get_version(issue)
|
||||
status_en = str(issue.status)
|
||||
status_ru = STATUS_TRANSLATION.get(status_en, status_en)
|
||||
time_text = hours_to_human(hours) if fill_time else ""
|
||||
|
||||
if fill_time:
|
||||
if by_activity and activities:
|
||||
time_text = _format_activities(activities)
|
||||
else:
|
||||
time_text = hours_to_human(hours)
|
||||
else:
|
||||
time_text = ""
|
||||
|
||||
display_project = project if project != prev_project else ""
|
||||
display_version = version if (project != prev_project or version != prev_version) else ""
|
||||
@@ -64,6 +82,7 @@ def build_grouped_report(
|
||||
"status_ru": status_ru,
|
||||
"time_text": time_text,
|
||||
"hours": round(hours, 2),
|
||||
"activities": activities,
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -74,11 +93,15 @@ def build_grouped_report(
|
||||
return rows
|
||||
|
||||
|
||||
def calculate_summary(rows: List[ReportRow]) -> Dict[str, float]:
|
||||
"""Возвращает сводку: общее время, время по проектам и версиям."""
|
||||
def calculate_summary(
|
||||
rows: List[ReportRow],
|
||||
by_activity: bool = False,
|
||||
) -> Dict[str, float]:
|
||||
"""Возвращает сводку: общее время, время по проектам, версиям и активностям."""
|
||||
total = 0.0
|
||||
by_project: Dict[str, float] = {}
|
||||
by_project_version: Dict[str, float] = {}
|
||||
by_activity_name: Dict[str, float] = {}
|
||||
|
||||
for r in rows:
|
||||
hours = r.get("hours", 0.0)
|
||||
@@ -87,11 +110,21 @@ def calculate_summary(rows: List[ReportRow]) -> Dict[str, float]:
|
||||
key = f"{r['project']}::{r['version']}"
|
||||
by_project_version[key] = by_project_version.get(key, 0.0) + hours
|
||||
|
||||
return {
|
||||
if by_activity:
|
||||
activities = r.get("activities")
|
||||
if activities:
|
||||
for name, act_hours in activities.items():
|
||||
by_activity_name[name] = by_activity_name.get(name, 0.0) + act_hours
|
||||
|
||||
result: Dict[str, float] = {
|
||||
"total": round(total, 2),
|
||||
**{f"project:{k}": round(v, 2) for k, v in by_project.items()},
|
||||
**{f"version:{k}": round(v, 2) for k, v in by_project_version.items()},
|
||||
}
|
||||
if by_activity:
|
||||
result.update({f"activity:{k}": round(v, 2) for k, v in by_activity_name.items()})
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def group_rows_by_project_and_version(
|
||||
|
||||
Reference in New Issue
Block a user