From c64c1c8f080a1b56dbd9341d5c04986d11013e07 Mon Sep 17 00:00:00 2001
From: G Johansson <goran.johansson@shiftit.se>
Date: Tue, 19 Dec 2023 12:33:05 +0100
Subject: [PATCH] Workday create repair if named holiday missing (#101201)

---
 .../components/workday/binary_sensor.py       | 22 ++++-
 homeassistant/components/workday/repairs.py   | 77 +++++++++++++++++-
 homeassistant/components/workday/strings.json | 20 +++++
 tests/components/workday/test_repairs.py      | 80 ++++++++++++++++++-
 4 files changed, 196 insertions(+), 3 deletions(-)

diff --git a/homeassistant/components/workday/binary_sensor.py b/homeassistant/components/workday/binary_sensor.py
index e2369baade5..bda3a576563 100644
--- a/homeassistant/components/workday/binary_sensor.py
+++ b/homeassistant/components/workday/binary_sensor.py
@@ -21,7 +21,8 @@ from homeassistant.helpers.entity_platform import (
     AddEntitiesCallback,
     async_get_current_platform,
 )
-from homeassistant.util import dt as dt_util
+from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
+from homeassistant.util import dt as dt_util, slugify
 
 from .const import (
     ALLOWED_DAYS,
@@ -122,6 +123,25 @@ async def async_setup_entry(
                     LOGGER.debug("Removed %s by name '%s'", holiday, remove_holiday)
         except KeyError as unmatched:
             LOGGER.warning("No holiday found matching %s", unmatched)
+            async_create_issue(
+                hass,
+                DOMAIN,
+                f"bad_named_holiday-{entry.entry_id}-{slugify(remove_holiday)}",
+                is_fixable=True,
+                is_persistent=False,
+                severity=IssueSeverity.WARNING,
+                translation_key="bad_named_holiday",
+                translation_placeholders={
+                    CONF_COUNTRY: country if country else "-",
+                    "title": entry.title,
+                    CONF_REMOVE_HOLIDAYS: remove_holiday,
+                },
+                data={
+                    "entry_id": entry.entry_id,
+                    "country": country,
+                    "named_holiday": remove_holiday,
+                },
+            )
 
     LOGGER.debug("Found the following holidays for your configuration:")
     for holiday_date, name in sorted(obj_holidays.items()):
diff --git a/homeassistant/components/workday/repairs.py b/homeassistant/components/workday/repairs.py
index fbed179763e..905434f76ac 100644
--- a/homeassistant/components/workday/repairs.py
+++ b/homeassistant/components/workday/repairs.py
@@ -18,7 +18,8 @@ from homeassistant.helpers.selector import (
     SelectSelectorMode,
 )
 
-from .const import CONF_PROVINCE
+from .config_flow import validate_custom_dates
+from .const import CONF_PROVINCE, CONF_REMOVE_HOLIDAYS
 
 
 class CountryFixFlow(RepairsFlow):
@@ -108,6 +109,76 @@ class CountryFixFlow(RepairsFlow):
         )
 
 
+class HolidayFixFlow(RepairsFlow):
+    """Handler for an issue fixing flow."""
+
+    def __init__(
+        self, entry: ConfigEntry, country: str | None, named_holiday: str
+    ) -> None:
+        """Create flow."""
+        self.entry = entry
+        self.country: str | None = country
+        self.named_holiday: str = named_holiday
+        super().__init__()
+
+    async def async_step_init(
+        self, user_input: dict[str, str] | None = None
+    ) -> data_entry_flow.FlowResult:
+        """Handle the first step of a fix flow."""
+        return await self.async_step_named_holiday()
+
+    async def async_step_named_holiday(
+        self, user_input: dict[str, Any] | None = None
+    ) -> data_entry_flow.FlowResult:
+        """Handle the options step of a fix flow."""
+        errors: dict[str, str] = {}
+        if user_input:
+            options = dict(self.entry.options)
+            new_options = {**options, **user_input}
+            try:
+                await self.hass.async_add_executor_job(
+                    validate_custom_dates, new_options
+                )
+            except Exception:  # pylint: disable=broad-except
+                errors["remove_holidays"] = "remove_holiday_error"
+            else:
+                self.hass.config_entries.async_update_entry(
+                    self.entry, options=new_options
+                )
+                await self.hass.config_entries.async_reload(self.entry.entry_id)
+                return self.async_create_entry(data={})
+
+        remove_holidays = self.entry.options[CONF_REMOVE_HOLIDAYS]
+        removed_named_holiday = [
+            value for value in remove_holidays if value != self.named_holiday
+        ]
+        new_schema = self.add_suggested_values_to_schema(
+            vol.Schema(
+                {
+                    vol.Optional(CONF_REMOVE_HOLIDAYS, default=[]): SelectSelector(
+                        SelectSelectorConfig(
+                            options=[],
+                            multiple=True,
+                            custom_value=True,
+                            mode=SelectSelectorMode.DROPDOWN,
+                        )
+                    ),
+                }
+            ),
+            {CONF_REMOVE_HOLIDAYS: removed_named_holiday},
+        )
+        return self.async_show_form(
+            step_id="named_holiday",
+            data_schema=new_schema,
+            description_placeholders={
+                CONF_COUNTRY: self.country if self.country else "-",
+                CONF_REMOVE_HOLIDAYS: self.named_holiday,
+                "title": self.entry.title,
+            },
+            errors=errors,
+        )
+
+
 async def async_create_fix_flow(
     hass: HomeAssistant,
     issue_id: str,
@@ -119,6 +190,10 @@ async def async_create_fix_flow(
         entry_id = cast(str, entry_id)
         entry = hass.config_entries.async_get_entry(entry_id)
 
+    if data and (holiday := data.get("named_holiday")) and entry:
+        # Bad named holiday in configuration
+        return HolidayFixFlow(entry, data.get("country"), holiday)
+
     if data and entry:
         # Country or province does not exist
         return CountryFixFlow(entry, data.get("country"))
diff --git a/homeassistant/components/workday/strings.json b/homeassistant/components/workday/strings.json
index 7e8439af5ea..bbb76676f96 100644
--- a/homeassistant/components/workday/strings.json
+++ b/homeassistant/components/workday/strings.json
@@ -132,6 +132,26 @@
           }
         }
       }
+    },
+    "bad_named_holiday": {
+      "title": "Configured named holiday {remove_holidays} for {title} does not exist",
+      "fix_flow": {
+        "step": {
+          "named_holiday": {
+            "title": "[%key:component::workday::issues::bad_named_holiday::title%]",
+            "description": "Remove named holiday `{remove_holidays}` as it can't be found in country {country}.",
+            "data": {
+              "remove_holidays": "[%key:component::workday::config::step::options::data::remove_holidays%]"
+            },
+            "data_description": {
+              "remove_holidays": "[%key:component::workday::config::step::options::data_description::remove_holidays%]"
+            }
+          }
+        },
+        "error": {
+          "remove_holiday_error": "[%key:component::workday::config::error::remove_holiday_error%]"
+        }
+      }
     }
   },
   "entity": {
diff --git a/tests/components/workday/test_repairs.py b/tests/components/workday/test_repairs.py
index d1920b7dc26..fc7bfeb1b0e 100644
--- a/tests/components/workday/test_repairs.py
+++ b/tests/components/workday/test_repairs.py
@@ -7,7 +7,7 @@ from homeassistant.components.repairs.websocket_api import (
     RepairsFlowIndexView,
     RepairsFlowResourceView,
 )
-from homeassistant.components.workday.const import DOMAIN
+from homeassistant.components.workday.const import CONF_REMOVE_HOLIDAYS, DOMAIN
 from homeassistant.const import CONF_COUNTRY
 from homeassistant.core import HomeAssistant
 from homeassistant.helpers.issue_registry import async_create_issue
@@ -16,6 +16,7 @@ from homeassistant.setup import async_setup_component
 from . import (
     TEST_CONFIG_INCORRECT_COUNTRY,
     TEST_CONFIG_INCORRECT_PROVINCE,
+    TEST_CONFIG_REMOVE_NAMED,
     init_integration,
 )
 
@@ -324,6 +325,83 @@ async def test_bad_province_none(
     assert not issue
 
 
+async def test_bad_named_holiday(
+    hass: HomeAssistant,
+    hass_client: ClientSessionGenerator,
+    hass_ws_client: WebSocketGenerator,
+) -> None:
+    """Test fixing bad province selecting none."""
+    assert await async_setup_component(hass, "repairs", {})
+    entry = await init_integration(hass, TEST_CONFIG_REMOVE_NAMED)
+
+    state = hass.states.get("binary_sensor.workday_sensor")
+    assert state
+
+    ws_client = await hass_ws_client(hass)
+    client = await hass_client()
+
+    await ws_client.send_json({"id": 1, "type": "repairs/list_issues"})
+    msg = await ws_client.receive_json()
+
+    assert msg["success"]
+    assert len(msg["result"]["issues"]) > 0
+    issue = None
+    for i in msg["result"]["issues"]:
+        if i["issue_id"] == "bad_named_holiday-1-not_a_holiday":
+            issue = i
+    assert issue is not None
+
+    url = RepairsFlowIndexView.url
+    resp = await client.post(
+        url,
+        json={"handler": DOMAIN, "issue_id": "bad_named_holiday-1-not_a_holiday"},
+    )
+    assert resp.status == HTTPStatus.OK
+    data = await resp.json()
+
+    flow_id = data["flow_id"]
+    assert data["description_placeholders"] == {
+        CONF_COUNTRY: "US",
+        CONF_REMOVE_HOLIDAYS: "Not a Holiday",
+        "title": entry.title,
+    }
+    assert data["step_id"] == "named_holiday"
+
+    url = RepairsFlowResourceView.url.format(flow_id=flow_id)
+    resp = await client.post(
+        url, json={"remove_holidays": ["Christmas", "Not exist 2"]}
+    )
+    assert resp.status == HTTPStatus.OK
+    data = await resp.json()
+
+    assert data["errors"] == {
+        CONF_REMOVE_HOLIDAYS: "remove_holiday_error",
+    }
+
+    url = RepairsFlowResourceView.url.format(flow_id=flow_id)
+    resp = await client.post(
+        url, json={"remove_holidays": ["Christmas", "Thanksgiving"]}
+    )
+    assert resp.status == HTTPStatus.OK
+    data = await resp.json()
+
+    assert data["type"] == "create_entry"
+    await hass.async_block_till_done()
+
+    state = hass.states.get("binary_sensor.workday_sensor")
+    assert state
+
+    await ws_client.send_json({"id": 2, "type": "repairs/list_issues"})
+    msg = await ws_client.receive_json()
+
+    assert msg["success"]
+    issue = None
+    for i in msg["result"]["issues"]:
+        if i["issue_id"] == "bad_named_holiday-1-not_a_holiday":
+            issue = i
+    assert not issue
+
+
 async def test_other_fixable_issues(
     hass: HomeAssistant,
     hass_client: ClientSessionGenerator,
-- 
GitLab