mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Add loader.async_suggest_report_issue and loader.async_get_issue_tracker (#101336)
* Add loader.async_suggest_report_issue and loader.async_get_issue_tracker * Update tests * Add tests * Address review comments * Address review comments
This commit is contained in:
parent
3aa6771835
commit
17779c5f0c
@ -4,7 +4,6 @@ from __future__ import annotations
|
|||||||
from abc import ABC
|
from abc import ABC
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Coroutine, Iterable, Mapping, MutableMapping
|
from collections.abc import Coroutine, Iterable, Mapping, MutableMapping
|
||||||
from contextlib import suppress
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
@ -50,11 +49,7 @@ from homeassistant.exceptions import (
|
|||||||
InvalidStateError,
|
InvalidStateError,
|
||||||
NoEntitySpecifiedError,
|
NoEntitySpecifiedError,
|
||||||
)
|
)
|
||||||
from homeassistant.loader import (
|
from homeassistant.loader import async_suggest_report_issue, bind_hass
|
||||||
IntegrationNotLoaded,
|
|
||||||
async_get_loaded_integration,
|
|
||||||
bind_hass,
|
|
||||||
)
|
|
||||||
from homeassistant.util import ensure_unique_string, slugify
|
from homeassistant.util import ensure_unique_string, slugify
|
||||||
|
|
||||||
from . import device_registry as dr, entity_registry as er
|
from . import device_registry as dr, entity_registry as er
|
||||||
@ -1257,35 +1252,12 @@ class Entity(ABC):
|
|||||||
|
|
||||||
def _suggest_report_issue(self) -> str:
|
def _suggest_report_issue(self) -> str:
|
||||||
"""Suggest to report an issue."""
|
"""Suggest to report an issue."""
|
||||||
report_issue = ""
|
|
||||||
|
|
||||||
integration = None
|
|
||||||
# The check for self.platform guards against integrations not using an
|
# The check for self.platform guards against integrations not using an
|
||||||
# EntityComponent and can be removed in HA Core 2024.1
|
# EntityComponent and can be removed in HA Core 2024.1
|
||||||
if self.platform:
|
platform_name = self.platform.platform_name if self.platform else None
|
||||||
with suppress(IntegrationNotLoaded):
|
return async_suggest_report_issue(
|
||||||
integration = async_get_loaded_integration(
|
self.hass, integration_domain=platform_name, module=type(self).__module__
|
||||||
self.hass, self.platform.platform_name
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if "custom_components" in type(self).__module__:
|
|
||||||
if integration and integration.issue_tracker:
|
|
||||||
report_issue = f"create a bug report at {integration.issue_tracker}"
|
|
||||||
else:
|
|
||||||
report_issue = "report it to the custom integration author"
|
|
||||||
else:
|
|
||||||
report_issue = (
|
|
||||||
"create a bug report at "
|
|
||||||
"https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue"
|
|
||||||
)
|
|
||||||
# The check for self.platform guards against integrations not using an
|
|
||||||
# EntityComponent and can be removed in HA Core 2024.1
|
|
||||||
if self.platform:
|
|
||||||
report_issue += (
|
|
||||||
f"+label%3A%22integration%3A+{self.platform.platform_name}%22"
|
|
||||||
)
|
|
||||||
|
|
||||||
return report_issue
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
|
@ -1187,3 +1187,55 @@ def _lookup_path(hass: HomeAssistant) -> list[str]:
|
|||||||
def is_component_module_loaded(hass: HomeAssistant, module: str) -> bool:
|
def is_component_module_loaded(hass: HomeAssistant, module: str) -> bool:
|
||||||
"""Test if a component module is loaded."""
|
"""Test if a component module is loaded."""
|
||||||
return module in hass.data[DATA_COMPONENTS]
|
return module in hass.data[DATA_COMPONENTS]
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_get_issue_tracker(
|
||||||
|
hass: HomeAssistant | None,
|
||||||
|
*,
|
||||||
|
integration_domain: str | None = None,
|
||||||
|
module: str | None = None,
|
||||||
|
) -> str | None:
|
||||||
|
"""Return a URL for an integration's issue tracker."""
|
||||||
|
issue_tracker = (
|
||||||
|
"https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue"
|
||||||
|
)
|
||||||
|
if not integration_domain and not module:
|
||||||
|
# If we know nothing about the entity, suggest opening an issue on HA core
|
||||||
|
return issue_tracker
|
||||||
|
|
||||||
|
if hass and integration_domain:
|
||||||
|
with suppress(IntegrationNotLoaded):
|
||||||
|
integration = async_get_loaded_integration(hass, integration_domain)
|
||||||
|
if not integration.is_built_in:
|
||||||
|
return integration.issue_tracker
|
||||||
|
|
||||||
|
if module and "custom_components" in module:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if integration_domain:
|
||||||
|
issue_tracker += f"+label%3A%22integration%3A+{integration_domain}%22"
|
||||||
|
return issue_tracker
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_suggest_report_issue(
|
||||||
|
hass: HomeAssistant | None,
|
||||||
|
*,
|
||||||
|
integration_domain: str | None = None,
|
||||||
|
module: str | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""Generate a blurb asking the user to file a bug report."""
|
||||||
|
issue_tracker = async_get_issue_tracker(
|
||||||
|
hass, integration_domain=integration_domain, module=module
|
||||||
|
)
|
||||||
|
|
||||||
|
if not issue_tracker:
|
||||||
|
if not integration_domain:
|
||||||
|
return "report it to the custom integration author"
|
||||||
|
return (
|
||||||
|
f"report it to the author of the '{integration_domain}' "
|
||||||
|
"custom integration"
|
||||||
|
)
|
||||||
|
|
||||||
|
return f"create a bug report at {issue_tracker}"
|
||||||
|
@ -166,7 +166,7 @@ async def test_deprecated_last_reset(
|
|||||||
f"with state_class {state_class} has set last_reset. Setting last_reset for "
|
f"with state_class {state_class} has set last_reset. Setting last_reset for "
|
||||||
"entities with state_class other than 'total' is not supported. Please update "
|
"entities with state_class other than 'total' is not supported. Please update "
|
||||||
"your configuration if state_class is manually configured, otherwise report it "
|
"your configuration if state_class is manually configured, otherwise report it "
|
||||||
"to the custom integration author"
|
"to the author of the 'test' custom integration"
|
||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
|
||||||
state = hass.states.get("sensor.test")
|
state = hass.states.get("sensor.test")
|
||||||
|
@ -776,9 +776,10 @@ async def test_warn_slow_write_state_custom_component(
|
|||||||
mock_entity.async_write_ha_state()
|
mock_entity.async_write_ha_state()
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
"Updating state for comp_test.test_entity "
|
"Updating state for comp_test.test_entity (<class 'custom_components.bla.sensor"
|
||||||
"(<class 'custom_components.bla.sensor.test_warn_slow_write_state_custom_component.<locals>.CustomComponentEntity'>) "
|
".test_warn_slow_write_state_custom_component.<locals>.CustomComponentEntity'>)"
|
||||||
"took 10.000 seconds. Please report it to the custom integration author"
|
" took 10.000 seconds. Please report it to the author of the 'hue' custom "
|
||||||
|
"integration"
|
||||||
) in caplog.text
|
) in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@ -744,3 +744,132 @@ async def test_loggers(hass: HomeAssistant) -> None:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert integration.loggers == ["name1", "name2"]
|
assert integration.loggers == ["name1", "name2"]
|
||||||
|
|
||||||
|
|
||||||
|
CORE_ISSUE_TRACKER = (
|
||||||
|
"https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue"
|
||||||
|
)
|
||||||
|
CORE_ISSUE_TRACKER_BUILT_IN = (
|
||||||
|
CORE_ISSUE_TRACKER + "+label%3A%22integration%3A+bla_built_in%22"
|
||||||
|
)
|
||||||
|
CORE_ISSUE_TRACKER_CUSTOM = (
|
||||||
|
CORE_ISSUE_TRACKER + "+label%3A%22integration%3A+bla_custom%22"
|
||||||
|
)
|
||||||
|
CORE_ISSUE_TRACKER_CUSTOM_NO_TRACKER = (
|
||||||
|
CORE_ISSUE_TRACKER + "+label%3A%22integration%3A+bla_custom_no_tracker%22"
|
||||||
|
)
|
||||||
|
CORE_ISSUE_TRACKER_HUE = CORE_ISSUE_TRACKER + "+label%3A%22integration%3A+hue%22"
|
||||||
|
CUSTOM_ISSUE_TRACKER = "https://blablabla.com"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("domain", "module", "issue_tracker"),
|
||||||
|
[
|
||||||
|
# If no information is available, open issue on core
|
||||||
|
(None, None, CORE_ISSUE_TRACKER),
|
||||||
|
("hue", "homeassistant.components.hue.sensor", CORE_ISSUE_TRACKER_HUE),
|
||||||
|
("hue", None, CORE_ISSUE_TRACKER_HUE),
|
||||||
|
("bla_built_in", None, CORE_ISSUE_TRACKER_BUILT_IN),
|
||||||
|
# Integration domain is not currently deduced from module
|
||||||
|
(None, "homeassistant.components.hue.sensor", CORE_ISSUE_TRACKER),
|
||||||
|
("hue", "homeassistant.components.mqtt.sensor", CORE_ISSUE_TRACKER_HUE),
|
||||||
|
# Custom integration with known issue tracker
|
||||||
|
("bla_custom", "custom_components.bla_custom.sensor", CUSTOM_ISSUE_TRACKER),
|
||||||
|
("bla_custom", None, CUSTOM_ISSUE_TRACKER),
|
||||||
|
# Custom integration without known issue tracker
|
||||||
|
(None, "custom_components.bla.sensor", None),
|
||||||
|
("bla_custom_no_tracker", "custom_components.bla_custom.sensor", None),
|
||||||
|
("bla_custom_no_tracker", None, None),
|
||||||
|
("hue", "custom_components.bla.sensor", None),
|
||||||
|
# Integration domain has priority over module
|
||||||
|
("bla_custom_no_tracker", "homeassistant.components.bla_custom.sensor", None),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_async_get_issue_tracker(
|
||||||
|
hass, domain: str | None, module: str | None, issue_tracker: str | None
|
||||||
|
) -> None:
|
||||||
|
"""Test async_get_issue_tracker."""
|
||||||
|
mock_integration(hass, MockModule("bla_built_in"))
|
||||||
|
mock_integration(
|
||||||
|
hass,
|
||||||
|
MockModule(
|
||||||
|
"bla_custom", partial_manifest={"issue_tracker": CUSTOM_ISSUE_TRACKER}
|
||||||
|
),
|
||||||
|
built_in=False,
|
||||||
|
)
|
||||||
|
mock_integration(hass, MockModule("bla_custom_no_tracker"), built_in=False)
|
||||||
|
assert (
|
||||||
|
loader.async_get_issue_tracker(hass, integration_domain=domain, module=module)
|
||||||
|
== issue_tracker
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("domain", "module", "issue_tracker"),
|
||||||
|
[
|
||||||
|
# If no information is available, open issue on core
|
||||||
|
(None, None, CORE_ISSUE_TRACKER),
|
||||||
|
("hue", "homeassistant.components.hue.sensor", CORE_ISSUE_TRACKER_HUE),
|
||||||
|
("hue", None, CORE_ISSUE_TRACKER_HUE),
|
||||||
|
("bla_built_in", None, CORE_ISSUE_TRACKER_BUILT_IN),
|
||||||
|
# Integration domain is not currently deduced from module
|
||||||
|
(None, "homeassistant.components.hue.sensor", CORE_ISSUE_TRACKER),
|
||||||
|
("hue", "homeassistant.components.mqtt.sensor", CORE_ISSUE_TRACKER_HUE),
|
||||||
|
# Custom integration with known issue tracker - can't find it without hass
|
||||||
|
("bla_custom", "custom_components.bla_custom.sensor", None),
|
||||||
|
# Assumed to be a core integration without hass and without module
|
||||||
|
("bla_custom", None, CORE_ISSUE_TRACKER_CUSTOM),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_async_get_issue_tracker_no_hass(
|
||||||
|
hass, domain: str | None, module: str | None, issue_tracker: str
|
||||||
|
) -> None:
|
||||||
|
"""Test async_get_issue_tracker."""
|
||||||
|
mock_integration(hass, MockModule("bla_built_in"))
|
||||||
|
mock_integration(
|
||||||
|
hass,
|
||||||
|
MockModule(
|
||||||
|
"bla_custom", partial_manifest={"issue_tracker": CUSTOM_ISSUE_TRACKER}
|
||||||
|
),
|
||||||
|
built_in=False,
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
loader.async_get_issue_tracker(None, integration_domain=domain, module=module)
|
||||||
|
== issue_tracker
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
REPORT_CUSTOM = (
|
||||||
|
"report it to the author of the 'bla_custom_no_tracker' custom integration"
|
||||||
|
)
|
||||||
|
REPORT_CUSTOM_UNKNOWN = "report it to the custom integration author"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("domain", "module", "report_issue"),
|
||||||
|
[
|
||||||
|
(None, None, f"create a bug report at {CORE_ISSUE_TRACKER}"),
|
||||||
|
("bla_custom", None, f"create a bug report at {CUSTOM_ISSUE_TRACKER}"),
|
||||||
|
("bla_custom_no_tracker", None, REPORT_CUSTOM),
|
||||||
|
(None, "custom_components.hue.sensor", REPORT_CUSTOM_UNKNOWN),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_async_suggest_report_issue(
|
||||||
|
hass, domain: str | None, module: str | None, report_issue: str
|
||||||
|
) -> None:
|
||||||
|
"""Test async_suggest_report_issue."""
|
||||||
|
mock_integration(hass, MockModule("bla_built_in"))
|
||||||
|
mock_integration(
|
||||||
|
hass,
|
||||||
|
MockModule(
|
||||||
|
"bla_custom", partial_manifest={"issue_tracker": CUSTOM_ISSUE_TRACKER}
|
||||||
|
),
|
||||||
|
built_in=False,
|
||||||
|
)
|
||||||
|
mock_integration(hass, MockModule("bla_custom_no_tracker"), built_in=False)
|
||||||
|
assert (
|
||||||
|
loader.async_suggest_report_issue(
|
||||||
|
hass, integration_domain=domain, module=module
|
||||||
|
)
|
||||||
|
== report_issue
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user