Add options flow for Recollect Waste (#44234)

* Add options flow for Recollect Waste

* Add test

* Typing

* Typing

* Typing AGAIN

* Add missing type hints

* Code review

* Code review

* Don't need to block until done
pull/37800/head
Aaron Bach 2020-12-19 10:29:37 -07:00 committed by GitHub
parent fbc695e5cf
commit 60ecc8282c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 121 additions and 11 deletions

View File

@ -14,6 +14,8 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import CONF_PLACE_ID, CONF_SERVICE_ID, DATA_COORDINATOR, DOMAIN, LOGGER
DATA_LISTENER = "listener"
DEFAULT_NAME = "recollect_waste"
DEFAULT_UPDATE_INTERVAL = timedelta(days=1)
@ -22,7 +24,7 @@ PLATFORMS = ["sensor"]
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
"""Set up the RainMachine component."""
hass.data[DOMAIN] = {DATA_COORDINATOR: {}}
hass.data[DOMAIN] = {DATA_COORDINATOR: {}, DATA_LISTENER: {}}
return True
@ -64,9 +66,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.config_entries.async_forward_entry_setup(entry, component)
)
hass.data[DOMAIN][DATA_LISTENER][entry.entry_id] = entry.add_update_listener(
async_reload_entry
)
return True
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Handle an options update."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload an RainMachine config entry."""
unload_ok = all(
@ -79,5 +90,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
if unload_ok:
hass.data[DOMAIN][DATA_COORDINATOR].pop(entry.entry_id)
cancel_listener = hass.data[DOMAIN][DATA_LISTENER].pop(entry.entry_id)
cancel_listener()
return unload_ok

View File

@ -1,9 +1,13 @@
"""Config flow for ReCollect Waste integration."""
from typing import Optional
from aiorecollect.client import Client
from aiorecollect.errors import RecollectError
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_FRIENDLY_NAME
from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client
from .const import ( # pylint:disable=unused-import
@ -24,6 +28,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
@staticmethod
@callback
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> config_entries.OptionsFlow:
"""Define the config flow to handle options."""
return RecollectWasteOptionsFlowHandler(config_entry)
async def async_step_import(self, import_config: dict = None) -> dict:
"""Handle configuration via YAML import."""
return await self.async_step_user(import_config)
@ -62,3 +74,28 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
CONF_SERVICE_ID: user_input[CONF_SERVICE_ID],
},
)
class RecollectWasteOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle a Recollect Waste options flow."""
def __init__(self, entry: config_entries.ConfigEntry):
"""Initialize."""
self._entry = entry
async def async_step_init(self, user_input: Optional[dict] = 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.Optional(
CONF_FRIENDLY_NAME,
default=self._entry.options.get(CONF_FRIENDLY_NAME),
): bool
}
),
)

View File

@ -1,11 +1,12 @@
"""Support for ReCollect Waste sensors."""
from typing import Callable
from typing import Callable, List
from aiorecollect.client import PickupType
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.const import ATTR_ATTRIBUTION, CONF_FRIENDLY_NAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.update_coordinator import (
@ -35,13 +36,26 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
)
@callback
def async_get_pickup_type_names(
entry: ConfigEntry, pickup_types: List[PickupType]
) -> List[str]:
"""Return proper pickup type names from their associated objects."""
return [
t.friendly_name
if entry.options.get(CONF_FRIENDLY_NAME) and t.friendly_name
else t.name
for t in pickup_types
]
async def async_setup_platform(
hass: HomeAssistant,
config: dict,
async_add_entities: Callable,
discovery_info: dict = None,
):
"""Import Awair configuration from YAML."""
"""Import Recollect Waste configuration from YAML."""
LOGGER.warning(
"Loading ReCollect Waste via platform setup is deprecated. "
"Please remove it from your configuration."
@ -70,8 +84,7 @@ class ReCollectWasteSensor(CoordinatorEntity):
"""Initialize the sensor."""
super().__init__(coordinator)
self._attributes = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
self._place_id = entry.data[CONF_PLACE_ID]
self._service_id = entry.data[CONF_SERVICE_ID]
self._entry = entry
self._state = None
@property
@ -97,7 +110,7 @@ class ReCollectWasteSensor(CoordinatorEntity):
@property
def unique_id(self) -> str:
"""Return a unique ID."""
return f"{self._place_id}{self._service_id}"
return f"{self._entry.data[CONF_PLACE_ID]}{self._entry.data[CONF_SERVICE_ID]}"
@callback
def _handle_coordinator_update(self) -> None:
@ -120,11 +133,13 @@ class ReCollectWasteSensor(CoordinatorEntity):
self._state = pickup_event.date
self._attributes.update(
{
ATTR_PICKUP_TYPES: [t.name for t in pickup_event.pickup_types],
ATTR_PICKUP_TYPES: async_get_pickup_type_names(
self._entry, pickup_event.pickup_types
),
ATTR_AREA_NAME: pickup_event.area_name,
ATTR_NEXT_PICKUP_TYPES: [
t.name for t in next_pickup_event.pickup_types
],
ATTR_NEXT_PICKUP_TYPES: async_get_pickup_type_names(
self._entry, next_pickup_event.pickup_types
),
ATTR_NEXT_PICKUP_DATE: next_date,
}
)

View File

@ -14,5 +14,15 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"options": {
"step": {
"init": {
"title": "Configure Recollect Waste",
"data": {
"friendly_name": "Use friendly names for pickup types (when possible)"
}
}
}
}
}

View File

@ -14,5 +14,15 @@
}
}
}
},
"options": {
"step": {
"init": {
"data": {
"friendly_name": "Use friendly names for pickup types (when possible)"
},
"title": "Configure Recollect Waste"
}
}
}
}

View File

@ -8,6 +8,7 @@ from homeassistant.components.recollect_waste import (
DOMAIN,
)
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
from homeassistant.const import CONF_FRIENDLY_NAME
from tests.async_mock import patch
from tests.common import MockConfigEntry
@ -45,6 +46,30 @@ async def test_invalid_place_or_service_id(hass):
assert result["errors"] == {"base": "invalid_place_or_service_id"}
async def test_options_flow(hass):
"""Test config flow options."""
conf = {CONF_PLACE_ID: "12345", CONF_SERVICE_ID: "12345"}
config_entry = MockConfigEntry(domain=DOMAIN, unique_id="12345, 12345", data=conf)
config_entry.add_to_hass(hass)
with patch(
"homeassistant.components.recollect_waste.async_setup_entry", return_value=True
):
await hass.config_entries.async_setup(config_entry.entry_id)
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_FRIENDLY_NAME: True}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert config_entry.options == {CONF_FRIENDLY_NAME: True}
async def test_show_form(hass):
"""Test that the form is served with no input."""
result = await hass.config_entries.flow.async_init(