Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion homeassistant/components/airzone_cloud/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
"iot_class": "cloud_push",
"loggers": ["aioairzone_cloud"],
"requirements": ["aioairzone-cloud==0.6.16"]
"requirements": ["aioairzone-cloud==0.7.0"]
}
96 changes: 62 additions & 34 deletions homeassistant/components/datadog/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

import logging

from datadog import initialize, statsd
from datadog import DogStatsd, initialize
import voluptuous as vol

from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import (
CONF_HOST,
CONF_PORT,
Expand All @@ -17,14 +18,19 @@
from homeassistant.helpers import config_validation as cv, state as state_helper
from homeassistant.helpers.typing import ConfigType

from . import config_flow as config_flow
from .const import (
CONF_RATE,
DEFAULT_HOST,
DEFAULT_PORT,
DEFAULT_PREFIX,
DEFAULT_RATE,
DOMAIN,
)

_LOGGER = logging.getLogger(__name__)

CONF_RATE = "rate"
DEFAULT_HOST = "localhost"
DEFAULT_PORT = 8125
DEFAULT_PREFIX = "hass"
DEFAULT_RATE = 1
DOMAIN = "datadog"
type DatadogConfigEntry = ConfigEntry[DogStatsd]

CONFIG_SCHEMA = vol.Schema(
{
Expand All @@ -43,63 +49,85 @@
)


def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Datadog component."""
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Datadog integration from YAML, initiating config flow import."""
if DOMAIN not in config:
return True

hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=config[DOMAIN],
)
)

return True


async def async_setup_entry(hass: HomeAssistant, entry: DatadogConfigEntry) -> bool:
"""Set up Datadog from a config entry."""

conf = config[DOMAIN]
host = conf[CONF_HOST]
port = conf[CONF_PORT]
sample_rate = conf[CONF_RATE]
prefix = conf[CONF_PREFIX]
data = entry.data
options = entry.options
host = data[CONF_HOST]
port = data[CONF_PORT]
prefix = options[CONF_PREFIX]
sample_rate = options[CONF_RATE]

statsd_client = DogStatsd(host=host, port=port, namespace=prefix)
entry.runtime_data = statsd_client

initialize(statsd_host=host, statsd_port=port)

def logbook_entry_listener(event):
"""Listen for logbook entries and send them as events."""
name = event.data.get("name")
message = event.data.get("message")

statsd.event(
entry.runtime_data.event(
title="Home Assistant",
text=f"%%% \n **{name}** {message} \n %%%",
message=f"%%% \n **{name}** {message} \n %%%",
tags=[
f"entity:{event.data.get('entity_id')}",
f"domain:{event.data.get('domain')}",
],
)

_LOGGER.debug("Sent event %s", event.data.get("entity_id"))

def state_changed_listener(event):
"""Listen for new messages on the bus and sends them to Datadog."""
state = event.data.get("new_state")

if state is None or state.state == STATE_UNKNOWN:
return

states = dict(state.attributes)
metric = f"{prefix}.{state.domain}"
tags = [f"entity:{state.entity_id}"]

for key, value in states.items():
if isinstance(value, (float, int)):
attribute = f"{metric}.{key.replace(' ', '_')}"
for key, value in state.attributes.items():
if isinstance(value, (float, int, bool)):
value = int(value) if isinstance(value, bool) else value
statsd.gauge(attribute, value, sample_rate=sample_rate, tags=tags)

_LOGGER.debug("Sent metric %s: %s (tags: %s)", attribute, value, tags)
attribute = f"{metric}.{key.replace(' ', '_')}"
entry.runtime_data.gauge(
attribute, value, sample_rate=sample_rate, tags=tags
)

try:
value = state_helper.state_as_number(state)
entry.runtime_data.gauge(metric, value, sample_rate=sample_rate, tags=tags)
except ValueError:
_LOGGER.debug("Error sending %s: %s (tags: %s)", metric, state.state, tags)
return
pass

statsd.gauge(metric, value, sample_rate=sample_rate, tags=tags)
entry.async_on_unload(
hass.bus.async_listen(EVENT_LOGBOOK_ENTRY, logbook_entry_listener)
)
entry.async_on_unload(
hass.bus.async_listen(EVENT_STATE_CHANGED, state_changed_listener)
)

_LOGGER.debug("Sent metric %s: %s (tags: %s)", metric, value, tags)
return True

hass.bus.listen(EVENT_LOGBOOK_ENTRY, logbook_entry_listener)
hass.bus.listen(EVENT_STATE_CHANGED, state_changed_listener)

async def async_unload_entry(hass: HomeAssistant, entry: DatadogConfigEntry) -> bool:
"""Unload a Datadog config entry."""
runtime = entry.runtime_data
runtime.flush()
runtime.close_socket()
return True
185 changes: 185 additions & 0 deletions homeassistant/components/datadog/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
"""Config flow for Datadog."""

from typing import Any

from datadog import DogStatsd
import voluptuous as vol

from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_PREFIX
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue

from .const import (
CONF_RATE,
DEFAULT_HOST,
DEFAULT_PORT,
DEFAULT_PREFIX,
DEFAULT_RATE,
DOMAIN,
)


class DatadogConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Datadog."""

VERSION = 1

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle user config flow."""
errors: dict[str, str] = {}
if user_input:
# Validate connection to Datadog Agent
success = await validate_datadog_connection(
self.hass,
user_input,
)
self._async_abort_entries_match(
{CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT]}
)
if not success:
errors["base"] = "cannot_connect"
else:
return self.async_create_entry(
title=f"Datadog {user_input['host']}",
data={
CONF_HOST: user_input[CONF_HOST],
CONF_PORT: user_input[CONF_PORT],
},
options={
CONF_PREFIX: user_input[CONF_PREFIX],
CONF_RATE: user_input[CONF_RATE],
},
)

return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_HOST, default=DEFAULT_HOST): str,
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
vol.Required(CONF_PREFIX, default=DEFAULT_PREFIX): str,
vol.Required(CONF_RATE, default=DEFAULT_RATE): int,
}
),
errors=errors,
)

async def async_step_import(self, user_input: dict[str, Any]) -> ConfigFlowResult:
"""Handle import from configuration.yaml."""
# Check for duplicates
self._async_abort_entries_match(
{CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT]}
)

result = await self.async_step_user(user_input)

if errors := result.get("errors"):
await deprecate_yaml_issue(self.hass, False)
return self.async_abort(reason=errors["base"])

await deprecate_yaml_issue(self.hass, True)
return result

@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
"""Get the options flow handler."""
return DatadogOptionsFlowHandler()


class DatadogOptionsFlowHandler(OptionsFlow):
"""Handle Datadog options."""

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Manage the Datadog options."""
errors: dict[str, str] = {}
data = self.config_entry.data
options = self.config_entry.options

if user_input is None:
user_input = {}

success = await validate_datadog_connection(
self.hass,
{**data, **user_input},
)
if success:
return self.async_create_entry(
data={
CONF_PREFIX: user_input[CONF_PREFIX],
CONF_RATE: user_input[CONF_RATE],
}
)

errors["base"] = "cannot_connect"
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required(CONF_PREFIX, default=options[CONF_PREFIX]): str,
vol.Required(CONF_RATE, default=options[CONF_RATE]): int,
}
),
errors=errors,
)


async def validate_datadog_connection(
hass: HomeAssistant, user_input: dict[str, Any]
) -> bool:
"""Attempt to send a test metric to the Datadog agent."""
try:
client = DogStatsd(user_input[CONF_HOST], user_input[CONF_PORT])
await hass.async_add_executor_job(client.increment, "connection_test")
except (OSError, ValueError):
return False
else:
return True


async def deprecate_yaml_issue(
hass: HomeAssistant,
import_success: bool,
) -> None:
"""Create an issue to deprecate YAML config."""
if import_success:
async_create_issue(
hass,
HOMEASSISTANT_DOMAIN,
f"deprecated_yaml_{DOMAIN}",
is_fixable=False,
issue_domain=DOMAIN,
breaks_in_ha_version="2026.2.0",
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "Datadog",
},
)
else:
async_create_issue(
hass,
DOMAIN,
"deprecated_yaml_import_connection_error",
breaks_in_ha_version="2026.2.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml_import_connection_error",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "Datadog",
"url": f"/config/integrations/dashboard/add?domain={DOMAIN}",
},
)
10 changes: 10 additions & 0 deletions homeassistant/components/datadog/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Constants for the Datadog integration."""

DOMAIN = "datadog"

CONF_RATE = "rate"

DEFAULT_HOST = "localhost"
DEFAULT_PORT = 8125
DEFAULT_PREFIX = "hass"
DEFAULT_RATE = 1
1 change: 1 addition & 0 deletions homeassistant/components/datadog/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"domain": "datadog",
"name": "Datadog",
"codeowners": [],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/datadog",
"iot_class": "local_push",
"loggers": ["datadog"],
Expand Down
Loading
Loading