Skip to content

Commit

Permalink
Add support for ignoring zwave_js device config file changes (#108990)
Browse files Browse the repository at this point in the history
* Add support for ignoring zwave_js device config file changes

* mistake

* fixes

* Small tweaks and add/update tests
  • Loading branch information
raman325 authored Jan 30, 2024
1 parent dcb5c0d commit 821d273
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 28 deletions.
48 changes: 29 additions & 19 deletions homeassistant/components/zwave_js/repairs.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""Repairs for Z-Wave JS."""
from __future__ import annotations

import voluptuous as vol

from homeassistant import data_entry_flow
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir

from .const import DOMAIN
from .helpers import async_get_node_from_device_id


Expand All @@ -15,34 +15,44 @@ class DeviceConfigFileChangedFlow(RepairsFlow):

def __init__(self, data: dict[str, str]) -> None:
"""Initialize."""
self.device_name: str = data["device_name"]
self.description_placeholders: dict[str, str] = {
"device_name": data["device_name"]
}
self.device_id: str = data["device_id"]

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_confirm()
return self.async_show_menu(
menu_options=["confirm", "ignore"],
description_placeholders=self.description_placeholders,
)

async def async_step_confirm(
self, user_input: dict[str, str] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the confirm step of a fix flow."""
if user_input is not None:
try:
node = async_get_node_from_device_id(self.hass, self.device_id)
except ValueError:
return self.async_abort(
reason="cannot_connect",
description_placeholders={"device_name": self.device_name},
)
self.hass.async_create_task(node.async_refresh_info())
return self.async_create_entry(title="", data={})

return self.async_show_form(
step_id="confirm",
data_schema=vol.Schema({}),
description_placeholders={"device_name": self.device_name},
try:
node = async_get_node_from_device_id(self.hass, self.device_id)
except ValueError:
return self.async_abort(
reason="cannot_connect",
description_placeholders=self.description_placeholders,
)
self.hass.async_create_task(node.async_refresh_info())
return self.async_create_entry(title="", data={})

async def async_step_ignore(
self, user_input: dict[str, str] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the ignore step of a fix flow."""
ir.async_get(self.hass).async_ignore(
DOMAIN, f"device_config_file_changed.{self.device_id}", True
)
return self.async_abort(
reason="issue_ignored",
description_placeholders=self.description_placeholders,
)


Expand Down
11 changes: 8 additions & 3 deletions homeassistant/components/zwave_js/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,18 @@
"title": "Device configuration file changed: {device_name}",
"fix_flow": {
"step": {
"confirm": {
"init": {
"menu_options": {
"confirm": "Re-interview device",
"ignore": "Ignore device config update"
},
"title": "Device configuration file changed: {device_name}",
"description": "The device configuration file for {device_name} has changed.\n\nZ-Wave JS discovers a lot of device metadata by interviewing the device. However, some of the information has to be loaded from a configuration file. Some of this information is only evaluated once, during the device interview.\n\nWhen a device config file is updated, this information may be stale and and the device must be re-interviewed to pick up the changes.\n\n This is not a required operation and device functionality will be impacted during the re-interview process, but you may see improvements for your device once it is complete.\n\nIf you'd like to proceed, click on SUBMIT below. The re-interview will take place in the background."
"description": "The device configuration file for {device_name} has changed.\n\nZ-Wave JS discovers a lot of device metadata by interviewing the device. However, some of the information has to be loaded from a configuration file. Some of this information is only evaluated once, during the device interview.\n\nWhen a device config file is updated, this information may be stale and and the device must be re-interviewed to pick up the changes.\n\n This is not a required operation and device functionality will be impacted during the re-interview process, but you may see improvements for your device once it is complete.\n\nIf you decide to proceed with the re-interview, it will take place in the background."
}
},
"abort": {
"cannot_connect": "Cannot connect to {device_name}. Please try again later after confirming that your Z-Wave network is up and connected to Home Assistant."
"cannot_connect": "Cannot connect to {device_name}. Please try again later after confirming that your Z-Wave network is up and connected to Home Assistant.",
"issue_ignored": "Device config file update for {device_name} ignored."
}
}
}
Expand Down
93 changes: 87 additions & 6 deletions tests/components/zwave_js/test_repairs.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ async def _trigger_repair_issue(
return node


async def test_device_config_file_changed(
async def test_device_config_file_changed_confirm_step(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
hass_ws_client: WebSocketGenerator,
client,
multisensor_6_state,
integration,
) -> None:
"""Test the device_config_file_changed issue."""
"""Test the device_config_file_changed issue confirm step."""
dev_reg = dr.async_get(hass)
node = await _trigger_repair_issue(hass, client, multisensor_6_state)

Expand Down Expand Up @@ -87,16 +87,25 @@ async def test_device_config_file_changed(
data = await resp.json()

flow_id = data["flow_id"]
assert data["step_id"] == "confirm"
assert data["step_id"] == "init"
assert data["description_placeholders"] == {"device_name": device.name}

# Apply fix
url = RepairsFlowResourceView.url.format(flow_id=flow_id)

# Show menu
resp = await http_client.post(url)

assert resp.status == HTTPStatus.OK
data = await resp.json()

assert data["type"] == "menu"

# Apply fix
resp = await http_client.post(url, json={"next_step_id": "confirm"})

assert resp.status == HTTPStatus.OK
data = await resp.json()

assert data["type"] == "create_entry"

await hass.async_block_till_done()
Expand All @@ -114,6 +123,78 @@ async def test_device_config_file_changed(
assert len(msg["result"]["issues"]) == 0


async def test_device_config_file_changed_ignore_step(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
hass_ws_client: WebSocketGenerator,
client,
multisensor_6_state,
integration,
) -> None:
"""Test the device_config_file_changed issue ignore step."""
dev_reg = dr.async_get(hass)
node = await _trigger_repair_issue(hass, client, multisensor_6_state)

client.async_send_command_no_wait.reset_mock()

device = dev_reg.async_get_device(identifiers={get_device_id(client.driver, node)})
assert device
issue_id = f"device_config_file_changed.{device.id}"

await async_process_repairs_platforms(hass)
ws_client = await hass_ws_client(hass)
http_client = await hass_client()

# Assert the issue is present
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"]) == 1
issue = msg["result"]["issues"][0]
assert issue["issue_id"] == issue_id
assert issue["translation_placeholders"] == {"device_name": device.name}

url = RepairsFlowIndexView.url
resp = await http_client.post(url, json={"handler": DOMAIN, "issue_id": issue_id})
assert resp.status == HTTPStatus.OK
data = await resp.json()

flow_id = data["flow_id"]
assert data["step_id"] == "init"
assert data["description_placeholders"] == {"device_name": device.name}

url = RepairsFlowResourceView.url.format(flow_id=flow_id)

# Show menu
resp = await http_client.post(url)

assert resp.status == HTTPStatus.OK
data = await resp.json()

assert data["type"] == "menu"

# Ignore the issue
resp = await http_client.post(url, json={"next_step_id": "ignore"})

assert resp.status == HTTPStatus.OK
data = await resp.json()

assert data["type"] == "abort"
assert data["reason"] == "issue_ignored"
assert data["description_placeholders"] == {"device_name": device.name}

await hass.async_block_till_done()

assert len(client.async_send_command_no_wait.call_args_list) == 0

# Assert the issue still exists but is ignored
await ws_client.send_json({"id": 2, "type": "repairs/list_issues"})
msg = await ws_client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
assert msg["result"]["issues"][0].get("dismissed_version") is not None


async def test_invalid_issue(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
Expand Down Expand Up @@ -196,14 +277,14 @@ async def test_abort_confirm(
data = await resp.json()

flow_id = data["flow_id"]
assert data["step_id"] == "confirm"
assert data["step_id"] == "init"

# Unload config entry so we can't connect to the node
await hass.config_entries.async_unload(integration.entry_id)

# Apply fix
url = RepairsFlowResourceView.url.format(flow_id=flow_id)
resp = await http_client.post(url)
resp = await http_client.post(url, json={"next_step_id": "confirm"})

assert resp.status == HTTPStatus.OK
data = await resp.json()
Expand Down

0 comments on commit 821d273

Please sign in to comment.