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

View file

@ -114,3 +114,73 @@ async def test_js_webcomponent(hass):
assert panel.frontend_url_path == 'nice_url' assert panel.frontend_url_path == 'nice_url'
assert panel.sidebar_icon == 'mdi:iconicon' assert panel.sidebar_icon == 'mdi:iconicon'
assert panel.sidebar_title == 'Sidebar Title' 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