Add addon support to Home Assistant Analytics Insights (#128806)
parent
c9aba288b4
commit
a95c232f11
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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%]"
|
||||
}
|
||||
|
|
|
@ -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"],
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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({
|
||||
|
|
|
@ -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"],
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue