mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Add availability template to template helper config flow (#147623)
Co-authored-by: Norbert Rittel <norbert@rittel.de> Co-authored-by: Joostlek <joostlek@outlook.com> Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
parent
3f42911af4
commit
b85ec55abb
@ -30,6 +30,7 @@ from homeassistant.const import (
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.data_entry_flow import section
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er, selector
|
||||
from homeassistant.helpers.schema_config_entry_flow import (
|
||||
@ -53,7 +54,14 @@ from .alarm_control_panel import (
|
||||
async_create_preview_alarm_control_panel,
|
||||
)
|
||||
from .binary_sensor import async_create_preview_binary_sensor
|
||||
from .const import CONF_PRESS, CONF_TURN_OFF, CONF_TURN_ON, DOMAIN
|
||||
from .const import (
|
||||
CONF_ADVANCED_OPTIONS,
|
||||
CONF_AVAILABILITY,
|
||||
CONF_PRESS,
|
||||
CONF_TURN_OFF,
|
||||
CONF_TURN_ON,
|
||||
DOMAIN,
|
||||
)
|
||||
from .number import (
|
||||
CONF_MAX,
|
||||
CONF_MIN,
|
||||
@ -214,7 +222,17 @@ def generate_schema(domain: str, flow_type: str) -> vol.Schema:
|
||||
vol.Optional(CONF_TURN_OFF): selector.ActionSelector(),
|
||||
}
|
||||
|
||||
schema[vol.Optional(CONF_DEVICE_ID)] = selector.DeviceSelector()
|
||||
schema |= {
|
||||
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
|
||||
vol.Optional(CONF_ADVANCED_OPTIONS): section(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_AVAILABILITY): selector.TemplateSelector(),
|
||||
}
|
||||
),
|
||||
{"collapsed": True},
|
||||
),
|
||||
}
|
||||
|
||||
return vol.Schema(schema)
|
||||
|
||||
@ -530,7 +548,11 @@ def ws_start_preview(
|
||||
)
|
||||
return
|
||||
|
||||
preview_entity = CREATE_PREVIEW_ENTITY[template_type](hass, name, msg["user_input"])
|
||||
config: dict = msg["user_input"]
|
||||
advanced_options = config.pop(CONF_ADVANCED_OPTIONS, {})
|
||||
preview_entity = CREATE_PREVIEW_ENTITY[template_type](
|
||||
hass, name, {**config, **advanced_options}
|
||||
)
|
||||
preview_entity.hass = hass
|
||||
preview_entity.registry_entry = entity_registry_entry
|
||||
|
||||
|
@ -6,6 +6,7 @@ from homeassistant.const import CONF_ICON, CONF_NAME, CONF_UNIQUE_ID, Platform
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
CONF_ADVANCED_OPTIONS = "advanced_options"
|
||||
CONF_ATTRIBUTE_TEMPLATES = "attribute_templates"
|
||||
CONF_ATTRIBUTES = "attributes"
|
||||
CONF_AVAILABILITY = "availability"
|
||||
|
@ -33,6 +33,7 @@ from homeassistant.helpers.singleton import singleton
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import (
|
||||
CONF_ADVANCED_OPTIONS,
|
||||
CONF_ATTRIBUTE_TEMPLATES,
|
||||
CONF_ATTRIBUTES,
|
||||
CONF_AVAILABILITY,
|
||||
@ -248,6 +249,9 @@ async def async_setup_template_entry(
|
||||
options = dict(config_entry.options)
|
||||
options.pop("template_type")
|
||||
|
||||
if advanced_options := options.pop(CONF_ADVANCED_OPTIONS, None):
|
||||
options = {**options, **advanced_options}
|
||||
|
||||
if replace_value_template and CONF_VALUE_TEMPLATE in options:
|
||||
options[CONF_STATE] = options.pop(CONF_VALUE_TEMPLATE)
|
||||
|
||||
|
@ -19,6 +19,14 @@
|
||||
"data_description": {
|
||||
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"name": "[%key:component::template::config::step::sensor::sections::advanced_options::name%]",
|
||||
"data": {
|
||||
"availability": "[%key:component::template::config::step::sensor::sections::advanced_options::data::availability%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Template alarm control panel"
|
||||
},
|
||||
"binary_sensor": {
|
||||
@ -31,6 +39,14 @@
|
||||
"data_description": {
|
||||
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"name": "[%key:component::template::config::step::sensor::sections::advanced_options::name%]",
|
||||
"data": {
|
||||
"availability": "[%key:component::template::config::step::sensor::sections::advanced_options::data::availability%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Template binary sensor"
|
||||
},
|
||||
"button": {
|
||||
@ -43,6 +59,14 @@
|
||||
"data_description": {
|
||||
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"name": "[%key:component::template::config::step::sensor::sections::advanced_options::name%]",
|
||||
"data": {
|
||||
"availability": "[%key:component::template::config::step::sensor::sections::advanced_options::data::availability%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Template button"
|
||||
},
|
||||
"image": {
|
||||
@ -55,6 +79,14 @@
|
||||
"data_description": {
|
||||
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"name": "[%key:component::template::config::step::sensor::sections::advanced_options::name%]",
|
||||
"data": {
|
||||
"availability": "[%key:component::template::config::step::sensor::sections::advanced_options::data::availability%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Template image"
|
||||
},
|
||||
"number": {
|
||||
@ -71,6 +103,14 @@
|
||||
"data_description": {
|
||||
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"name": "[%key:component::template::config::step::sensor::sections::advanced_options::name%]",
|
||||
"data": {
|
||||
"availability": "[%key:component::template::config::step::sensor::sections::advanced_options::data::availability%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Template number"
|
||||
},
|
||||
"select": {
|
||||
@ -84,6 +124,14 @@
|
||||
"data_description": {
|
||||
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"name": "[%key:component::template::config::step::sensor::sections::advanced_options::name%]",
|
||||
"data": {
|
||||
"availability": "[%key:component::template::config::step::sensor::sections::advanced_options::data::availability%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Template select"
|
||||
},
|
||||
"sensor": {
|
||||
@ -98,6 +146,14 @@
|
||||
"data_description": {
|
||||
"device_id": "Select a device to link to this entity."
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"name": "Advanced options",
|
||||
"data": {
|
||||
"availability": "Availability template"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Template sensor"
|
||||
},
|
||||
"user": {
|
||||
@ -126,6 +182,14 @@
|
||||
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]",
|
||||
"value_template": "Defines a template to set the state of the switch. If not defined, the switch will optimistically assume all commands are successful."
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"name": "[%key:component::template::config::step::sensor::sections::advanced_options::name%]",
|
||||
"data": {
|
||||
"availability": "[%key:component::template::config::step::sensor::sections::advanced_options::data::availability%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Template switch"
|
||||
}
|
||||
}
|
||||
@ -149,6 +213,14 @@
|
||||
"data_description": {
|
||||
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"name": "[%key:component::template::config::step::sensor::sections::advanced_options::name%]",
|
||||
"data": {
|
||||
"availability": "[%key:component::template::config::step::sensor::sections::advanced_options::data::availability%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "[%key:component::template::config::step::alarm_control_panel::title%]"
|
||||
},
|
||||
"binary_sensor": {
|
||||
@ -159,6 +231,14 @@
|
||||
"data_description": {
|
||||
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"name": "[%key:component::template::config::step::sensor::sections::advanced_options::name%]",
|
||||
"data": {
|
||||
"availability": "[%key:component::template::config::step::sensor::sections::advanced_options::data::availability%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "[%key:component::template::config::step::binary_sensor::title%]"
|
||||
},
|
||||
"button": {
|
||||
@ -169,6 +249,14 @@
|
||||
"data_description": {
|
||||
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"name": "[%key:component::template::config::step::sensor::sections::advanced_options::name%]",
|
||||
"data": {
|
||||
"availability": "[%key:component::template::config::step::sensor::sections::advanced_options::data::availability%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "[%key:component::template::config::step::button::title%]"
|
||||
},
|
||||
"image": {
|
||||
@ -180,6 +268,14 @@
|
||||
"data_description": {
|
||||
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"name": "[%key:component::template::config::step::sensor::sections::advanced_options::name%]",
|
||||
"data": {
|
||||
"availability": "[%key:component::template::config::step::sensor::sections::advanced_options::data::availability%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "[%key:component::template::config::step::image::title%]"
|
||||
},
|
||||
"number": {
|
||||
@ -195,6 +291,14 @@
|
||||
"data_description": {
|
||||
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"name": "[%key:component::template::config::step::sensor::sections::advanced_options::name%]",
|
||||
"data": {
|
||||
"availability": "[%key:component::template::config::step::sensor::sections::advanced_options::data::availability%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "[%key:component::template::config::step::number::title%]"
|
||||
},
|
||||
"select": {
|
||||
@ -208,6 +312,14 @@
|
||||
"data_description": {
|
||||
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"name": "[%key:component::template::config::step::sensor::sections::advanced_options::name%]",
|
||||
"data": {
|
||||
"availability": "[%key:component::template::config::step::sensor::sections::advanced_options::data::availability%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "[%key:component::template::config::step::select::title%]"
|
||||
},
|
||||
"sensor": {
|
||||
@ -221,6 +333,14 @@
|
||||
"data_description": {
|
||||
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"name": "[%key:component::template::config::step::sensor::sections::advanced_options::name%]",
|
||||
"data": {
|
||||
"availability": "[%key:component::template::config::step::sensor::sections::advanced_options::data::availability%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "[%key:component::template::config::step::sensor::title%]"
|
||||
},
|
||||
"switch": {
|
||||
@ -235,6 +355,14 @@
|
||||
"device_id": "[%key:component::template::config::step::sensor::data_description::device_id%]",
|
||||
"value_template": "[%key:component::template::config::step::switch::data_description::value_template%]"
|
||||
},
|
||||
"sections": {
|
||||
"advanced_options": {
|
||||
"name": "[%key:component::template::config::step::sensor::sections::advanced_options::name%]",
|
||||
"data": {
|
||||
"availability": "[%key:component::template::config::step::sensor::sections::advanced_options::data::availability%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "[%key:component::template::config::step::switch::title%]"
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ from pytest_unordered import unordered
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.template import DOMAIN, async_setup_entry
|
||||
from homeassistant.const import STATE_UNAVAILABLE
|
||||
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
@ -217,16 +217,14 @@ async def test_config_flow(
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == template_type
|
||||
|
||||
availability = {"advanced_options": {"availability": "{{ True }}"}}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.template.async_setup_entry", wraps=async_setup_entry
|
||||
) as mock_setup_entry:
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
"name": "My template",
|
||||
**state_template,
|
||||
**extra_input,
|
||||
},
|
||||
{"name": "My template", **state_template, **extra_input, **availability},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -238,6 +236,7 @@ async def test_config_flow(
|
||||
"template_type": template_type,
|
||||
**state_template,
|
||||
**extra_options,
|
||||
**availability,
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
@ -248,6 +247,7 @@ async def test_config_flow(
|
||||
"template_type": template_type,
|
||||
**state_template,
|
||||
**extra_options,
|
||||
**availability,
|
||||
}
|
||||
|
||||
state = hass.states.get(f"{template_type}.my_template")
|
||||
@ -675,7 +675,7 @@ async def test_options(
|
||||
"{{ float(states('sensor.one'), default='') + float(states('sensor.two'), default='') }}",
|
||||
{},
|
||||
{"one": "30.0", "two": "20.0"},
|
||||
["", STATE_UNAVAILABLE, "50.0"],
|
||||
["", STATE_UNKNOWN, "50.0"],
|
||||
[{}, {}],
|
||||
[["one", "two"], ["one", "two"]],
|
||||
),
|
||||
@ -695,6 +695,9 @@ async def test_config_flow_preview(
|
||||
"""Test the config flow preview."""
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
hass.states.async_set("binary_sensor.available", "on")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
input_entities = ["one", "two"]
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
@ -712,12 +715,22 @@ async def test_config_flow_preview(
|
||||
assert result["errors"] is None
|
||||
assert result["preview"] == "template"
|
||||
|
||||
availability = {
|
||||
"advanced_options": {
|
||||
"availability": "{{ is_state('binary_sensor.available', 'on') }}"
|
||||
}
|
||||
}
|
||||
|
||||
await client.send_json_auto_id(
|
||||
{
|
||||
"type": "template/start_preview",
|
||||
"flow_id": result["flow_id"],
|
||||
"flow_type": "config_flow",
|
||||
"user_input": {"name": "My template", "state": state_template}
|
||||
"user_input": {
|
||||
"name": "My template",
|
||||
"state": state_template,
|
||||
**availability,
|
||||
}
|
||||
| extra_user_input,
|
||||
}
|
||||
)
|
||||
@ -725,13 +738,16 @@ async def test_config_flow_preview(
|
||||
assert msg["success"]
|
||||
assert msg["result"] is None
|
||||
|
||||
entities = [f"{template_type}.{_id}" for _id in listeners[0]]
|
||||
entities.append("binary_sensor.available")
|
||||
|
||||
msg = await client.receive_json()
|
||||
assert msg["event"] == {
|
||||
"attributes": {"friendly_name": "My template"} | extra_attributes[0],
|
||||
"listeners": {
|
||||
"all": False,
|
||||
"domains": [],
|
||||
"entities": unordered([f"{template_type}.{_id}" for _id in listeners[0]]),
|
||||
"entities": unordered(entities),
|
||||
"time": False,
|
||||
},
|
||||
"state": template_states[0],
|
||||
@ -743,6 +759,9 @@ async def test_config_flow_preview(
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entities = [f"{template_type}.{_id}" for _id in listeners[1]]
|
||||
entities.append("binary_sensor.available")
|
||||
|
||||
for template_state in template_states[1:]:
|
||||
msg = await client.receive_json()
|
||||
assert msg["event"] == {
|
||||
@ -752,14 +771,32 @@ async def test_config_flow_preview(
|
||||
"listeners": {
|
||||
"all": False,
|
||||
"domains": [],
|
||||
"entities": unordered(
|
||||
[f"{template_type}.{_id}" for _id in listeners[1]]
|
||||
),
|
||||
"entities": unordered(entities),
|
||||
"time": False,
|
||||
},
|
||||
"state": template_state,
|
||||
}
|
||||
assert len(hass.states.async_all()) == 2
|
||||
assert len(hass.states.async_all()) == 3
|
||||
|
||||
# Test preview availability.
|
||||
hass.states.async_set("binary_sensor.available", "off")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
msg = await client.receive_json()
|
||||
assert msg["event"] == {
|
||||
"attributes": {"friendly_name": "My template"}
|
||||
| extra_attributes[0]
|
||||
| extra_attributes[1],
|
||||
"listeners": {
|
||||
"all": False,
|
||||
"domains": [],
|
||||
"entities": unordered(entities),
|
||||
"time": False,
|
||||
},
|
||||
"state": STATE_UNAVAILABLE,
|
||||
}
|
||||
|
||||
assert len(hass.states.async_all()) == 3
|
||||
|
||||
|
||||
EARLY_END_ERROR = "invalid template (TemplateSyntaxError: unexpected 'end of template')"
|
||||
|
Loading…
x
Reference in New Issue
Block a user