Skip to content

Commit e125d3e

Browse files
authored
feat(panel): Help & feedback page — report a bug or suggest a feature from Home Assistant (#1109)
* feat(panel): Help & feedback page — report a bug or suggest a feature from Home Assistant A new System → Help & feedback page in the admin panel builds a pre-filled GitHub issue from the repo's own issue templates and opens it for the parent to check and submit. Nothing is sent from Home Assistant. - Bug and feature forms mirroring bug_report.yml / feature_request.yml, required fields enforced, live character counts, draft kept in the browser - Setup details filled in automatically via a new admin-only taskmate/feedback/info command: TaskMate and HA versions, installation type, HACS default/custom/manual, Lovelace mode, family counts (counts only), card design, health-check codes - Detected drop-down answers can be corrected; GitHub can't pre-select drop-downs, so they also travel as text in Additional context - Recent TaskMate warnings/errors from HA's system log, with names, phone and person ids, signed-URL signatures, tokens and e-mail addresses redacted; oldest entries dropped first to stay under GitHub's link length limit - Similar-issue search on GitHub as the title is typed - Diagnostics download, Copy as Markdown (works on plain http), search link - Warns when the browser is running an older cached panel than the installed integration, and points to the health check when it found problems - Still works when the integration can't answer, using the browser's versions - Bug template gains a "HACS (default)" installation option - Translated into all locales; docs page + nav entry * feat(issues): pre-fill every required field from the Help & feedback link GitHub fills text fields from an issue link but never pre-selects a drop-down, so a panel report still arrived with "None" in three required drop-downs. - Bug template: Home Assistant installation type, TaskMate installation method and Lovelace mode are now text fields, each listing its valid answers, so the panel fills them in - Bug "reproducible"/"affected area" and feature "area"/"priority" stay drop-downs for people filing directly on GitHub, but are optional; the panel writes those answers into the issue text instead - The install answers no longer repeat in the issue text (Copy as Markdown keeps them, having no separate fields) - GitHub's button is now "Create": the panel and docs say so - Test: no required template field may be a drop-down, and the install answers must match the ones each text field lists * test: use placeholder names in the Help & feedback tests
1 parent b407f81 commit e125d3e

28 files changed

Lines changed: 3587 additions & 24 deletions

‎.github/ISSUE_TEMPLATE/bug_report.yml‎

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,38 +33,33 @@ body:
3333
validations:
3434
required: true
3535

36-
- type: dropdown
36+
# Text boxes rather than drop-downs: GitHub only pre-fills text fields from
37+
# a link, and the panel's Help & feedback page fills these in for you.
38+
39+
- type: input
3740
id: ha_install_type
3841
attributes:
3942
label: Home Assistant Installation Type
40-
options:
41-
- Home Assistant OS (HAOS)
42-
- Home Assistant Supervised
43-
- Home Assistant Container (Docker)
44-
- Home Assistant Core
45-
- Not sure
43+
description: "One of: Home Assistant OS (HAOS), Home Assistant Supervised, Home Assistant Container (Docker), Home Assistant Core, Not sure. Found in Settings → System → About"
44+
placeholder: e.g. Home Assistant OS (HAOS)
4645
validations:
4746
required: true
4847

49-
- type: dropdown
48+
- type: input
5049
id: install_method
5150
attributes:
5251
label: TaskMate Installation Method
53-
options:
54-
- HACS (custom repository)
55-
- Manual (copied files)
52+
description: "One of: HACS (default), HACS (custom repository), Manual (copied files)"
53+
placeholder: e.g. HACS (default)
5654
validations:
5755
required: true
5856

59-
- type: dropdown
57+
- type: input
6058
id: lovelace_mode
6159
attributes:
6260
label: Lovelace Mode
63-
description: Found in Settings → Dashboards → ⋮ menu → Edit → Manage Resources
64-
options:
65-
- Storage (default — managed via UI)
66-
- YAML (configuration.yaml)
67-
- Not sure
61+
description: "One of: Storage (default — managed via UI), YAML (configuration.yaml), Not sure. Found in Settings → Dashboards → ⋮ menu → Edit → Manage Resources"
62+
placeholder: e.g. Storage (default — managed via UI)
6863
validations:
6964
required: true
7065

@@ -111,7 +106,9 @@ body:
111106
- Sometimes — happens intermittently
112107
- Once — only happened one time
113108
validations:
114-
required: true
109+
# Optional so a report from the panel (which can't pre-select a
110+
# drop-down) isn't asked twice; the panel puts its answer in the text.
111+
required: false
115112

116113
# ── Affected Area ─────────────────────────────────────────────────────────────
117114

@@ -149,7 +146,7 @@ body:
149146
- Bonus points (weekend / streak / perfect week)
150147
- Other
151148
validations:
152-
required: true
149+
required: false
153150

154151
# ── Sensor & Entity State ─────────────────────────────────────────────────────
155152

‎.github/ISSUE_TEMPLATE/feature_request.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ body:
9090
- New card (not listed above)
9191
- Other / not sure
9292
validations:
93-
required: true
93+
required: false
9494

9595
# ── Alternatives ─────────────────────────────────────────────────────────────
9696

@@ -114,7 +114,7 @@ body:
114114
- Important — a gap that affects my daily use
115115
- Critical — a blocker for me using TaskMate properly
116116
validations:
117-
required: true
117+
required: false
118118

119119
# ── Use Case ──────────────────────────────────────────────────────────────────
120120

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
"""What the admin panel's Help & feedback page fills into a GitHub issue.
2+
3+
The page builds a pre-filled ``issues/new`` link; nothing is sent from Home
4+
Assistant. This gathers the environment half of a bug report — the bit people
5+
get wrong or leave out by hand: versions, how HA and TaskMate are installed,
6+
the Lovelace resource mode, rough sizes and the health check's issue codes.
7+
8+
It never says who the family are: counts only, no names, and the health
9+
check contributes its issue *codes*, not its messages (those name chores).
10+
"""
11+
12+
from __future__ import annotations
13+
14+
import logging
15+
from typing import Any
16+
17+
import homeassistant.const as ha_const
18+
from homeassistant.core import HomeAssistant
19+
from homeassistant.loader import async_get_integration
20+
21+
from .const import DOMAIN
22+
23+
_LOGGER = logging.getLogger(__name__)
24+
25+
REPO_FULL_NAME = "tempus2016/taskmate"
26+
27+
# HA's system_info installation_type → the bug template's dropdown options.
28+
_INSTALL_TYPES = {
29+
"Home Assistant OS": "haos",
30+
"Home Assistant Supervised": "supervised",
31+
"Home Assistant Container": "container",
32+
"Unsupported Third Party Container": "container",
33+
"Home Assistant Core": "core",
34+
}
35+
36+
37+
async def _installation_type(hass: HomeAssistant) -> tuple[str, str]:
38+
"""(template key, HA's own wording, e.g. "Home Assistant OS")."""
39+
try:
40+
from homeassistant.helpers.system_info import async_get_system_info
41+
42+
info = await async_get_system_info(hass)
43+
except Exception: # noqa: BLE001 — a report without it is still a report
44+
_LOGGER.debug("TaskMate feedback: system info unavailable", exc_info=True)
45+
return "unknown", ""
46+
raw = str(info.get("installation_type") or "")
47+
return _INSTALL_TYPES.get(raw, "unknown"), raw
48+
49+
50+
def _install_method(hass: HomeAssistant) -> str:
51+
"""How TaskMate itself was installed: hacs_default, hacs_custom, manual or unknown.
52+
53+
Reads HACS's in-memory repository list. That is HACS's internal API, so
54+
any surprise in its shape degrades to "unknown" rather than failing.
55+
"""
56+
if "hacs" not in hass.config.components:
57+
return "manual"
58+
try:
59+
hacs = hass.data.get("hacs")
60+
repo = hacs.repositories.get_by_full_name(REPO_FULL_NAME)
61+
if repo is None:
62+
return "manual"
63+
if not getattr(repo.data, "installed", True):
64+
return "manual"
65+
return "hacs_default" if hacs.repositories.is_default(str(repo.data.id)) else "hacs_custom"
66+
except Exception: # noqa: BLE001
67+
_LOGGER.debug("TaskMate feedback: could not read HACS repositories", exc_info=True)
68+
return "unknown"
69+
70+
71+
def _lovelace_mode(hass: HomeAssistant) -> str:
72+
"""storage, yaml or unknown — where Lovelace resources are managed.
73+
74+
HA keeps this as a dataclass since 2024.12 (``mode``, later renamed
75+
``resource_mode``) and as a plain dict before it.
76+
"""
77+
data = hass.data.get("lovelace")
78+
if data is None:
79+
return "unknown"
80+
if isinstance(data, dict):
81+
mode = data.get("resource_mode") or data.get("mode")
82+
else:
83+
mode = getattr(data, "resource_mode", None) or getattr(data, "mode", None)
84+
mode = str(mode or "").lower()
85+
return mode if mode in ("storage", "yaml") else "unknown"
86+
87+
88+
def _entry_id(hass: HomeAssistant, coordinator) -> str:
89+
entry = getattr(coordinator, "config_entry", None)
90+
if entry is not None and getattr(entry, "entry_id", None):
91+
return str(entry.entry_id)
92+
for key, value in hass.data.get(DOMAIN, {}).items():
93+
if value is coordinator:
94+
return str(key)
95+
return ""
96+
97+
98+
async def async_feedback_info(hass: HomeAssistant, coordinator) -> dict[str, Any]:
99+
"""The environment summary for a bug report or feature request."""
100+
integration = await async_get_integration(hass, DOMAIN)
101+
install_type, install_type_raw = await _installation_type(hass)
102+
storage = coordinator.storage
103+
children = storage.get_children()
104+
try:
105+
health = coordinator.health_report()
106+
health_codes = sorted({str(i.get("code", "")) for i in health.get("issues", []) if i.get("code")})
107+
except Exception: # noqa: BLE001
108+
_LOGGER.debug("TaskMate feedback: health report failed", exc_info=True)
109+
health_codes = []
110+
settings = storage.get_settings() or {}
111+
return {
112+
"integration_version": str(integration.version or ""),
113+
"ha_version": str(getattr(ha_const, "__version__", "")),
114+
"installation_type": install_type,
115+
"installation_type_raw": install_type_raw,
116+
"install_method": _install_method(hass),
117+
"lovelace_mode": _lovelace_mode(hass),
118+
"language": str(getattr(hass.config, "language", "") or ""),
119+
"counts": {
120+
"children": sum(1 for c in children if not getattr(c, "is_adult", False)),
121+
"adults": sum(1 for c in children if getattr(c, "is_adult", False)),
122+
"chores": len(storage.get_chores()),
123+
"rewards": len(storage.get_rewards()),
124+
},
125+
"card_design": str(settings.get("card_design") or "classic"),
126+
"health_codes": health_codes,
127+
"entry_id": _entry_id(hass, coordinator),
128+
}

‎custom_components/taskmate/websocket.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@
127127
WS_REPORT_HEALTH: Final = "taskmate/reports/health"
128128
WS_REPORT_QUALITY: Final = "taskmate/reports/quality"
129129
WS_REPORT_MOODS: Final = "taskmate/reports/moods"
130+
# Help & feedback page: the environment half of a GitHub issue.
131+
WS_FEEDBACK_INFO: Final = "taskmate/feedback/info"
130132
WS_SCHEDULED_LIST: Final = "taskmate/scheduled/list"
131133
WS_SCHEDULED_ADD: Final = "taskmate/scheduled/add"
132134
WS_SCHEDULED_REMOVE: Final = "taskmate/scheduled/remove"
@@ -269,6 +271,7 @@
269271
WS_REPORT_HEALTH,
270272
WS_REPORT_QUALITY,
271273
WS_REPORT_MOODS,
274+
WS_FEEDBACK_INFO,
272275
WS_SETUP_CATALOGUE,
273276
}
274277

@@ -1150,6 +1153,17 @@ async def _ws_report_health(hass, connection, msg, coordinator):
11501153
connection.send_result(msg["id"], coordinator.health_report())
11511154

11521155

1156+
@websocket_api.websocket_command({vol.Required("type"): WS_FEEDBACK_INFO})
1157+
@websocket_api.async_response
1158+
@_admin_only
1159+
async def _ws_feedback_info(hass, connection, msg, coordinator):
1160+
# Imported here: feedback.py needs homeassistant.loader, which nothing
1161+
# else on this module's import path does.
1162+
from .feedback import async_feedback_info
1163+
1164+
connection.send_result(msg["id"], await async_feedback_info(hass, coordinator))
1165+
1166+
11531167
# ---------------------------------------------------------------------------
11541168
# Scheduled config changes (#675)
11551169
# ---------------------------------------------------------------------------
@@ -3290,6 +3304,7 @@ async def _ws_config_import(hass, connection, msg, coordinator):
32903304
_ws_report_friction,
32913305
_ws_report_projection,
32923306
_ws_report_health,
3307+
_ws_feedback_info,
32933308
_ws_report_quality,
32943309
_ws_report_moods,
32953310
_ws_templates_export,

0 commit comments

Comments
 (0)