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
pull/90928/head^2
Erik Montnemery 2023-10-04 13:40:33 +02:00 committed by GitHub
parent 3aa6771835
commit 17779c5f0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 191 additions and 37 deletions

View File

@ -4,7 +4,6 @@ from __future__ import annotations
from abc import ABC
import asyncio
from collections.abc import Coroutine, Iterable, Mapping, MutableMapping
from contextlib import suppress
from dataclasses import dataclass
from datetime import timedelta
from enum import Enum, auto
@ -50,11 +49,7 @@ from homeassistant.exceptions import (
InvalidStateError,
NoEntitySpecifiedError,
)
from homeassistant.loader import (
IntegrationNotLoaded,
async_get_loaded_integration,
bind_hass,
)
from homeassistant.loader import async_suggest_report_issue, bind_hass
from homeassistant.util import ensure_unique_string, slugify
from . import device_registry as dr, entity_registry as er
@ -1257,35 +1252,12 @@ class Entity(ABC):
def _suggest_report_issue(self) -> str:
"""Suggest to report an issue."""
report_issue = ""
integration = None
# The check for self.platform guards against integrations not using an
# EntityComponent and can be removed in HA Core 2024.1
if self.platform:
with suppress(IntegrationNotLoaded):
integration = async_get_loaded_integration(
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
platform_name = self.platform.platform_name if self.platform else None
return async_suggest_report_issue(
self.hass, integration_domain=platform_name, module=type(self).__module__
)
@dataclass(slots=True)

View File

@ -1187,3 +1187,55 @@ def _lookup_path(hass: HomeAssistant) -> list[str]:
def is_component_module_loaded(hass: HomeAssistant, module: str) -> bool:
"""Test if a component module is loaded."""
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}"

View File

@ -166,7 +166,7 @@ async def test_deprecated_last_reset(
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 "
"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
state = hass.states.get("sensor.test")

View File

@ -776,9 +776,10 @@ async def test_warn_slow_write_state_custom_component(
mock_entity.async_write_ha_state()
assert (
"Updating state for comp_test.test_entity "
"(<class 'custom_components.bla.sensor.test_warn_slow_write_state_custom_component.<locals>.CustomComponentEntity'>) "
"took 10.000 seconds. Please report it to the custom integration author"
"Updating state for comp_test.test_entity (<class 'custom_components.bla.sensor"
".test_warn_slow_write_state_custom_component.<locals>.CustomComponentEntity'>)"
" took 10.000 seconds. Please report it to the author of the 'hue' custom "
"integration"
) in caplog.text

View File

@ -744,3 +744,132 @@ async def test_loggers(hass: HomeAssistant) -> None:
},
)
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
)