From 60ecc8282c8834bc942f14e54d20af7d2b9504f3 Mon Sep 17 00:00:00 2001
From: Aaron Bach <bachya1208@gmail.com>
Date: Sat, 19 Dec 2020 10:29:37 -0700
Subject: [PATCH] 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
---
 .../components/recollect_waste/__init__.py    | 15 +++++++-
 .../components/recollect_waste/config_flow.py | 37 +++++++++++++++++++
 .../components/recollect_waste/sensor.py      | 35 +++++++++++++-----
 .../components/recollect_waste/strings.json   | 10 +++++
 .../recollect_waste/translations/en.json      | 10 +++++
 .../recollect_waste/test_config_flow.py       | 25 +++++++++++++
 6 files changed, 121 insertions(+), 11 deletions(-)

diff --git a/homeassistant/components/recollect_waste/__init__.py b/homeassistant/components/recollect_waste/__init__.py
index 10d39c1fc40..0600d73d8a1 100644
--- a/homeassistant/components/recollect_waste/__init__.py
+++ b/homeassistant/components/recollect_waste/__init__.py
@@ -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
diff --git a/homeassistant/components/recollect_waste/config_flow.py b/homeassistant/components/recollect_waste/config_flow.py
index 402c143706e..8e208f57cc6 100644
--- a/homeassistant/components/recollect_waste/config_flow.py
+++ b/homeassistant/components/recollect_waste/config_flow.py
@@ -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
+                }
+            ),
+        )
diff --git a/homeassistant/components/recollect_waste/sensor.py b/homeassistant/components/recollect_waste/sensor.py
index 405d66989bb..d66c2aae0e4 100644
--- a/homeassistant/components/recollect_waste/sensor.py
+++ b/homeassistant/components/recollect_waste/sensor.py
@@ -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,
             }
         )
diff --git a/homeassistant/components/recollect_waste/strings.json b/homeassistant/components/recollect_waste/strings.json
index 0cd251c737b..a350b9880fc 100644
--- a/homeassistant/components/recollect_waste/strings.json
+++ b/homeassistant/components/recollect_waste/strings.json
@@ -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)"
+        }
+      }
+    }
   }
 }
diff --git a/homeassistant/components/recollect_waste/translations/en.json b/homeassistant/components/recollect_waste/translations/en.json
index 28d73d189b8..e9deabec71b 100644
--- a/homeassistant/components/recollect_waste/translations/en.json
+++ b/homeassistant/components/recollect_waste/translations/en.json
@@ -14,5 +14,15 @@
                 }
             }
         }
+    },
+    "options": {
+        "step": {
+            "init": {
+                "data": {
+                    "friendly_name": "Use friendly names for pickup types (when possible)"
+                },
+                "title": "Configure Recollect Waste"
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/tests/components/recollect_waste/test_config_flow.py b/tests/components/recollect_waste/test_config_flow.py
index 1f17e1f60d2..ca3fbe8be56 100644
--- a/tests/components/recollect_waste/test_config_flow.py
+++ b/tests/components/recollect_waste/test_config_flow.py
@@ -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(