Add support for JS modules in custom panels (#16096)

* Added backend support for JavaScript modules in custom panels.

* Fixed test_panel_custom.py

* Delete core.entity_registry

* Update panel_custom.py

* Corrected panel_custom.py with module_url.

* Rebase

* Missed elif

* Add vol.Exclusive module_url

* Correct vol.Exclusive usage

* Test for js module

* Corrected line continuation indentation

* Added webcomponent path to exclusive group

* Corrected line length

* Line break

* Test for conflicting url options

* Self -> hass fix

* Fix self -> hass again

* Use assert_setup_component

* Setup missing

* Correct test

* Fix again

* Fix

* Mising async

* Fix

* test real

* Test real

* Final

* check

* Final check

* safety

* Final commit and check

* Removed unused dependencies

* Test for multiple url options in config
This commit is contained in:
squidwardy 2018-08-23 11:14:18 +02:00 committed by Paulus Schoutsen
parent ced5eeacc2
commit 8e173f1658
2 changed files with 93 additions and 6 deletions

View file

@ -22,8 +22,13 @@ CONF_URL_PATH = 'url_path'
CONF_CONFIG = 'config'
CONF_WEBCOMPONENT_PATH = 'webcomponent_path'
CONF_JS_URL = 'js_url'
CONF_MODULE_URL = 'module_url'
CONF_EMBED_IFRAME = 'embed_iframe'
CONF_TRUST_EXTERNAL_SCRIPT = 'trust_external_script'
CONF_URL_EXCLUSIVE_GROUP = 'url_exclusive_group'
MSG_URL_CONFLICT = \
'Pass in only one of webcomponent_path, module_url or js_url'
DEFAULT_EMBED_IFRAME = False
DEFAULT_TRUST_EXTERNAL = False
@ -40,8 +45,12 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_SIDEBAR_ICON, default=DEFAULT_ICON): cv.icon,
vol.Optional(CONF_URL_PATH): cv.string,
vol.Optional(CONF_CONFIG): dict,
vol.Optional(CONF_WEBCOMPONENT_PATH): cv.isfile,
vol.Optional(CONF_JS_URL): cv.string,
vol.Exclusive(CONF_WEBCOMPONENT_PATH, CONF_URL_EXCLUSIVE_GROUP,
msg=MSG_URL_CONFLICT): cv.string,
vol.Exclusive(CONF_JS_URL, CONF_URL_EXCLUSIVE_GROUP,
msg=MSG_URL_CONFLICT): cv.string,
vol.Exclusive(CONF_MODULE_URL, CONF_URL_EXCLUSIVE_GROUP,
msg=MSG_URL_CONFLICT): cv.string,
vol.Optional(CONF_EMBED_IFRAME,
default=DEFAULT_EMBED_IFRAME): cv.boolean,
vol.Optional(CONF_TRUST_EXTERNAL_SCRIPT,
@ -66,6 +75,8 @@ async def async_register_panel(
html_url=None,
# JS source of your panel
js_url=None,
# JS module of your panel
module_url=None,
# If your panel should be run inside an iframe
embed_iframe=DEFAULT_EMBED_IFRAME,
# Should user be asked for confirmation when loading external source
@ -73,10 +84,10 @@ async def async_register_panel(
# Configuration to be passed to the panel
config=None):
"""Register a new custom panel."""
if js_url is None and html_url is None:
raise ValueError('Either js_url or html_url is required.')
elif js_url and html_url:
raise ValueError('Pass in either JS url or HTML url, not both.')
if js_url is None and html_url is None and module_url is None:
raise ValueError('Either js_url, module_url or html_url is required.')
elif (js_url and html_url) or (module_url and html_url):
raise ValueError('Pass in only one of JS url, Module url or HTML url.')
if config is not None and not isinstance(config, dict):
raise ValueError('Config needs to be a dictionary.')
@ -90,6 +101,9 @@ async def async_register_panel(
if js_url is not None:
custom_panel_config['js_url'] = js_url
if module_url is not None:
custom_panel_config['module_url'] = module_url
if html_url is not None:
custom_panel_config['html_url'] = html_url
@ -136,6 +150,9 @@ async def async_setup(hass, config):
if CONF_JS_URL in panel:
kwargs['js_url'] = panel[CONF_JS_URL]
elif CONF_MODULE_URL in panel:
kwargs['module_url'] = panel[CONF_MODULE_URL]
elif not await hass.async_add_job(os.path.isfile, panel_path):
_LOGGER.error('Unable to find webcomponent for %s: %s',
name, panel_path)

View file

@ -114,3 +114,73 @@ async def test_js_webcomponent(hass):
assert panel.frontend_url_path == 'nice_url'
assert panel.sidebar_icon == 'mdi:iconicon'
assert panel.sidebar_title == 'Sidebar Title'
async def test_module_webcomponent(hass):
"""Test if a js module is found in config panels dir."""
config = {
'panel_custom': {
'name': 'todo-mvc',
'module_url': '/local/bla.js',
'sidebar_title': 'Sidebar Title',
'sidebar_icon': 'mdi:iconicon',
'url_path': 'nice_url',
'config': {
'hello': 'world',
},
'embed_iframe': True,
'trust_external_script': True,
}
}
result = await setup.async_setup_component(
hass, 'panel_custom', config
)
assert result
panels = hass.data.get(frontend.DATA_PANELS, [])
assert panels
assert 'nice_url' in panels
panel = panels['nice_url']
assert panel.config == {
'hello': 'world',
'_panel_custom': {
'module_url': '/local/bla.js',
'name': 'todo-mvc',
'embed_iframe': True,
'trust_external': True,
}
}
assert panel.frontend_url_path == 'nice_url'
assert panel.sidebar_icon == 'mdi:iconicon'
assert panel.sidebar_title == 'Sidebar Title'
async def test_url_option_conflict(hass):
"""Test config with multiple url options."""
to_try = [
{'panel_custom': {
'name': 'todo-mvc',
'module_url': '/local/bla.js',
'js_url': '/local/bla.js',
}
}, {'panel_custom': {
'name': 'todo-mvc',
'webcomponent_path': '/local/bla.html',
'js_url': '/local/bla.js',
}}, {'panel_custom': {
'name': 'todo-mvc',
'webcomponent_path': '/local/bla.html',
'module_url': '/local/bla.js',
'js_url': '/local/bla.js',
}}
]
for config in to_try:
result = await setup.async_setup_component(
hass, 'panel_custom', config
)
assert not result