Add options flow for AirVisual (#32634)

* Add options flow for AirVisual

* Code review

Co-Authored-By: Robert Svensson <Kane610@users.noreply.github.com>

Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>
This commit is contained in:
Aaron Bach 2020-03-10 14:16:25 -06:00 committed by GitHub
parent a3c55b5e96
commit 21cff003f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 132 additions and 21 deletions

View file

@ -11,13 +11,23 @@
"data": {
"api_key": "API Key",
"latitude": "Latitude",
"longitude": "Longitude",
"show_on_map": "Show monitored geography on the map"
"longitude": "Longitude"
},
"description": "Monitor air quality in a geographical location.",
"title": "Configure AirVisual"
}
},
"title": "AirVisual"
},
"options": {
"step": {
"init": {
"data": {
"show_on_map": "Show monitored geography on the map"
},
"description": "Set various options for the AirVisual integration.",
"title": "Configure AirVisual"
}
}
}
}

View file

@ -139,6 +139,8 @@ async def async_setup_entry(hass, config_entry):
hass, refresh, DEFAULT_SCAN_INTERVAL
)
config_entry.add_update_listener(async_update_options)
return True
@ -154,6 +156,12 @@ async def async_unload_entry(hass, config_entry):
return True
async def async_update_options(hass, config_entry):
"""Handle an options update."""
airvisual = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
airvisual.async_update_options(config_entry.options)
class AirVisualData:
"""Define a class to manage data from the AirVisual cloud API."""
@ -162,7 +170,7 @@ class AirVisualData:
self._client = client
self._hass = hass
self.data = {}
self.show_on_map = config_entry.options[CONF_SHOW_ON_MAP]
self.options = config_entry.options
self.geographies = {
async_get_geography_id(geography): geography
@ -199,3 +207,9 @@ class AirVisualData:
_LOGGER.debug("Received new data")
async_dispatcher_send(self._hass, TOPIC_UPDATE)
@callback
def async_update_options(self, options):
"""Update the data manager's options."""
self.options = options
async_dispatcher_send(self._hass, TOPIC_UPDATE)

View file

@ -6,7 +6,12 @@ from pyairvisual.errors import InvalidKeyError
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.const import (
CONF_API_KEY,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_SHOW_ON_MAP,
)
from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client, config_validation as cv
@ -16,7 +21,7 @@ _LOGGER = logging.getLogger("homeassistant.components.airvisual")
class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a AirVisual config flow."""
"""Handle an AirVisual config flow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
@ -48,6 +53,12 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
step_id="user", data_schema=self.cloud_api_schema, errors=errors or {},
)
@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Define the config flow to handle options."""
return AirVisualOptionsFlowHandler(config_entry)
async def async_step_import(self, import_config):
"""Import a config entry from configuration.yaml."""
return await self.async_step_user(import_config)
@ -85,3 +96,28 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_create_entry(
title=f"Cloud API (API key: {user_input[CONF_API_KEY][:4]}...)", data=data
)
class AirVisualOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle an AirVisual options flow."""
def __init__(self, config_entry):
"""Initialize."""
self.config_entry = config_entry
async def async_step_init(self, user_input=None):
"""Manage the options."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required(
CONF_SHOW_ON_MAP,
default=self.config_entry.options.get(CONF_SHOW_ON_MAP),
): bool
}
),
)

View file

@ -11,6 +11,7 @@ from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_SHOW_ON_MAP,
CONF_STATE,
)
from homeassistant.core import callback
@ -110,15 +111,6 @@ class AirVisualSensor(Entity):
ATTR_COUNTRY: airvisual.data[geography_id].get(CONF_COUNTRY),
}
geography = airvisual.geographies[geography_id]
if geography.get(CONF_LATITUDE):
if airvisual.show_on_map:
self._attrs[ATTR_LATITUDE] = geography[CONF_LATITUDE]
self._attrs[ATTR_LONGITUDE] = geography[CONF_LONGITUDE]
else:
self._attrs["lati"] = geography[CONF_LATITUDE]
self._attrs["long"] = geography[CONF_LONGITUDE]
@property
def available(self):
"""Return True if entity is available."""
@ -199,6 +191,19 @@ class AirVisualSensor(Entity):
}
)
geography = self._airvisual.geographies[self._geography_id]
if CONF_LATITUDE in geography:
if self._airvisual.options[CONF_SHOW_ON_MAP]:
self._attrs[ATTR_LATITUDE] = geography[CONF_LATITUDE]
self._attrs[ATTR_LONGITUDE] = geography[CONF_LONGITUDE]
self._attrs.pop("lati", None)
self._attrs.pop("long", None)
else:
self._attrs["lati"] = geography[CONF_LATITUDE]
self._attrs["long"] = geography[CONF_LONGITUDE]
self._attrs.pop(ATTR_LATITUDE, None)
self._attrs.pop(ATTR_LONGITUDE, None)
async def async_will_remove_from_hass(self) -> None:
"""Disconnect dispatcher listener when removed."""
for cancel in self._async_unsub_dispatcher_connects:

View file

@ -8,8 +8,7 @@
"data": {
"api_key": "API Key",
"latitude": "Latitude",
"longitude": "Longitude",
"show_on_map": "Show monitored geography on the map"
"longitude": "Longitude"
}
}
},
@ -19,5 +18,16 @@
"abort": {
"already_configured": "This API key is already in use."
}
},
"options": {
"step": {
"init": {
"title": "Configure AirVisual",
"description": "Set various options for the AirVisual integration.",
"data": {
"show_on_map": "Show monitored geography on the map"
}
}
}
}
}

View file

@ -5,7 +5,12 @@ from pyairvisual.errors import InvalidKeyError
from homeassistant import data_entry_flow
from homeassistant.components.airvisual import CONF_GEOGRAPHIES, DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.const import (
CONF_API_KEY,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_SHOW_ON_MAP,
)
from tests.common import MockConfigEntry
@ -37,6 +42,34 @@ async def test_invalid_api_key(hass):
assert result["errors"] == {CONF_API_KEY: "invalid_api_key"}
async def test_options_flow(hass):
"""Test config flow options."""
conf = {CONF_API_KEY: "abcde12345"}
config_entry = MockConfigEntry(
domain=DOMAIN,
unique_id="abcde12345",
data=conf,
options={CONF_SHOW_ON_MAP: True},
)
config_entry.add_to_hass(hass)
with patch(
"homeassistant.components.airvisual.async_setup_entry", return_value=True
):
result = await hass.config_entries.options.async_init(config_entry.entry_id)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"], user_input={CONF_SHOW_ON_MAP: False}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert config_entry.options == {CONF_SHOW_ON_MAP: False}
async def test_show_form(hass):
"""Test that the form is served with no input."""
result = await hass.config_entries.flow.async_init(
@ -49,10 +82,13 @@ async def test_show_form(hass):
async def test_step_import(hass):
"""Test that the import step works."""
conf = {CONF_API_KEY: "abcde12345"}
conf = {
CONF_API_KEY: "abcde12345",
CONF_GEOGRAPHIES: [{CONF_LATITUDE: 51.528308, CONF_LONGITUDE: -0.3817765}],
}
with patch(
"homeassistant.components.wwlln.async_setup_entry", return_value=True
"homeassistant.components.airvisual.async_setup_entry", return_value=True
), patch("pyairvisual.api.API.nearest_city"):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=conf
@ -62,7 +98,7 @@ async def test_step_import(hass):
assert result["title"] == "Cloud API (API key: abcd...)"
assert result["data"] == {
CONF_API_KEY: "abcde12345",
CONF_GEOGRAPHIES: [{CONF_LATITUDE: 32.87336, CONF_LONGITUDE: -117.22743}],
CONF_GEOGRAPHIES: [{CONF_LATITUDE: 51.528308, CONF_LONGITUDE: -0.3817765}],
}
@ -75,7 +111,7 @@ async def test_step_user(hass):
}
with patch(
"homeassistant.components.wwlln.async_setup_entry", return_value=True
"homeassistant.components.airvisual.async_setup_entry", return_value=True
), patch("pyairvisual.api.API.nearest_city"):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=conf