Merge branch 'fix/default-to-today'
This commit is contained in:
@@ -72,6 +72,7 @@ redmine:
|
|||||||
period:
|
period:
|
||||||
precision: date # date | datetime
|
precision: date # date | datetime
|
||||||
default_from: "2026-06-01"
|
default_from: "2026-06-01"
|
||||||
|
# default_to можно не указывать — конец периода будет сегодня
|
||||||
default_to: "2026-06-30"
|
default_to: "2026-06-30"
|
||||||
dynamic: false
|
dynamic: false
|
||||||
# last_used заполняется --commit (см. docs/CONFIG.md)
|
# last_used заполняется --commit (см. docs/CONFIG.md)
|
||||||
@@ -115,6 +116,7 @@ REDMINE_URL=https://red.eltex.loc/
|
|||||||
REDMINE_API_KEY=ваш_api_token
|
REDMINE_API_KEY=ваш_api_token
|
||||||
REDMINE_AUTHOR=Иванов Иван Иванович
|
REDMINE_AUTHOR=Иванов Иван Иванович
|
||||||
DEFAULT_FROM_DATE=2026-01-01
|
DEFAULT_FROM_DATE=2026-01-01
|
||||||
|
# DEFAULT_TO_DATE можно не задавать — тогда конец периода будет сегодня
|
||||||
DEFAULT_TO_DATE=2026-01-31
|
DEFAULT_TO_DATE=2026-01-31
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -128,7 +130,7 @@ DEFAULT_TO_DATE=2026-01-31
|
|||||||
| `REDMINE_PASSWORD` | Да, если нет токена | Пароль Redmine. |
|
| `REDMINE_PASSWORD` | Да, если нет токена | Пароль Redmine. |
|
||||||
| `REDMINE_AUTHOR` | Нет | Имя автора для отчёта. |
|
| `REDMINE_AUTHOR` | Нет | Имя автора для отчёта. |
|
||||||
| `DEFAULT_FROM_DATE` | Нет | Начальная дата периода по умолчанию (`YYYY-MM-DD`). |
|
| `DEFAULT_FROM_DATE` | Нет | Начальная дата периода по умолчанию (`YYYY-MM-DD`). |
|
||||||
| `DEFAULT_TO_DATE` | Нет | Конечная дата периода по умолчанию (`YYYY-MM-DD`). |
|
| `DEFAULT_TO_DATE` | Нет | Конечная дата периода по умолчанию (`YYYY-MM-DD`). Если не задана, а `DEFAULT_FROM_DATE` задана — используется сегодняшняя дата. |
|
||||||
| `REDMINE_VERIFY` | Нет | TLS-проверка: `true` / `false` / путь к CA bundle. |
|
| `REDMINE_VERIFY` | Нет | TLS-проверка: `true` / `false` / путь к CA bundle. |
|
||||||
|
|
||||||
## Использование
|
## Использование
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ redmine:
|
|||||||
period:
|
period:
|
||||||
precision: date
|
precision: date
|
||||||
default_from: "2026-06-01"
|
default_from: "2026-06-01"
|
||||||
|
# default_to можно не указывать — тогда конец периода будет сегодня
|
||||||
default_to: "2026-06-30"
|
default_to: "2026-06-30"
|
||||||
dynamic: false
|
dynamic: false
|
||||||
last_used:
|
last_used:
|
||||||
@@ -73,6 +74,23 @@ email:
|
|||||||
|
|
||||||
`last_used.from` / `last_used.to` записываются автоматически при `--commit`. Вручную редактировать не требуется.
|
`last_used.from` / `last_used.to` записываются автоматически при `--commit`. Вручную редактировать не требуется.
|
||||||
|
|
||||||
|
### `period.default_to` — необязательное окончание периода
|
||||||
|
|
||||||
|
Если `period.default_to` не задан, а `period.default_from` задан, инструмент
|
||||||
|
использует сегодняшнюю дату в качестве конца периода.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
period:
|
||||||
|
default_from: "2026-07-01"
|
||||||
|
# default_to отсутствует → конец периода = сегодня
|
||||||
|
```
|
||||||
|
|
||||||
|
Это предотвращает устаревание периода, когда отчёт генерируется автоматически
|
||||||
|
(`--send`, `--commit`) без явного `--date`.
|
||||||
|
|
||||||
|
Аналогично работает `DEFAULT_TO_DATE`: если переменная не задана, а
|
||||||
|
`DEFAULT_FROM_DATE` задана, конец периода = сегодня.
|
||||||
|
|
||||||
### `--commit` — автофиксация периода
|
### `--commit` — автофиксация периода
|
||||||
|
|
||||||
Флаг `--commit` сохраняет использованный период в YAML-конфиг, чтобы следующий запуск автоматически начинался с нового периода.
|
Флаг `--commit` сохраняет использованный период в YAML-конфиг, чтобы следующий запуск автоматически начинался с нового периода.
|
||||||
@@ -353,6 +371,10 @@ vim ~/.config/redmine-reporter/config.yml
|
|||||||
| `--init-config --force` | Перезаписать существующий YAML |
|
| `--init-config --force` | Перезаписать существующий YAML |
|
||||||
| `--config-path PATH` | Сохранить YAML по указанному пути (по умолчанию `~/.config/redmine-reporter/config.yml`) |
|
| `--config-path PATH` | Сохранить YAML по указанному пути (по умолчанию `~/.config/redmine-reporter/config.yml`) |
|
||||||
|
|
||||||
|
Если `DEFAULT_TO_DATE` не задана, а `DEFAULT_FROM_DATE` задана, сгенерированный
|
||||||
|
YAML будет содержать пустое `default_to`, и при запуске инструмент использует
|
||||||
|
сегодняшнюю дату.
|
||||||
|
|
||||||
### Проверка после миграции
|
### Проверка после миграции
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "redmine-reporter"
|
name = "redmine-reporter"
|
||||||
version = "1.10.0"
|
version = "1.10.1"
|
||||||
description = "Redmine time-entry based issue reporter for internal use"
|
description = "Redmine time-entry based issue reporter for internal use"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [{ name = "Artem Kokos", email = "artem-kokos@mail.ru" }]
|
authors = [{ name = "Artem Kokos", email = "artem-kokos@mail.ru" }]
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "1.10.0"
|
__version__ = "1.10.1"
|
||||||
|
|||||||
@@ -370,8 +370,11 @@ class Config:
|
|||||||
def get_default_date_range(cls) -> str:
|
def get_default_date_range(cls) -> str:
|
||||||
from_env = os.getenv("DEFAULT_FROM_DATE", "").strip()
|
from_env = os.getenv("DEFAULT_FROM_DATE", "").strip()
|
||||||
to_env = os.getenv("DEFAULT_TO_DATE", "").strip()
|
to_env = os.getenv("DEFAULT_TO_DATE", "").strip()
|
||||||
if from_env and to_env:
|
today = date.today()
|
||||||
return f"{from_env}--{to_env}"
|
today_str = today.isoformat()
|
||||||
|
|
||||||
|
if from_env:
|
||||||
|
return f"{from_env}--{to_env or today_str}"
|
||||||
|
|
||||||
if (
|
if (
|
||||||
cls._app
|
cls._app
|
||||||
@@ -386,17 +389,12 @@ class Config:
|
|||||||
)
|
)
|
||||||
return f"{nf}--{nt}"
|
return f"{nf}--{nt}"
|
||||||
|
|
||||||
if cls._app and cls._app.period_default_from and cls._app.period_default_to:
|
if cls._app and cls._app.period_default_from:
|
||||||
return f"{cls._app.period_default_from}--{cls._app.period_default_to}"
|
default_to = cls._app.period_default_to or today_str
|
||||||
|
return f"{cls._app.period_default_from}--{default_to}"
|
||||||
|
|
||||||
today = date.today()
|
|
||||||
start = today.replace(day=1)
|
start = today.replace(day=1)
|
||||||
if today.month == 12:
|
return f"{start.isoformat()}--{today_str}"
|
||||||
next_month = today.replace(year=today.year + 1, month=1, day=1)
|
|
||||||
else:
|
|
||||||
next_month = today.replace(month=today.month + 1, day=1)
|
|
||||||
end = next_month - timedelta(days=1)
|
|
||||||
return f"{start.isoformat()}--{end.isoformat()}"
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate(cls) -> None:
|
def validate(cls) -> None:
|
||||||
|
|||||||
@@ -79,21 +79,29 @@ def test_get_default_date_range_from_env():
|
|||||||
assert Config.get_default_date_range() == "2026-01-01--2026-01-31"
|
assert Config.get_default_date_range() == "2026-01-01--2026-01-31"
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.dict(
|
||||||
|
os.environ,
|
||||||
|
{"DEFAULT_FROM_DATE": "2026-01-01"},
|
||||||
|
clear=True,
|
||||||
|
)
|
||||||
|
def test_get_default_date_range_from_env_without_to():
|
||||||
|
"""Если DEFAULT_TO_DATE не задан, конец периода — сегодня."""
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
today = date.today().isoformat()
|
||||||
|
assert Config.get_default_date_range() == f"2026-01-01--{today}"
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.dict(os.environ, {}, clear=True)
|
@mock.patch.dict(os.environ, {}, clear=True)
|
||||||
def test_get_default_date_range_fallback():
|
def test_get_default_date_range_fallback():
|
||||||
"""Если даты не заданы -- используется текущий месяц."""
|
"""Если даты не заданы -- используется текущий месяц с начала до сегодня."""
|
||||||
from datetime import date, timedelta
|
from datetime import date
|
||||||
|
|
||||||
today = date.today()
|
today = date.today()
|
||||||
start = today.replace(day=1)
|
start = today.replace(day=1)
|
||||||
if today.month == 12:
|
|
||||||
next_month = today.replace(year=today.year + 1, month=1, day=1)
|
|
||||||
else:
|
|
||||||
next_month = today.replace(month=today.month + 1, day=1)
|
|
||||||
end = next_month - timedelta(days=1)
|
|
||||||
|
|
||||||
result = Config.get_default_date_range()
|
result = Config.get_default_date_range()
|
||||||
assert result == f"{start.isoformat()}--{end.isoformat()}"
|
assert result == f"{start.isoformat()}--{today.isoformat()}"
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.dict(os.environ, {}, clear=True)
|
@mock.patch.dict(os.environ, {}, clear=True)
|
||||||
@@ -567,6 +575,24 @@ class TestDefaultDateRangeWithLastUsed:
|
|||||||
result = Config.get_default_date_range()
|
result = Config.get_default_date_range()
|
||||||
assert result == "2026-04-01--2026-04-15"
|
assert result == "2026-04-01--2026-04-15"
|
||||||
|
|
||||||
|
@mock.patch.dict(os.environ, {}, clear=True)
|
||||||
|
def test_falls_back_to_today_when_default_to_missing(self):
|
||||||
|
"""Если default_from задан, а default_to нет — конец периода сегодня."""
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
yaml_path = Path(tmp) / "config.yml"
|
||||||
|
yaml_path.write_text(
|
||||||
|
"period:\n"
|
||||||
|
" precision: date\n"
|
||||||
|
" dynamic: false\n"
|
||||||
|
" default_from: '2026-04-01'\n"
|
||||||
|
)
|
||||||
|
Config._app = AppConfig.from_yaml(yaml_path)
|
||||||
|
today = date.today().isoformat()
|
||||||
|
result = Config.get_default_date_range()
|
||||||
|
assert result == f"2026-04-01--{today}"
|
||||||
|
|
||||||
|
|
||||||
class TestGetEmailConfig:
|
class TestGetEmailConfig:
|
||||||
"""Tests for Config.get_email_config()."""
|
"""Tests for Config.get_email_config()."""
|
||||||
|
|||||||
Reference in New Issue
Block a user