Files
redmine-reporter/redmine_reporter/formatters/html.py
Кокос Артем Николаевич 5d1cf4ab74 feat: add --my-tasks mode for open assigned issues (#71)
List open Redmine issues assigned to the current (or overridden) user,
sorted by priority rank (P1..P5) then version due date with a stable id
tie-break. Renders an OSC 8 hyperlinked console table on stdout and a
counts/hours summary line on stderr; version due dates are fetched once
per unique version, unavailable versions degrade to an empty due date.

Also migrates the codebase to ruff 0.17.3 defaults (PEP 585/604
annotations, targeted noqa for intentional blind excepts and CLI root
logging). Behavior-neutral: 408 tests pass with -W error.
2026-10-28 15:26:41 +07:00

85 lines
3.3 KiB
Python

from html import escape
from ..report_builder import group_rows_by_project_and_version
from ..types import ReportRow
from .base import Formatter
class HTMLFormatter(Formatter):
"""Форматтер для экспорта отчёта в HTML."""
def __init__(self, **_kwargs):
super().__init__()
def format(self, rows: list[ReportRow]) -> str:
projects = group_rows_by_project_and_version(rows)
lines = [
"<!DOCTYPE html>",
'<html lang="ru">',
"<head>",
' <meta charset="utf-8">',
"</head>",
"<body>",
'<table border="1" cellpadding="6" cellspacing="0" style="border-collapse: collapse; font-family: Arial, sans-serif;">',
" <thead>",
" <tr>",
" <th>Наименование Проекта</th>",
" <th>Номер версии*</th>",
" <th>Задача</th>",
" <th>Статус Готовность*</th>",
" <th>Затрачено за отчетный период</th>",
" </tr>",
" </thead>",
" <tbody>",
]
for project, versions in projects.items():
project_text = escape(project)
total_project_rows = sum(len(tasks) for tasks in versions.values())
first_version_in_project = True
for version, task_rows in versions.items():
version_text = escape(version)
row_span_version = len(task_rows)
first_row_in_version = True
for r in task_rows:
task_cell = escape(f"{r['issue_id']}. {r['subject']}")
status_text = escape(r["status_ru"])
time_text = escape(r["time_text"]).replace("\n", "<br>")
lines.append(" <tr>")
# Ячейка "Проект" - только в первой строке проекта
if first_version_in_project and first_row_in_version:
lines.append(
f' <td rowspan="{total_project_rows}" style="vertical-align: top;">{project_text}</td>'
)
# Ячейка "Версия" - только в первой строке версии
if first_row_in_version:
lines.append(
f' <td rowspan="{row_span_version}" style="vertical-align: top;">{version_text}</td>'
)
first_row_in_version = False
# Остальные колонки
lines.append(f" <td>{task_cell}</td>")
lines.append(f" <td>{status_text}</td>")
lines.append(f" <td>{time_text}</td>")
lines.append(" </tr>")
first_version_in_project = False
lines.append(" </tbody>")
lines.append("</table>")
lines.append("</body>")
lines.append("</html>")
return "\n".join(lines)
def save(self, rows: list[ReportRow], output_path: str) -> None:
content = self.format(rows)
with open(output_path, "w", encoding="utf-8") as f:
f.write(content)