Extract grouping logic into shared function (#19)

Add group_rows_by_project_and_version() to report_builder.py and replace
the duplicated inline grouping in both html.py and odt.py with a single
call to the shared function. This ensures consistent grouping behavior
across formatters and removes a maintenance burden — any change to
grouping logic now happens in one place.

Closes #19
This commit is contained in:
Артём Кокос
2026-06-26 00:21:58 +07:00
parent dbc4cf960a
commit da069993b9
4 changed files with 138 additions and 26 deletions

View File

@@ -1,6 +1,7 @@
from html import escape
from typing import Dict, List
from typing import List
from ..report_builder import group_rows_by_project_and_version
from ..types import ReportRow
from .base import Formatter
@@ -12,16 +13,7 @@ class HTMLFormatter(Formatter):
super().__init__()
def format(self, rows: List[ReportRow]) -> str:
# Сгруппируем данные
projects: Dict[str, Dict[str, List[ReportRow]]] = {}
for r in rows:
proj = r["project"]
ver = r["version"]
if proj not in projects:
projects[proj] = {}
if ver not in projects[proj]:
projects[proj][ver] = []
projects[proj][ver].append(r)
projects = group_rows_by_project_and_version(rows)
lines = [
'<table border="1" cellpadding="6" cellspacing="0" style="border-collapse: collapse; font-family: Arial, sans-serif;">',

View File

@@ -1,11 +1,12 @@
from importlib import resources
from typing import Dict, List
from typing import List
from odf.opendocument import OpenDocument, load
from odf.style import Style, TableCellProperties, TableColumnProperties
from odf.table import Table, TableCell, TableColumn, TableRow
from odf.text import P
from ..report_builder import group_rows_by_project_and_version
from ..types import ReportRow
from ..utils import get_month_name_from_range
from .base import Formatter
@@ -69,18 +70,7 @@ class ODTFormatter(Formatter):
header_row.addElement(cell)
table.addElement(header_row)
projects: Dict[str, Dict[str, List[ReportRow]]] = {}
for r in rows:
project = r["project"]
version = r["version"]
if project not in projects:
projects[project] = {}
if version not in projects[project]:
projects[project][version] = []
projects[project][version].append(r)
projects = group_rows_by_project_and_version(rows)
# Данные с двухуровневой группировкой и объединением ячеек
for project, versions in projects.items():

View File

@@ -1,4 +1,4 @@
from typing import List, Tuple, cast
from typing import Dict, List, Tuple, cast
from redminelib.resources import Issue
@@ -71,3 +71,28 @@ def build_grouped_report(
prev_version = version
return rows
def group_rows_by_project_and_version(
rows: List[ReportRow],
) -> Dict[str, Dict[str, List[ReportRow]]]:
"""
Группирует плоский список строк отчёта в иерархию project → version → [rows].
Предполагается, что rows уже отсортирован (build_grouped_report это гарантирует).
Возвращает обычный dict, сохраняющий порядок вставки (Python 3.7+).
Используется форматтерами HTML и ODT для объединения ячеек.
"""
projects: Dict[str, Dict[str, List[ReportRow]]] = {}
for r in rows:
project = r["project"]
version = r["version"]
if project not in projects:
projects[project] = {}
if version not in projects[project]:
projects[project][version] = []
projects[project][version].append(r)
return projects