From 21cff003f8602bfe4e2e88bb0a81c44737731064 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 10 Mar 2020 14:16:25 -0600 Subject: [PATCH] Add options flow for AirVisual (#32634) * Add options flow for AirVisual * Code review Co-Authored-By: Robert Svensson Co-authored-by: Robert Svensson --- .../airvisual/.translations/en.json | 14 +++++- .../components/airvisual/__init__.py | 16 ++++++- .../components/airvisual/config_flow.py | 40 +++++++++++++++- homeassistant/components/airvisual/sensor.py | 23 ++++++---- .../components/airvisual/strings.json | 14 +++++- .../components/airvisual/test_config_flow.py | 46 +++++++++++++++++-- 6 files changed, 132 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/airvisual/.translations/en.json b/homeassistant/components/airvisual/.translations/en.json index 844174220b1..2bcff29b770 100644 --- a/homeassistant/components/airvisual/.translations/en.json +++ b/homeassistant/components/airvisual/.translations/en.json @@ -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" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index 24d8257d041..a48acf7bb34 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -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) diff --git a/homeassistant/components/airvisual/config_flow.py b/homeassistant/components/airvisual/config_flow.py index bdd1d9a7b70..2f961ccfb49 100644 --- a/homeassistant/components/airvisual/config_flow.py +++ b/homeassistant/components/airvisual/config_flow.py @@ -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 + } + ), + ) diff --git a/homeassistant/components/airvisual/sensor.py b/homeassistant/components/airvisual/sensor.py index a25114b7f02..28d2b3f5f86 100644 --- a/homeassistant/components/airvisual/sensor.py +++ b/homeassistant/components/airvisual/sensor.py @@ -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: diff --git a/homeassistant/components/airvisual/strings.json b/homeassistant/components/airvisual/strings.json index 6c3f17c92dd..6e94c393da6 100644 --- a/homeassistant/components/airvisual/strings.json +++ b/homeassistant/components/airvisual/strings.json @@ -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" + } + } + } } } diff --git a/tests/components/airvisual/test_config_flow.py b/tests/components/airvisual/test_config_flow.py index 5057f1c3345..fb32a86a01a 100644 --- a/tests/components/airvisual/test_config_flow.py +++ b/tests/components/airvisual/test_config_flow.py @@ -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