Support in service descriptions for input sections (#116100)

This commit is contained in:
Erik Montnemery 2024-06-25 20:00:48 +02:00 committed by GitHub
parent c4b277b6ab
commit 75c7ae7c69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 198 additions and 95 deletions

View file

@ -199,45 +199,6 @@ turn_on:
example: "[255, 100, 100]"
selector:
color_rgb:
rgbw_color: &rgbw_color
filter: *color_support
advanced: true
example: "[255, 100, 100, 50]"
selector:
object:
rgbww_color: &rgbww_color
filter: *color_support
advanced: true
example: "[255, 100, 100, 50, 70]"
selector:
object:
color_name: &color_name
filter: *color_support
advanced: true
selector:
select:
translation_key: color_name
options: *named_colors
hs_color: &hs_color
filter: *color_support
advanced: true
example: "[300, 70]"
selector:
object:
xy_color: &xy_color
filter: *color_support
advanced: true
example: "[0.52, 0.43]"
selector:
object:
color_temp: &color_temp
filter: *color_temp_support
advanced: true
selector:
color_temp:
unit: "mired"
min: 153
max: 500
kelvin: &kelvin
filter: *color_temp_support
selector:
@ -245,13 +206,6 @@ turn_on:
unit: "kelvin"
min: 2000
max: 6500
brightness: &brightness
filter: *brightness_support
advanced: true
selector:
number:
min: 0
max: 255
brightness_pct: &brightness_pct
filter: *brightness_support
selector:
@ -259,13 +213,6 @@ turn_on:
min: 0
max: 100
unit_of_measurement: "%"
brightness_step:
filter: *brightness_support
advanced: true
selector:
number:
min: -225
max: 255
brightness_step_pct:
filter: *brightness_support
selector:
@ -273,39 +220,84 @@ turn_on:
min: -100
max: 100
unit_of_measurement: "%"
white: &white
filter:
attribute:
supported_color_modes:
- light.ColorMode.WHITE
advanced: true
selector:
constant:
value: true
label: Enabled
profile: &profile
advanced: true
example: relax
selector:
text:
flash: &flash
filter:
supported_features:
- light.LightEntityFeature.FLASH
advanced: true
selector:
select:
options:
- label: "Long"
value: "long"
- label: "Short"
value: "short"
effect: &effect
filter:
supported_features:
- light.LightEntityFeature.EFFECT
selector:
text:
advanced_fields:
collapsed: true
fields:
rgbw_color: &rgbw_color
filter: *color_support
example: "[255, 100, 100, 50]"
selector:
object:
rgbww_color: &rgbww_color
filter: *color_support
example: "[255, 100, 100, 50, 70]"
selector:
object:
color_name: &color_name
filter: *color_support
selector:
select:
translation_key: color_name
options: *named_colors
hs_color: &hs_color
filter: *color_support
example: "[300, 70]"
selector:
object:
xy_color: &xy_color
filter: *color_support
example: "[0.52, 0.43]"
selector:
object:
color_temp: &color_temp
filter: *color_temp_support
selector:
color_temp:
unit: "mired"
min: 153
max: 500
brightness: &brightness
filter: *brightness_support
selector:
number:
min: 0
max: 255
brightness_step:
filter: *brightness_support
selector:
number:
min: -225
max: 255
white: &white
filter:
attribute:
supported_color_modes:
- light.ColorMode.WHITE
selector:
constant:
value: true
label: Enabled
profile: &profile
example: relax
selector:
text:
flash: &flash
filter:
supported_features:
- light.LightEntityFeature.FLASH
selector:
select:
options:
- label: "Long"
value: "long"
- label: "Short"
value: "short"
turn_off:
target:
@ -313,7 +305,10 @@ turn_off:
domain: light
fields:
transition: *transition
flash: *flash
advanced_fields:
collapsed: true
fields:
flash: *flash
toggle:
target:
@ -322,16 +317,19 @@ toggle:
fields:
transition: *transition
rgb_color: *rgb_color
rgbw_color: *rgbw_color
rgbww_color: *rgbww_color
color_name: *color_name
hs_color: *hs_color
xy_color: *xy_color
color_temp: *color_temp
kelvin: *kelvin
brightness: *brightness
brightness_pct: *brightness_pct
white: *white
profile: *profile
flash: *flash
effect: *effect
advanced_fields:
collapsed: true
fields:
rgbw_color: *rgbw_color
rgbww_color: *rgbww_color
color_name: *color_name
hs_color: *hs_color
xy_color: *xy_color
color_temp: *color_temp
brightness: *brightness
white: *white
profile: *profile
flash: *flash

View file

@ -34,7 +34,8 @@
"field_white_description": "Set the light to white mode.",
"field_white_name": "White",
"field_xy_color_description": "Color in XY-format. A list of two decimal numbers between 0 and 1.",
"field_xy_color_name": "XY-color"
"field_xy_color_name": "XY-color",
"section_advanced_fields_name": "Advanced options"
},
"device_automation": {
"action_type": {
@ -354,6 +355,11 @@
"name": "[%key:component::light::common::field_effect_name%]",
"description": "[%key:component::light::common::field_effect_description%]"
}
},
"sections": {
"advanced_fields": {
"name": "[%key:component::light::common::section_advanced_fields_name%]"
}
}
},
"turn_off": {
@ -368,6 +374,11 @@
"name": "[%key:component::light::common::field_flash_name%]",
"description": "[%key:component::light::common::field_flash_description%]"
}
},
"sections": {
"advanced_fields": {
"name": "[%key:component::light::common::section_advanced_fields_name%]"
}
}
},
"toggle": {
@ -434,6 +445,11 @@
"name": "[%key:component::light::common::field_effect_name%]",
"description": "[%key:component::light::common::field_effect_description%]"
}
},
"sections": {
"advanced_fields": {
"name": "[%key:component::light::common::section_advanced_fields_name%]"
}
}
}
}

View file

@ -179,10 +179,19 @@ _FIELD_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA,
)
_SECTION_SCHEMA = vol.Schema(
{
vol.Required("fields"): vol.Schema({str: _FIELD_SCHEMA}),
},
extra=vol.ALLOW_EXTRA,
)
_SERVICE_SCHEMA = vol.Schema(
{
vol.Optional("target"): vol.Any(TargetSelector.CONFIG_SCHEMA, None),
vol.Optional("fields"): vol.Schema({str: _FIELD_SCHEMA}),
vol.Optional("fields"): vol.Schema(
{str: vol.Any(_SECTION_SCHEMA, _FIELD_SCHEMA)}
),
},
extra=vol.ALLOW_EXTRA,
)

View file

@ -26,6 +26,23 @@ def exists(value: Any) -> Any:
return value
def unique_field_validator(fields: Any) -> Any:
"""Validate the inputs don't have duplicate keys under different sections."""
all_fields = set()
for key, value in fields.items():
if value and "fields" in value:
for key in value["fields"]:
if key in all_fields:
raise vol.Invalid(f"Duplicate use of field {key} in service.")
all_fields.add(key)
else:
if key in all_fields:
raise vol.Invalid(f"Duplicate use of field {key} in service.")
all_fields.add(key)
return fields
CORE_INTEGRATION_FIELD_SCHEMA = vol.Schema(
{
vol.Optional("example"): exists,
@ -44,6 +61,13 @@ CORE_INTEGRATION_FIELD_SCHEMA = vol.Schema(
}
)
CORE_INTEGRATION_SECTION_SCHEMA = vol.Schema(
{
vol.Optional("collapsed"): bool,
vol.Required("fields"): vol.Schema({str: CORE_INTEGRATION_FIELD_SCHEMA}),
}
)
CUSTOM_INTEGRATION_FIELD_SCHEMA = CORE_INTEGRATION_FIELD_SCHEMA.extend(
{
vol.Optional("description"): str,
@ -57,7 +81,17 @@ CORE_INTEGRATION_SERVICE_SCHEMA = vol.Any(
vol.Optional("target"): vol.Any(
selector.TargetSelector.CONFIG_SCHEMA, None
),
vol.Optional("fields"): vol.Schema({str: CORE_INTEGRATION_FIELD_SCHEMA}),
vol.Optional("fields"): vol.All(
vol.Schema(
{
str: vol.Any(
CORE_INTEGRATION_FIELD_SCHEMA,
CORE_INTEGRATION_SECTION_SCHEMA,
)
}
),
unique_field_validator,
),
}
),
None,
@ -107,7 +141,7 @@ def grep_dir(path: pathlib.Path, glob_pattern: str, search_pattern: str) -> bool
return False
def validate_services(config: Config, integration: Integration) -> None:
def validate_services(config: Config, integration: Integration) -> None: # noqa: C901
"""Validate services."""
try:
data = load_yaml_dict(str(integration.path / "services.yaml"))
@ -200,6 +234,9 @@ def validate_services(config: Config, integration: Integration) -> None:
# The same check is done for the description in each of the fields of the
# service schema.
for field_name, field_schema in service_schema.get("fields", {}).items():
if "fields" in field_schema:
# This is a section
continue
if "name" not in field_schema:
try:
strings["services"][service_name]["fields"][field_name]["name"]
@ -233,6 +270,20 @@ def validate_services(config: Config, integration: Integration) -> None:
f"Service {service_name} has a field {field_name} with a selector with a translation key {translation_key} that is not in the translations file",
)
# The same check is done for the description in each of the sections of the
# service schema.
for section_name, section_schema in service_schema.get("fields", {}).items():
if "fields" not in section_schema:
# This is not a section
continue
try:
strings["services"][service_name]["sections"][section_name]["name"]
except KeyError:
integration.add_error(
"services",
f"Service {service_name} has a section {section_name} with no name {error_msg_suffix}",
)
def validate(integrations: dict[str, Integration], config: Config) -> None:
"""Handle dependencies for integrations."""

View file

@ -383,6 +383,13 @@ def gen_strings_schema(config: Config, integration: Integration) -> vol.Schema:
},
slug_validator=translation_key_validator,
),
vol.Optional("sections"): cv.schema_with_slug_keys(
{
vol.Required("name"): str,
vol.Optional("description"): translation_value_validator,
},
slug_validator=translation_key_validator,
),
},
slug_validator=translation_key_validator,
),

View file

@ -990,6 +990,17 @@ async def test_async_get_all_descriptions_filter(hass: HomeAssistant) -> None:
- light.ColorMode.COLOR_TEMP
selector:
number:
advanced_stuff:
fields:
temperature:
filter:
supported_features:
- alarm_control_panel.AlarmControlPanelEntityFeature.ARM_HOME
attribute:
supported_color_modes:
- light.ColorMode.COLOR_TEMP
selector:
number:
"""
domain = "test_domain"
@ -1024,6 +1035,17 @@ async def test_async_get_all_descriptions_filter(hass: HomeAssistant) -> None:
test_service_schema = {
"description": "",
"fields": {
"advanced_stuff": {
"fields": {
"temperature": {
"filter": {
"attribute": {"supported_color_modes": ["color_temp"]},
"supported_features": [1],
},
"selector": {"number": None},
},
},
},
"temperature": {
"filter": {
"attribute": {"supported_color_modes": ["color_temp"]},