mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Add diagnostics integration (#64330)
This commit is contained in:
parent
a334e0c7b9
commit
6055cd20c8
@ -208,6 +208,8 @@ tests/components/dexcom/* @gagebenne
|
|||||||
homeassistant/components/dhcp/* @bdraco
|
homeassistant/components/dhcp/* @bdraco
|
||||||
tests/components/dhcp/* @bdraco
|
tests/components/dhcp/* @bdraco
|
||||||
homeassistant/components/dht/* @thegardenmonkey
|
homeassistant/components/dht/* @thegardenmonkey
|
||||||
|
homeassistant/components/diagnostics/* @home-assistant/core
|
||||||
|
tests/components/diagnostics/* @home-assistant/core
|
||||||
homeassistant/components/digital_ocean/* @fabaff
|
homeassistant/components/digital_ocean/* @fabaff
|
||||||
homeassistant/components/discogs/* @thibmaek
|
homeassistant/components/discogs/* @thibmaek
|
||||||
homeassistant/components/dlna_dmr/* @StevenLooman @chishm
|
homeassistant/components/dlna_dmr/* @StevenLooman @chishm
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"cloud",
|
"cloud",
|
||||||
"counter",
|
"counter",
|
||||||
"dhcp",
|
"dhcp",
|
||||||
|
"diagnostics",
|
||||||
"energy",
|
"energy",
|
||||||
"frontend",
|
"frontend",
|
||||||
"history",
|
"history",
|
||||||
|
123
homeassistant/components/diagnostics/__init__.py
Normal file
123
homeassistant/components/diagnostics/__init__.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
"""The Diagnostics integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components import http, websocket_api
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers import integration_platform
|
||||||
|
from homeassistant.helpers.json import ExtendedJSONEncoder
|
||||||
|
from homeassistant.util.json import (
|
||||||
|
find_paths_unserializable_data,
|
||||||
|
format_unserializable_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
|
||||||
|
"""Set up Diagnostics from a config entry."""
|
||||||
|
hass.data[DOMAIN] = {}
|
||||||
|
|
||||||
|
await integration_platform.async_process_integration_platforms(
|
||||||
|
hass, DOMAIN, _register_diagnostics_platform
|
||||||
|
)
|
||||||
|
|
||||||
|
websocket_api.async_register_command(hass, handle_info)
|
||||||
|
hass.http.register_view(DownloadDiagnosticsView)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class DiagnosticsProtocol(Protocol):
|
||||||
|
"""Define the format that diagnostics platforms can have."""
|
||||||
|
|
||||||
|
async def async_get_config_entry_diagnostics(
|
||||||
|
self, hass: HomeAssistant, config_entry: ConfigEntry
|
||||||
|
) -> dict:
|
||||||
|
"""Return diagnostics for a config entry."""
|
||||||
|
|
||||||
|
|
||||||
|
async def _register_diagnostics_platform(
|
||||||
|
hass: HomeAssistant, integration_domain: str, platform: DiagnosticsProtocol
|
||||||
|
):
|
||||||
|
"""Register a diagnostics platform."""
|
||||||
|
hass.data[DOMAIN][integration_domain] = {
|
||||||
|
"config_entry": getattr(platform, "async_get_config_entry_diagnostics", None)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@websocket_api.require_admin
|
||||||
|
@websocket_api.websocket_command({vol.Required("type"): "diagnostics/list"})
|
||||||
|
@callback
|
||||||
|
def handle_info(
|
||||||
|
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
|
||||||
|
):
|
||||||
|
"""List all possible diagnostic handlers."""
|
||||||
|
connection.send_result(
|
||||||
|
msg["id"],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"domain": domain,
|
||||||
|
"handlers": {key: val is not None for key, val in info.items()},
|
||||||
|
}
|
||||||
|
for domain, info in hass.data[DOMAIN].items()
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadDiagnosticsView(http.HomeAssistantView):
|
||||||
|
"""Download diagnostics view."""
|
||||||
|
|
||||||
|
url = "/api/diagnostics/{d_type}/{d_id}"
|
||||||
|
name = "api:diagnostics"
|
||||||
|
|
||||||
|
async def get( # pylint: disable=no-self-use
|
||||||
|
self, request: web.Request, d_type: str, d_id: str
|
||||||
|
) -> web.Response:
|
||||||
|
"""Download diagnostics."""
|
||||||
|
if d_type != "config_entry":
|
||||||
|
return web.Response(status=404)
|
||||||
|
|
||||||
|
hass = request.app["hass"]
|
||||||
|
config_entry = hass.config_entries.async_get_entry(d_id)
|
||||||
|
|
||||||
|
if config_entry is None:
|
||||||
|
return web.Response(status=404)
|
||||||
|
|
||||||
|
info = hass.data[DOMAIN].get(config_entry.domain)
|
||||||
|
|
||||||
|
if info is None:
|
||||||
|
return web.Response(status=404)
|
||||||
|
|
||||||
|
if info["config_entry"] is None:
|
||||||
|
return web.Response(status=404)
|
||||||
|
|
||||||
|
data = await info["config_entry"](hass, config_entry)
|
||||||
|
|
||||||
|
try:
|
||||||
|
json_data = json.dumps(data, indent=4, cls=ExtendedJSONEncoder)
|
||||||
|
except TypeError:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Failed to serialize to JSON: %s/%s. Bad data at %s",
|
||||||
|
d_type,
|
||||||
|
d_id,
|
||||||
|
format_unserializable_data(find_paths_unserializable_data(data)),
|
||||||
|
)
|
||||||
|
return web.Response(status=500)
|
||||||
|
|
||||||
|
return web.Response(
|
||||||
|
body=json_data,
|
||||||
|
content_type="application/json",
|
||||||
|
headers={
|
||||||
|
"Content-Disposition": f'attachment; filename="{config_entry.domain}-{config_entry.entry_id}.json"'
|
||||||
|
},
|
||||||
|
)
|
3
homeassistant/components/diagnostics/const.py
Normal file
3
homeassistant/components/diagnostics/const.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
"""Constants for the Diagnostics integration."""
|
||||||
|
|
||||||
|
DOMAIN = "diagnostics"
|
9
homeassistant/components/diagnostics/manifest.json
Normal file
9
homeassistant/components/diagnostics/manifest.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"domain": "diagnostics",
|
||||||
|
"name": "Diagnostics",
|
||||||
|
"config_flow": false,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/diagnostics",
|
||||||
|
"dependencies": ["http"],
|
||||||
|
"codeowners": ["@home-assistant/core"],
|
||||||
|
"quality_scale": "internal"
|
||||||
|
}
|
3
homeassistant/components/diagnostics/strings.json
Normal file
3
homeassistant/components/diagnostics/strings.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"title": "Diagnostics"
|
||||||
|
}
|
21
homeassistant/components/diagnostics/translations/en.json
Normal file
21
homeassistant/components/diagnostics/translations/en.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Device is already configured"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect",
|
||||||
|
"invalid_auth": "Invalid authentication",
|
||||||
|
"unknown": "Unexpected error"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "Host",
|
||||||
|
"password": "Password",
|
||||||
|
"username": "Username"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -52,6 +52,7 @@ NO_IOT_CLASS = [
|
|||||||
"default_config",
|
"default_config",
|
||||||
"device_automation",
|
"device_automation",
|
||||||
"device_tracker",
|
"device_tracker",
|
||||||
|
"diagnostics",
|
||||||
"discovery",
|
"discovery",
|
||||||
"downloader",
|
"downloader",
|
||||||
"fan",
|
"fan",
|
||||||
|
1
tests/components/diagnostics/__init__.py
Normal file
1
tests/components/diagnostics/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the Diagnostics integration."""
|
56
tests/components/diagnostics/test_init.py
Normal file
56
tests/components/diagnostics/test_init.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
"""Test the Diagnostics integration."""
|
||||||
|
from http import HTTPStatus
|
||||||
|
from unittest.mock import AsyncMock, Mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.websocket_api.const import TYPE_RESULT
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, mock_platform
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
async def mock_diagnostics_integration(hass):
|
||||||
|
"""Mock a diagnostics integration."""
|
||||||
|
hass.config.components.add("fake_integration")
|
||||||
|
mock_platform(
|
||||||
|
hass,
|
||||||
|
"fake_integration.diagnostics",
|
||||||
|
Mock(
|
||||||
|
async_get_config_entry_diagnostics=AsyncMock(
|
||||||
|
return_value={
|
||||||
|
"hello": "info",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assert await async_setup_component(hass, "diagnostics", {})
|
||||||
|
|
||||||
|
|
||||||
|
async def test_websocket_info(hass, hass_ws_client):
|
||||||
|
"""Test camera_thumbnail websocket command."""
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
await client.send_json({"id": 5, "type": "diagnostics/list"})
|
||||||
|
|
||||||
|
msg = await client.receive_json()
|
||||||
|
|
||||||
|
assert msg["id"] == 5
|
||||||
|
assert msg["type"] == TYPE_RESULT
|
||||||
|
assert msg["success"]
|
||||||
|
assert msg["result"] == [
|
||||||
|
{"domain": "fake_integration", "handlers": {"config_entry": True}}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_download_diagnostics(hass, hass_client):
|
||||||
|
"""Test record service."""
|
||||||
|
config_entry = MockConfigEntry(domain="fake_integration")
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
client = await hass_client()
|
||||||
|
response = await client.get(
|
||||||
|
f"/api/diagnostics/config_entry/{config_entry.entry_id}"
|
||||||
|
)
|
||||||
|
assert response.status == HTTPStatus.OK
|
||||||
|
assert await response.json() == {"hello": "info"}
|
Loading…
x
Reference in New Issue
Block a user