Add addon support to Home Assistant Analytics Insights (#128806)

pull/129453/head
Michael 2024-10-29 20:13:56 +01:00 committed by GitHub
parent c9aba288b4
commit a95c232f11
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 187 additions and 5 deletions

View File

@ -27,6 +27,7 @@ from homeassistant.helpers.selector import (
)
from .const import (
CONF_TRACKED_ADDONS,
CONF_TRACKED_CUSTOM_INTEGRATIONS,
CONF_TRACKED_INTEGRATIONS,
DOMAIN,
@ -55,8 +56,12 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
if not user_input.get(CONF_TRACKED_INTEGRATIONS) and not user_input.get(
CONF_TRACKED_CUSTOM_INTEGRATIONS
if all(
[
not user_input.get(CONF_TRACKED_ADDONS),
not user_input.get(CONF_TRACKED_INTEGRATIONS),
not user_input.get(CONF_TRACKED_CUSTOM_INTEGRATIONS),
]
):
errors["base"] = "no_integrations_selected"
else:
@ -64,6 +69,7 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
title="Home Assistant Analytics Insights",
data={},
options={
CONF_TRACKED_ADDONS: user_input.get(CONF_TRACKED_ADDONS, []),
CONF_TRACKED_INTEGRATIONS: user_input.get(
CONF_TRACKED_INTEGRATIONS, []
),
@ -77,6 +83,7 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
session=async_get_clientsession(self.hass)
)
try:
addons = await client.get_addons()
integrations = await client.get_integrations()
custom_integrations = await client.get_custom_integrations()
except HomeassistantAnalyticsConnectionError:
@ -99,6 +106,13 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
errors=errors,
data_schema=vol.Schema(
{
vol.Optional(CONF_TRACKED_ADDONS): SelectSelector(
SelectSelectorConfig(
options=list(addons),
multiple=True,
sort=True,
)
),
vol.Optional(CONF_TRACKED_INTEGRATIONS): SelectSelector(
SelectSelectorConfig(
options=options,
@ -127,14 +141,19 @@ class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlowWithConfigEntry):
"""Manage the options."""
errors: dict[str, str] = {}
if user_input is not None:
if not user_input.get(CONF_TRACKED_INTEGRATIONS) and not user_input.get(
CONF_TRACKED_CUSTOM_INTEGRATIONS
if all(
[
not user_input.get(CONF_TRACKED_ADDONS),
not user_input.get(CONF_TRACKED_INTEGRATIONS),
not user_input.get(CONF_TRACKED_CUSTOM_INTEGRATIONS),
]
):
errors["base"] = "no_integrations_selected"
else:
return self.async_create_entry(
title="",
data={
CONF_TRACKED_ADDONS: user_input.get(CONF_TRACKED_ADDONS, []),
CONF_TRACKED_INTEGRATIONS: user_input.get(
CONF_TRACKED_INTEGRATIONS, []
),
@ -148,6 +167,7 @@ class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlowWithConfigEntry):
session=async_get_clientsession(self.hass)
)
try:
addons = await client.get_addons()
integrations = await client.get_integrations()
custom_integrations = await client.get_custom_integrations()
except HomeassistantAnalyticsConnectionError:
@ -168,6 +188,13 @@ class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlowWithConfigEntry):
data_schema=self.add_suggested_values_to_schema(
vol.Schema(
{
vol.Optional(CONF_TRACKED_ADDONS): SelectSelector(
SelectSelectorConfig(
options=list(addons),
multiple=True,
sort=True,
)
),
vol.Optional(CONF_TRACKED_INTEGRATIONS): SelectSelector(
SelectSelectorConfig(
options=options,

View File

@ -4,6 +4,7 @@ import logging
DOMAIN = "analytics_insights"
CONF_TRACKED_ADDONS = "tracked_addons"
CONF_TRACKED_INTEGRATIONS = "tracked_integrations"
CONF_TRACKED_CUSTOM_INTEGRATIONS = "tracked_custom_integrations"

View File

@ -12,11 +12,13 @@ from python_homeassistant_analytics import (
HomeassistantAnalyticsConnectionError,
HomeassistantAnalyticsNotModifiedError,
)
from python_homeassistant_analytics.models import Addon
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import (
CONF_TRACKED_ADDONS,
CONF_TRACKED_CUSTOM_INTEGRATIONS,
CONF_TRACKED_INTEGRATIONS,
DOMAIN,
@ -33,6 +35,7 @@ class AnalyticsData:
active_installations: int
reports_integrations: int
addons: dict[str, int]
core_integrations: dict[str, int]
custom_integrations: dict[str, int]
@ -53,6 +56,7 @@ class HomeassistantAnalyticsDataUpdateCoordinator(DataUpdateCoordinator[Analytic
update_interval=timedelta(hours=12),
)
self._client = client
self._tracked_addons = self.config_entry.options.get(CONF_TRACKED_ADDONS, [])
self._tracked_integrations = self.config_entry.options[
CONF_TRACKED_INTEGRATIONS
]
@ -62,6 +66,7 @@ class HomeassistantAnalyticsDataUpdateCoordinator(DataUpdateCoordinator[Analytic
async def _async_update_data(self) -> AnalyticsData:
try:
addons_data = await self._client.get_addons()
data = await self._client.get_current_analytics()
custom_data = await self._client.get_custom_integrations()
except HomeassistantAnalyticsConnectionError as err:
@ -70,6 +75,9 @@ class HomeassistantAnalyticsDataUpdateCoordinator(DataUpdateCoordinator[Analytic
) from err
except HomeassistantAnalyticsNotModifiedError:
return self.data
addons = {
addon: get_addon_value(addons_data, addon) for addon in self._tracked_addons
}
core_integrations = {
integration: data.integrations.get(integration, 0)
for integration in self._tracked_integrations
@ -81,11 +89,19 @@ class HomeassistantAnalyticsDataUpdateCoordinator(DataUpdateCoordinator[Analytic
return AnalyticsData(
data.active_installations,
data.reports_integrations,
addons,
core_integrations,
custom_integrations,
)
def get_addon_value(data: dict[str, Addon], name_slug: str) -> int:
"""Get addon value."""
if name_slug in data:
return data[name_slug].total
return 0
def get_custom_integration_value(
data: dict[str, CustomIntegration], domain: str
) -> int:

View File

@ -29,6 +29,20 @@ class AnalyticsSensorEntityDescription(SensorEntityDescription):
value_fn: Callable[[AnalyticsData], StateType]
def get_addon_entity_description(
name_slug: str,
) -> AnalyticsSensorEntityDescription:
"""Get addon entity description."""
return AnalyticsSensorEntityDescription(
key=f"addon_{name_slug}_active_installations",
translation_key="addons",
name=name_slug,
state_class=SensorStateClass.TOTAL,
native_unit_of_measurement="active installations",
value_fn=lambda data: data.addons.get(name_slug),
)
def get_core_integration_entity_description(
domain: str, name: str
) -> AnalyticsSensorEntityDescription:
@ -89,6 +103,13 @@ async def async_setup_entry(
analytics_data.coordinator
)
entities: list[HomeassistantAnalyticsSensor] = []
entities.extend(
HomeassistantAnalyticsSensor(
coordinator,
get_addon_entity_description(addon_name_slug),
)
for addon_name_slug in coordinator.data.addons
)
entities.extend(
HomeassistantAnalyticsSensor(
coordinator,

View File

@ -3,10 +3,12 @@
"step": {
"user": {
"data": {
"tracked_addons": "Addons",
"tracked_integrations": "Integrations",
"tracked_custom_integrations": "Custom integrations"
},
"data_description": {
"tracked_addons": "Select the addons you want to track",
"tracked_integrations": "Select the integrations you want to track",
"tracked_custom_integrations": "Select the custom integrations you want to track"
}
@ -24,10 +26,12 @@
"step": {
"init": {
"data": {
"tracked_addons": "[%key:component::analytics_insights::config::step::user::data::tracked_addons%]",
"tracked_integrations": "[%key:component::analytics_insights::config::step::user::data::tracked_integrations%]",
"tracked_custom_integrations": "[%key:component::analytics_insights::config::step::user::data::tracked_custom_integrations%]"
},
"data_description": {
"tracked_addons": "[%key:component::analytics_insights::config::step::user::data_description::tracked_addons%]",
"tracked_integrations": "[%key:component::analytics_insights::config::step::user::data_description::tracked_integrations%]",
"tracked_custom_integrations": "[%key:component::analytics_insights::config::step::user::data_description::tracked_custom_integrations%]"
}

View File

@ -5,9 +5,10 @@ from unittest.mock import AsyncMock, patch
import pytest
from python_homeassistant_analytics import CurrentAnalytics
from python_homeassistant_analytics.models import CustomIntegration, Integration
from python_homeassistant_analytics.models import Addon, CustomIntegration, Integration
from homeassistant.components.analytics_insights.const import (
CONF_TRACKED_ADDONS,
CONF_TRACKED_CUSTOM_INTEGRATIONS,
CONF_TRACKED_INTEGRATIONS,
DOMAIN,
@ -43,6 +44,10 @@ def mock_analytics_client() -> Generator[AsyncMock]:
client.get_current_analytics.return_value = CurrentAnalytics.from_json(
load_fixture("analytics_insights/current_data.json")
)
addons = load_json_object_fixture("analytics_insights/addons.json")
client.get_addons.return_value = {
key: Addon.from_dict(value) for key, value in addons.items()
}
integrations = load_json_object_fixture("analytics_insights/integrations.json")
client.get_integrations.return_value = {
key: Integration.from_dict(value) for key, value in integrations.items()
@ -65,6 +70,7 @@ def mock_config_entry() -> MockConfigEntry:
title="Homeassistant Analytics",
data={},
options={
CONF_TRACKED_ADDONS: ["core_samba"],
CONF_TRACKED_INTEGRATIONS: ["youtube", "spotify", "myq"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},

View File

@ -0,0 +1,31 @@
{
"core_samba": {
"total": 76357,
"versions": {
"12.3.2": 65875,
"12.2.0": 1313,
"12.3.1": 5018,
"12.1.0": 211,
"10.0.0": 1139,
"9.4.0": 4,
"12.3.0": 704,
"9.3.1": 36,
"10.0.2": 1290,
"9.5.1": 379,
"9.6.1": 66,
"10.0.1": 200,
"9.3.0": 20,
"9.2.0": 9,
"9.5.0": 13,
"12.0.0": 39,
"9.7.0": 20,
"11.0.0": 13,
"3.0": 1,
"9.6.0": 2,
"8.1": 2,
"9.0": 3
},
"protected": 76345,
"auto_update": 32732
}
}

View File

@ -1,4 +1,54 @@
# serializer version: 1
# name: test_all_entities[sensor.homeassistant_analytics_core_samba-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL: 'total'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.homeassistant_analytics_core_samba',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'core_samba',
'platform': 'analytics_insights',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'addons',
'unique_id': 'addon_core_samba_active_installations',
'unit_of_measurement': 'active installations',
})
# ---
# name: test_all_entities[sensor.homeassistant_analytics_core_samba-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Homeassistant Analytics core_samba',
'state_class': <SensorStateClass.TOTAL: 'total'>,
'unit_of_measurement': 'active installations',
}),
'context': <ANY>,
'entity_id': 'sensor.homeassistant_analytics_core_samba',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '76357',
})
# ---
# name: test_all_entities[sensor.homeassistant_analytics_hacs_custom-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@ -7,6 +7,7 @@ import pytest
from python_homeassistant_analytics import HomeassistantAnalyticsConnectionError
from homeassistant.components.analytics_insights.const import (
CONF_TRACKED_ADDONS,
CONF_TRACKED_CUSTOM_INTEGRATIONS,
CONF_TRACKED_INTEGRATIONS,
DOMAIN,
@ -25,10 +26,12 @@ from tests.common import MockConfigEntry
[
(
{
CONF_TRACKED_ADDONS: ["core_samba"],
CONF_TRACKED_INTEGRATIONS: ["youtube"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
{
CONF_TRACKED_ADDONS: ["core_samba"],
CONF_TRACKED_INTEGRATIONS: ["youtube"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
@ -38,6 +41,7 @@ from tests.common import MockConfigEntry
CONF_TRACKED_INTEGRATIONS: ["youtube"],
},
{
CONF_TRACKED_ADDONS: [],
CONF_TRACKED_INTEGRATIONS: ["youtube"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: [],
},
@ -47,6 +51,7 @@ from tests.common import MockConfigEntry
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
{
CONF_TRACKED_ADDONS: [],
CONF_TRACKED_INTEGRATIONS: [],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
@ -83,6 +88,7 @@ async def test_form(
"user_input",
[
{
CONF_TRACKED_ADDONS: [],
CONF_TRACKED_INTEGRATIONS: [],
CONF_TRACKED_CUSTOM_INTEGRATIONS: [],
},
@ -113,6 +119,7 @@ async def test_submitting_empty_form(
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_TRACKED_ADDONS: ["core_samba"],
CONF_TRACKED_INTEGRATIONS: ["youtube"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
@ -123,6 +130,7 @@ async def test_submitting_empty_form(
assert result["title"] == "Home Assistant Analytics Insights"
assert result["data"] == {}
assert result["options"] == {
CONF_TRACKED_ADDONS: ["core_samba"],
CONF_TRACKED_INTEGRATIONS: ["youtube"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
}
@ -161,6 +169,7 @@ async def test_form_already_configured(
domain=DOMAIN,
data={},
options={
CONF_TRACKED_ADDONS: [],
CONF_TRACKED_INTEGRATIONS: ["youtube", "spotify"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: [],
},
@ -179,19 +188,32 @@ async def test_form_already_configured(
[
(
{
CONF_TRACKED_ADDONS: ["core_samba"],
CONF_TRACKED_INTEGRATIONS: ["youtube"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
{
CONF_TRACKED_ADDONS: ["core_samba"],
CONF_TRACKED_INTEGRATIONS: ["youtube"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
),
(
{
CONF_TRACKED_ADDONS: ["core_samba"],
},
{
CONF_TRACKED_ADDONS: ["core_samba"],
CONF_TRACKED_INTEGRATIONS: [],
CONF_TRACKED_CUSTOM_INTEGRATIONS: [],
},
),
(
{
CONF_TRACKED_INTEGRATIONS: ["youtube"],
},
{
CONF_TRACKED_ADDONS: [],
CONF_TRACKED_INTEGRATIONS: ["youtube"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: [],
},
@ -201,6 +223,7 @@ async def test_form_already_configured(
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
{
CONF_TRACKED_ADDONS: [],
CONF_TRACKED_INTEGRATIONS: [],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
@ -237,6 +260,7 @@ async def test_options_flow(
"user_input",
[
{
CONF_TRACKED_ADDONS: [],
CONF_TRACKED_INTEGRATIONS: [],
CONF_TRACKED_CUSTOM_INTEGRATIONS: [],
},
@ -267,6 +291,7 @@ async def test_submitting_empty_options_flow(
result = await hass.config_entries.options.async_configure(
result["flow_id"],
{
CONF_TRACKED_ADDONS: ["core_samba"],
CONF_TRACKED_INTEGRATIONS: ["youtube", "hue"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
},
@ -275,6 +300,7 @@ async def test_submitting_empty_options_flow(
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"] == {
CONF_TRACKED_ADDONS: ["core_samba"],
CONF_TRACKED_INTEGRATIONS: ["youtube", "hue"],
CONF_TRACKED_CUSTOM_INTEGRATIONS: ["hacs"],
}