mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 17:57:11 +00:00
Add support for ignoring zwave_js device config file changes (#108990)
* Add support for ignoring zwave_js device config file changes * mistake * fixes * Small tweaks and add/update tests
This commit is contained in:
parent
dcb5c0d439
commit
821d273e4d
@ -1,12 +1,12 @@
|
|||||||
"""Repairs for Z-Wave JS."""
|
"""Repairs for Z-Wave JS."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from homeassistant import data_entry_flow
|
from homeassistant import data_entry_flow
|
||||||
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
|
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
|
||||||
from homeassistant.core import HomeAssistant
|
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
|
from .helpers import async_get_node_from_device_id
|
||||||
|
|
||||||
|
|
||||||
@ -15,34 +15,44 @@ class DeviceConfigFileChangedFlow(RepairsFlow):
|
|||||||
|
|
||||||
def __init__(self, data: dict[str, str]) -> None:
|
def __init__(self, data: dict[str, str]) -> None:
|
||||||
"""Initialize."""
|
"""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"]
|
self.device_id: str = data["device_id"]
|
||||||
|
|
||||||
async def async_step_init(
|
async def async_step_init(
|
||||||
self, user_input: dict[str, str] | None = None
|
self, user_input: dict[str, str] | None = None
|
||||||
) -> data_entry_flow.FlowResult:
|
) -> data_entry_flow.FlowResult:
|
||||||
"""Handle the first step of a fix flow."""
|
"""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(
|
async def async_step_confirm(
|
||||||
self, user_input: dict[str, str] | None = None
|
self, user_input: dict[str, str] | None = None
|
||||||
) -> data_entry_flow.FlowResult:
|
) -> data_entry_flow.FlowResult:
|
||||||
"""Handle the confirm step of a fix flow."""
|
"""Handle the confirm step of a fix flow."""
|
||||||
if user_input is not None:
|
try:
|
||||||
try:
|
node = async_get_node_from_device_id(self.hass, self.device_id)
|
||||||
node = async_get_node_from_device_id(self.hass, self.device_id)
|
except ValueError:
|
||||||
except ValueError:
|
return self.async_abort(
|
||||||
return self.async_abort(
|
reason="cannot_connect",
|
||||||
reason="cannot_connect",
|
description_placeholders=self.description_placeholders,
|
||||||
description_placeholders={"device_name": self.device_name},
|
)
|
||||||
)
|
self.hass.async_create_task(node.async_refresh_info())
|
||||||
self.hass.async_create_task(node.async_refresh_info())
|
return self.async_create_entry(title="", data={})
|
||||||
return self.async_create_entry(title="", data={})
|
|
||||||
|
|
||||||
return self.async_show_form(
|
async def async_step_ignore(
|
||||||
step_id="confirm",
|
self, user_input: dict[str, str] | None = None
|
||||||
data_schema=vol.Schema({}),
|
) -> data_entry_flow.FlowResult:
|
||||||
description_placeholders={"device_name": self.device_name},
|
"""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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -155,13 +155,18 @@
|
|||||||
"title": "Device configuration file changed: {device_name}",
|
"title": "Device configuration file changed: {device_name}",
|
||||||
"fix_flow": {
|
"fix_flow": {
|
||||||
"step": {
|
"step": {
|
||||||
"confirm": {
|
"init": {
|
||||||
|
"menu_options": {
|
||||||
|
"confirm": "Re-interview device",
|
||||||
|
"ignore": "Ignore device config update"
|
||||||
|
},
|
||||||
"title": "Device configuration file changed: {device_name}",
|
"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": {
|
"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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ async def _trigger_repair_issue(
|
|||||||
return node
|
return node
|
||||||
|
|
||||||
|
|
||||||
async def test_device_config_file_changed(
|
async def test_device_config_file_changed_confirm_step(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
hass_client: ClientSessionGenerator,
|
hass_client: ClientSessionGenerator,
|
||||||
hass_ws_client: WebSocketGenerator,
|
hass_ws_client: WebSocketGenerator,
|
||||||
@ -58,7 +58,7 @@ async def test_device_config_file_changed(
|
|||||||
multisensor_6_state,
|
multisensor_6_state,
|
||||||
integration,
|
integration,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the device_config_file_changed issue."""
|
"""Test the device_config_file_changed issue confirm step."""
|
||||||
dev_reg = dr.async_get(hass)
|
dev_reg = dr.async_get(hass)
|
||||||
node = await _trigger_repair_issue(hass, client, multisensor_6_state)
|
node = await _trigger_repair_issue(hass, client, multisensor_6_state)
|
||||||
|
|
||||||
@ -87,16 +87,25 @@ async def test_device_config_file_changed(
|
|||||||
data = await resp.json()
|
data = await resp.json()
|
||||||
|
|
||||||
flow_id = data["flow_id"]
|
flow_id = data["flow_id"]
|
||||||
assert data["step_id"] == "confirm"
|
assert data["step_id"] == "init"
|
||||||
assert data["description_placeholders"] == {"device_name": device.name}
|
assert data["description_placeholders"] == {"device_name": device.name}
|
||||||
|
|
||||||
# Apply fix
|
|
||||||
url = RepairsFlowResourceView.url.format(flow_id=flow_id)
|
url = RepairsFlowResourceView.url.format(flow_id=flow_id)
|
||||||
|
|
||||||
|
# Show menu
|
||||||
resp = await http_client.post(url)
|
resp = await http_client.post(url)
|
||||||
|
|
||||||
assert resp.status == HTTPStatus.OK
|
assert resp.status == HTTPStatus.OK
|
||||||
data = await resp.json()
|
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"
|
assert data["type"] == "create_entry"
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -114,6 +123,78 @@ async def test_device_config_file_changed(
|
|||||||
assert len(msg["result"]["issues"]) == 0
|
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(
|
async def test_invalid_issue(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
hass_client: ClientSessionGenerator,
|
hass_client: ClientSessionGenerator,
|
||||||
@ -196,14 +277,14 @@ async def test_abort_confirm(
|
|||||||
data = await resp.json()
|
data = await resp.json()
|
||||||
|
|
||||||
flow_id = data["flow_id"]
|
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
|
# Unload config entry so we can't connect to the node
|
||||||
await hass.config_entries.async_unload(integration.entry_id)
|
await hass.config_entries.async_unload(integration.entry_id)
|
||||||
|
|
||||||
# Apply fix
|
# Apply fix
|
||||||
url = RepairsFlowResourceView.url.format(flow_id=flow_id)
|
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
|
assert resp.status == HTTPStatus.OK
|
||||||
data = await resp.json()
|
data = await resp.json()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user