New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Sentry component #30422
Add Sentry component #30422
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"config": { | ||
"title": "Sentry", | ||
"step": { | ||
"user": { | ||
"title": "Sentry", | ||
"description": "Enter your Sentry DSN" | ||
} | ||
}, | ||
"error": { | ||
"unknown": "Unexpected error", | ||
"bad_dsn": "Invalid DSN" | ||
}, | ||
"abort": { | ||
"already_configured": "Sentry is already configured" | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
"""The sentry integration.""" | ||
import logging | ||
|
||
import sentry_sdk | ||
from sentry_sdk.integrations.logging import LoggingIntegration | ||
import voluptuous as vol | ||
|
||
from homeassistant import config_entries | ||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers import config_validation as cv | ||
|
||
from .const import CONF_DSN, CONF_ENVIRONMENT, DOMAIN | ||
|
||
CONFIG_SCHEMA = vol.Schema( | ||
{ | ||
DOMAIN: vol.Schema( | ||
{vol.Required(CONF_DSN): cv.string, CONF_ENVIRONMENT: cv.string} | ||
) | ||
}, | ||
extra=vol.ALLOW_EXTRA, | ||
) | ||
|
||
|
||
async def async_setup(hass: HomeAssistant, config: dict): | ||
"""Set up the Sentry component.""" | ||
conf = config.get(DOMAIN) | ||
if conf is not None: | ||
hass.async_create_task( | ||
hass.config_entries.flow.async_init( | ||
DOMAIN, context={"source": config_entries.SOURCE_IMPORT} | ||
) | ||
) | ||
|
||
return True | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): | ||
"""Set up Sentry from a config entry.""" | ||
conf = entry.data | ||
|
||
hass.data[DOMAIN] = conf | ||
|
||
# https://docs.sentry.io/platforms/python/logging/ | ||
sentry_logging = LoggingIntegration( | ||
level=logging.INFO, # Capture info and above as breadcrumbs | ||
event_level=logging.ERROR, # Send errors as events | ||
) | ||
|
||
sentry_sdk.init( | ||
dsn=conf.get(CONF_DSN), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use |
||
environment=conf.get(CONF_ENVIRONMENT), | ||
integrations=[sentry_logging], | ||
) | ||
|
||
return True |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
"""Config flow for sentry integration.""" | ||
import logging | ||
|
||
from sentry_sdk.utils import BadDsn, Dsn | ||
import voluptuous as vol | ||
|
||
from homeassistant import config_entries, core | ||
|
||
from .const import CONF_DSN, DOMAIN # pylint: disable=unused-import | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
DATA_SCHEMA = vol.Schema({vol.Required(CONF_DSN): str}) | ||
|
||
|
||
async def validate_input(hass: core.HomeAssistant, data): | ||
"""Validate the DSN input allows us to connect. | ||
|
||
Data has the keys from DATA_SCHEMA with values provided by the user. | ||
""" | ||
# validate the dsn | ||
Dsn(data["dsn"]) | ||
dcramer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return {"title": "Sentry"} | ||
|
||
|
||
class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rename this class to something integration specific, eg |
||
"""Handle a Sentry config flow.""" | ||
|
||
VERSION = 1 | ||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL | ||
|
||
async def async_step_user(self, user_input=None): | ||
"""Handle a user config flow.""" | ||
if self._async_current_entries(): | ||
return self.async_abort(reason="already_configured") | ||
|
||
dcramer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
errors = {} | ||
if user_input is not None: | ||
try: | ||
info = await validate_input(self.hass, user_input) | ||
|
||
return self.async_create_entry(title=info["title"], data=user_input) | ||
except BadDsn: | ||
errors["base"] = "bad_dsn" | ||
except Exception: # pylint: disable=broad-except | ||
_LOGGER.exception("Unexpected exception") | ||
errors["base"] = "unknown" | ||
|
||
return self.async_show_form( | ||
step_id="user", data_schema=DATA_SCHEMA, errors=errors | ||
) | ||
|
||
async def async_step_import(self, import_config): | ||
"""Import a config entry from configuration.yaml.""" | ||
return await self.async_step_user(import_config) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
"""Constants for the sentry integration.""" | ||
|
||
DOMAIN = "sentry" | ||
|
||
CONF_DSN = "dsn" | ||
CONF_ENVIRONMENT = "environment" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"domain": "sentry", | ||
"name": "Sentry", | ||
"config_flow": true, | ||
"documentation": "https://www.home-assistant.io/integrations/sentry", | ||
"requirements": ["sentry-sdk==0.13.5"], | ||
"ssdp": [], | ||
"zeroconf": [], | ||
"homekit": {}, | ||
"dependencies": [], | ||
"codeowners": ["@dcramer"] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"config": { | ||
"title": "Sentry", | ||
"step": { | ||
"user": { | ||
"title": "Sentry", | ||
"description": "Enter your Sentry DSN" | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The data dict representing the input form data schema is missing here. |
||
}, | ||
"error": { | ||
"unknown": "Unexpected error", | ||
"bad_dsn": "Invalid DSN" | ||
}, | ||
"abort": { | ||
"already_configured": "Sentry is already configured" | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,6 +65,7 @@ | |
"point", | ||
"ps4", | ||
"rainmachine", | ||
"sentry", | ||
"simplisafe", | ||
"smartthings", | ||
"smhi", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""Tests for the sentry integration.""" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
"""Configuration for Sonos tests.""" | ||
import pytest | ||
|
||
from homeassistant.components.sentry import DOMAIN | ||
|
||
from tests.common import MockConfigEntry | ||
|
||
|
||
@pytest.fixture(name="config_entry") | ||
def config_entry_fixture(): | ||
"""Create a mock config entry.""" | ||
return MockConfigEntry(domain=DOMAIN, title="Sentry") | ||
|
||
|
||
@pytest.fixture(name="config") | ||
def config_fixture(): | ||
"""Create hass config fixture.""" | ||
return {DOMAIN: {"dsn": "http://public@sentry.local/1"}} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
"""Test the sentry config flow.""" | ||
from unittest.mock import patch | ||
|
||
from sentry_sdk.utils import BadDsn | ||
|
||
from homeassistant import config_entries, setup | ||
from homeassistant.components.sentry.const import DOMAIN | ||
|
||
from tests.common import mock_coro | ||
|
||
|
||
async def test_form(hass): | ||
"""Test we get the form.""" | ||
await setup.async_setup_component(hass, "persistent_notification", {}) | ||
result = await hass.config_entries.flow.async_init( | ||
DOMAIN, context={"source": config_entries.SOURCE_USER} | ||
) | ||
assert result["type"] == "form" | ||
assert result["errors"] == {} | ||
|
||
with patch( | ||
"homeassistant.components.sentry.config_flow.validate_input", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We shouldn't patch our own code under test. We should patch the library function we use in the input validation function. We want all our code in the config flow to be covered. |
||
return_value=mock_coro({"title": "Sentry"}), | ||
), patch( | ||
"homeassistant.components.sentry.async_setup", return_value=mock_coro(True) | ||
) as mock_setup, patch( | ||
"homeassistant.components.sentry.async_setup_entry", | ||
return_value=mock_coro(True), | ||
) as mock_setup_entry: | ||
result2 = await hass.config_entries.flow.async_configure( | ||
result["flow_id"], {"dsn": "http://public@sentry.local/1"}, | ||
) | ||
|
||
assert result2["type"] == "create_entry" | ||
assert result2["title"] == "Sentry" | ||
assert result2["data"] == { | ||
"dsn": "http://public@sentry.local/1", | ||
} | ||
await hass.async_block_till_done() | ||
assert len(mock_setup.mock_calls) == 1 | ||
assert len(mock_setup_entry.mock_calls) == 1 | ||
|
||
|
||
async def test_form_bad_dsn(hass): | ||
"""Test we handle bad dsn error.""" | ||
result = await hass.config_entries.flow.async_init( | ||
DOMAIN, context={"source": config_entries.SOURCE_USER} | ||
) | ||
|
||
with patch( | ||
"homeassistant.components.sentry.config_flow.validate_input", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above. |
||
side_effect=BadDsn, | ||
): | ||
result2 = await hass.config_entries.flow.async_configure( | ||
result["flow_id"], {"dsn": "foo"}, | ||
) | ||
|
||
assert result2["type"] == "form" | ||
assert result2["errors"] == {"base": "bad_dsn"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're not passing the config as data. That should be done to make it available in the import step in the config flow.