-
-
Notifications
You must be signed in to change notification settings - Fork 31.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Homeassistant Analytics Insights integration (#107634)
* Add Homeassistant Analytics integration * Add Homeassistant Analytics integration * Add Homeassistant Analytics integration * Fix feedback * Fix test * Update conftest.py * Add some testcases * Make code clear * log exception * Bump python-homeassistant-analytics to 0.2.1 * Bump python-homeassistant-analytics to 0.3.0 * Change domain to homeassistant_analytics_consumer * Add integration name to config flow selector * Update homeassistant/components/homeassistant_analytics_consumer/manifest.json Co-authored-by: Sid <27780930+autinerd@users.noreply.github.com> * Fix hassfest * Apply suggestions from code review Co-authored-by: Robert Resch <robert@resch.dev> * Bump python-homeassistant-analytics to 0.4.0 * Rename to Home Assistant Analytics Insights * Update homeassistant/components/analytics_insights/config_flow.py Co-authored-by: Robert Resch <robert@resch.dev> * Update homeassistant/components/analytics_insights/manifest.json Co-authored-by: Robert Resch <robert@resch.dev> * Rename to Home Assistant Analytics Insights * add test * Fallback to 0 when there is no data found * Allow to select any integration * Fix tests * Fix tests * Update tests/components/analytics_insights/conftest.py Co-authored-by: Robert Resch <robert@resch.dev> * Update tests/components/analytics_insights/test_sensor.py Co-authored-by: Robert Resch <robert@resch.dev> * Fix format * Fix tests --------- Co-authored-by: Sid <27780930+autinerd@users.noreply.github.com> Co-authored-by: Robert Resch <robert@resch.dev>
- Loading branch information
1 parent
52ede95
commit d9f1450
Showing
22 changed files
with
3,226 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
"""The Homeassistant Analytics integration.""" | ||
from __future__ import annotations | ||
|
||
from dataclasses import dataclass | ||
|
||
from python_homeassistant_analytics import HomeassistantAnalyticsClient | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import Platform | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
|
||
from .const import CONF_TRACKED_INTEGRATIONS, DOMAIN | ||
from .coordinator import HomeassistantAnalyticsDataUpdateCoordinator | ||
|
||
PLATFORMS: list[Platform] = [Platform.SENSOR] | ||
|
||
|
||
@dataclass(frozen=True) | ||
class AnalyticsData: | ||
"""Analytics data class.""" | ||
|
||
coordinator: HomeassistantAnalyticsDataUpdateCoordinator | ||
names: dict[str, str] | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Set up Homeassistant Analytics from a config entry.""" | ||
client = HomeassistantAnalyticsClient(session=async_get_clientsession(hass)) | ||
|
||
integrations = await client.get_integrations() | ||
|
||
names = {} | ||
for integration in entry.options[CONF_TRACKED_INTEGRATIONS]: | ||
if integration not in integrations: | ||
names[integration] = integration | ||
continue | ||
names[integration] = integrations[integration].title | ||
|
||
coordinator = HomeassistantAnalyticsDataUpdateCoordinator(hass, client) | ||
|
||
await coordinator.async_config_entry_first_refresh() | ||
|
||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = AnalyticsData( | ||
coordinator=coordinator, names=names | ||
) | ||
|
||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): | ||
hass.data[DOMAIN].pop(entry.entry_id) | ||
|
||
return unload_ok |
74 changes: 74 additions & 0 deletions
74
homeassistant/components/analytics_insights/config_flow.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
"""Config flow for Homeassistant Analytics integration.""" | ||
from __future__ import annotations | ||
|
||
from typing import Any | ||
|
||
from python_homeassistant_analytics import ( | ||
HomeassistantAnalyticsClient, | ||
HomeassistantAnalyticsConnectionError, | ||
) | ||
from python_homeassistant_analytics.models import IntegrationType | ||
import voluptuous as vol | ||
|
||
from homeassistant.config_entries import ConfigFlow | ||
from homeassistant.data_entry_flow import FlowResult | ||
from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
from homeassistant.helpers.selector import ( | ||
SelectOptionDict, | ||
SelectSelector, | ||
SelectSelectorConfig, | ||
) | ||
|
||
from .const import CONF_TRACKED_INTEGRATIONS, DOMAIN, LOGGER | ||
|
||
INTEGRATION_TYPES_WITHOUT_ANALYTICS = ( | ||
IntegrationType.BRAND, | ||
IntegrationType.ENTITY, | ||
IntegrationType.VIRTUAL, | ||
) | ||
|
||
|
||
class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN): | ||
"""Handle a config flow for Homeassistant Analytics.""" | ||
|
||
async def async_step_user( | ||
self, user_input: dict[str, Any] | None = None | ||
) -> FlowResult: | ||
"""Handle the initial step.""" | ||
self._async_abort_entries_match() | ||
if user_input: | ||
return self.async_create_entry( | ||
title="Home Assistant Analytics Insights", data={}, options=user_input | ||
) | ||
|
||
client = HomeassistantAnalyticsClient( | ||
session=async_get_clientsession(self.hass) | ||
) | ||
try: | ||
integrations = await client.get_integrations() | ||
except HomeassistantAnalyticsConnectionError: | ||
LOGGER.exception("Error connecting to Home Assistant analytics") | ||
return self.async_abort(reason="cannot_connect") | ||
|
||
options = [ | ||
SelectOptionDict( | ||
value=domain, | ||
label=integration.title, | ||
) | ||
for domain, integration in integrations.items() | ||
if integration.integration_type not in INTEGRATION_TYPES_WITHOUT_ANALYTICS | ||
] | ||
return self.async_show_form( | ||
step_id="user", | ||
data_schema=vol.Schema( | ||
{ | ||
vol.Required(CONF_TRACKED_INTEGRATIONS): SelectSelector( | ||
SelectSelectorConfig( | ||
options=options, | ||
multiple=True, | ||
sort=True, | ||
) | ||
), | ||
} | ||
), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
"""Constants for the Homeassistant Analytics integration.""" | ||
import logging | ||
|
||
DOMAIN = "analytics_insights" | ||
|
||
CONF_TRACKED_INTEGRATIONS = "tracked_integrations" | ||
|
||
LOGGER = logging.getLogger(__package__) |
53 changes: 53 additions & 0 deletions
53
homeassistant/components/analytics_insights/coordinator.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
"""DataUpdateCoordinator for the Homeassistant Analytics integration.""" | ||
from __future__ import annotations | ||
|
||
from datetime import timedelta | ||
|
||
from python_homeassistant_analytics import ( | ||
HomeassistantAnalyticsClient, | ||
HomeassistantAnalyticsConnectionError, | ||
HomeassistantAnalyticsNotModifiedError, | ||
) | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed | ||
|
||
from .const import CONF_TRACKED_INTEGRATIONS, DOMAIN, LOGGER | ||
|
||
|
||
class HomeassistantAnalyticsDataUpdateCoordinator( | ||
DataUpdateCoordinator[dict[str, int]] | ||
): | ||
"""A Homeassistant Analytics Data Update Coordinator.""" | ||
|
||
config_entry: ConfigEntry | ||
|
||
def __init__( | ||
self, hass: HomeAssistant, client: HomeassistantAnalyticsClient | ||
) -> None: | ||
"""Initialize the Homeassistant Analytics data coordinator.""" | ||
super().__init__( | ||
hass, | ||
LOGGER, | ||
name=DOMAIN, | ||
update_interval=timedelta(hours=12), | ||
) | ||
self._client = client | ||
self._tracked_integrations = self.config_entry.options[ | ||
CONF_TRACKED_INTEGRATIONS | ||
] | ||
|
||
async def _async_update_data(self) -> dict[str, int]: | ||
try: | ||
data = await self._client.get_current_analytics() | ||
except HomeassistantAnalyticsConnectionError as err: | ||
raise UpdateFailed( | ||
"Error communicating with Homeassistant Analytics" | ||
) from err | ||
except HomeassistantAnalyticsNotModifiedError: | ||
return self.data | ||
return { | ||
integration: data.integrations.get(integration, 0) | ||
for integration in self._tracked_integrations | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"domain": "analytics_insights", | ||
"name": "Home Assistant Analytics Insights", | ||
"codeowners": ["@joostlek"], | ||
"config_flow": true, | ||
"documentation": "https://www.home-assistant.io/integrations/analytics_insights", | ||
"integration_type": "service", | ||
"iot_class": "cloud_polling", | ||
"loggers": ["python_homeassistant_analytics"], | ||
"requirements": ["python-homeassistant-analytics==0.5.0"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
"""Sensor for Home Assistant analytics.""" | ||
from __future__ import annotations | ||
|
||
from homeassistant.components.sensor import SensorEntity, SensorStateClass | ||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo | ||
from homeassistant.helpers.entity_platform import AddEntitiesCallback | ||
from homeassistant.helpers.update_coordinator import CoordinatorEntity | ||
|
||
from . import AnalyticsData | ||
from .const import DOMAIN | ||
from .coordinator import HomeassistantAnalyticsDataUpdateCoordinator | ||
|
||
|
||
async def async_setup_entry( | ||
hass: HomeAssistant, | ||
entry: ConfigEntry, | ||
async_add_entities: AddEntitiesCallback, | ||
) -> None: | ||
"""Initialize the entries.""" | ||
|
||
analytics_data: AnalyticsData = hass.data[DOMAIN][entry.entry_id] | ||
async_add_entities( | ||
HomeassistantAnalyticsSensor( | ||
analytics_data.coordinator, | ||
integration_domain, | ||
analytics_data.names[integration_domain], | ||
) | ||
for integration_domain in analytics_data.coordinator.data | ||
) | ||
|
||
|
||
class HomeassistantAnalyticsSensor( | ||
CoordinatorEntity[HomeassistantAnalyticsDataUpdateCoordinator], SensorEntity | ||
): | ||
"""Home Assistant Analytics Sensor.""" | ||
|
||
_attr_has_entity_name = True | ||
_attr_state_class = SensorStateClass.TOTAL | ||
_attr_native_unit_of_measurement = "active installations" | ||
|
||
def __init__( | ||
self, | ||
coordinator: HomeassistantAnalyticsDataUpdateCoordinator, | ||
integration_domain: str, | ||
name: str, | ||
) -> None: | ||
"""Initialize the sensor.""" | ||
super().__init__(coordinator) | ||
self._attr_name = name | ||
self._attr_unique_id = f"core_{integration_domain}_active_installations" | ||
self._attr_device_info = DeviceInfo( | ||
identifiers={(DOMAIN, DOMAIN)}, | ||
entry_type=DeviceEntryType.SERVICE, | ||
) | ||
self._integration_domain = integration_domain | ||
|
||
@property | ||
def native_value(self) -> int | None: | ||
"""Return the state of the sensor.""" | ||
return self.coordinator.data.get(self._integration_domain) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"config": { | ||
"step": { | ||
"user": { | ||
"data": { | ||
"tracked_integrations": "Integrations" | ||
} | ||
} | ||
}, | ||
"error": { | ||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", | ||
"unknown": "[%key:common::config_flow::error::unknown%]" | ||
}, | ||
"abort": { | ||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
"""Tests for the Homeassistant Analytics integration.""" | ||
from homeassistant.core import HomeAssistant | ||
|
||
from tests.common import MockConfigEntry | ||
|
||
|
||
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None: | ||
"""Fixture for setting up the component.""" | ||
config_entry.add_to_hass(hass) | ||
|
||
await hass.config_entries.async_setup(config_entry.entry_id) |
Oops, something went wrong.