From 8081fe794e97137277b80f24fca502eb044dc323 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Aug 2016 17:35:46 -0700 Subject: [PATCH] Add panel custom to load any webcomponent (#2747) --- .gitignore | 4 +- .../custom_components/react_panel/__init__.py | 30 -------- .../panel.html => panels/react.html} | 17 ++++ homeassistant/components/panel_custom.py | 64 +++++++++++++++ tests/components/test_panel_custom.py | 77 +++++++++++++++++++ 5 files changed, 161 insertions(+), 31 deletions(-) delete mode 100644 config/custom_components/react_panel/__init__.py rename config/{custom_components/react_panel/panel.html => panels/react.html} (96%) create mode 100644 homeassistant/components/panel_custom.py create mode 100644 tests/components/test_panel_custom.py diff --git a/.gitignore b/.gitignore index d749a8608fc..b73dcef1073 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,9 @@ config/custom_components/* !config/custom_components/example.py !config/custom_components/hello_world.py !config/custom_components/mqtt_example.py -!config/custom_components/react_panel +!config/panels +config/panels/* +!config/panels/react.html tests/testing_config/deps tests/testing_config/home-assistant.log diff --git a/config/custom_components/react_panel/__init__.py b/config/custom_components/react_panel/__init__.py deleted file mode 100644 index 57073b8cddc..00000000000 --- a/config/custom_components/react_panel/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Custom panel example showing TodoMVC using React. - -Will add a panel to control lights and switches using React. Allows configuring -the title via configuration.yaml: - -react_panel: - title: 'home' - -""" -import os - -from homeassistant.components.frontend import register_panel - -DOMAIN = 'react_panel' -DEPENDENCIES = ['frontend'] - -PANEL_PATH = os.path.join(os.path.dirname(__file__), 'panel.html') - - -def setup(hass, config): - """Initialize custom panel.""" - title = config.get(DOMAIN, {}).get('title') - - config = None if title is None else {'title': title} - - register_panel(hass, 'react', PANEL_PATH, - title='TodoMVC', icon='mdi:checkbox-marked-outline', - config=config) - return True diff --git a/config/custom_components/react_panel/panel.html b/config/panels/react.html similarity index 96% rename from config/custom_components/react_panel/panel.html rename to config/panels/react.html index eceee0f0616..dc2735cf759 100644 --- a/config/custom_components/react_panel/panel.html +++ b/config/panels/react.html @@ -1,3 +1,20 @@ + + diff --git a/homeassistant/components/panel_custom.py b/homeassistant/components/panel_custom.py new file mode 100644 index 00000000000..e4b2480f6d4 --- /dev/null +++ b/homeassistant/components/panel_custom.py @@ -0,0 +1,64 @@ +"""Register a custom front end panel.""" +import logging +import os + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.frontend import register_panel + +DOMAIN = 'panel_custom' +DEPENDENCIES = ['frontend'] + +CONF_COMPONENT_NAME = 'name' +CONF_SIDEBAR_TITLE = 'sidebar_title' +CONF_SIDEBAR_ICON = 'sidebar_icon' +CONF_URL_PATH = 'url_path' +CONF_CONFIG = 'config' +CONF_WEBCOMPONENT_PATH = 'webcomponent_path' + +DEFAULT_ICON = 'mdi:bookmark' + +PANEL_DIR = 'panels' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.All(cv.ensure_list, [{ + vol.Required(CONF_COMPONENT_NAME): cv.slug, + vol.Optional(CONF_SIDEBAR_TITLE): cv.string, + vol.Optional(CONF_SIDEBAR_ICON, default=DEFAULT_ICON): cv.icon, + vol.Optional(CONF_URL_PATH): cv.string, + vol.Optional(CONF_CONFIG): cv.match_all, + vol.Optional(CONF_WEBCOMPONENT_PATH): cv.isfile, + }]) +}, extra=vol.ALLOW_EXTRA) + +_LOGGER = logging.getLogger(__name__) + + +def setup(hass, config): + """Initialize custom panel.""" + success = False + + for panel in config.get(DOMAIN): + name = panel.get(CONF_COMPONENT_NAME) + panel_path = panel.get(CONF_WEBCOMPONENT_PATH) + + if panel_path is None: + panel_path = hass.config.path(PANEL_DIR, '{}.html'.format(name)) + + if not os.path.isfile(panel_path): + _LOGGER.error('Unable to find webcomponent for %s: %s', + name, panel_path) + continue + + register_panel( + hass, name, panel_path, + sidebar_title=panel.get(CONF_SIDEBAR_TITLE), + sidebar_icon=panel.get(CONF_SIDEBAR_ICON), + url_path=panel.get(CONF_URL_PATH), + config=panel.get(CONF_CONFIG), + ) + + success = True + + return success diff --git a/tests/components/test_panel_custom.py b/tests/components/test_panel_custom.py new file mode 100644 index 00000000000..6a41706db98 --- /dev/null +++ b/tests/components/test_panel_custom.py @@ -0,0 +1,77 @@ +"""The tests for the panel_custom component.""" +import os +import shutil +from tempfile import NamedTemporaryFile +import unittest +from unittest.mock import patch + +from homeassistant import bootstrap +from homeassistant.components import panel_custom + +from tests.common import get_test_home_assistant + + +@patch('homeassistant.components.frontend.setup', return_value=True) +class TestPanelCustom(unittest.TestCase): + """Test the panel_custom component.""" + + def setup_method(self, method): + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + + def teardown_method(self, method): + """Stop everything that was started.""" + self.hass.stop() + shutil.rmtree(self.hass.config.path(panel_custom.PANEL_DIR), + ignore_errors=True) + + @patch('homeassistant.components.panel_custom.register_panel') + def test_webcomponent_in_panels_dir(self, mock_register, _mock_setup): + """Test if a web component is found in config panels dir.""" + config = { + 'panel_custom': { + 'name': 'todomvc', + } + } + + assert not bootstrap.setup_component(self.hass, 'panel_custom', config) + assert not mock_register.called + + path = self.hass.config.path(panel_custom.PANEL_DIR) + os.mkdir(path) + + with open(os.path.join(path, 'todomvc.html'), 'a'): + assert bootstrap.setup_component(self.hass, 'panel_custom', config) + assert mock_register.called + + @patch('homeassistant.components.panel_custom.register_panel') + def test_webcomponent_custom_path(self, mock_register, _mock_setup): + """Test if a web component is found in config panels dir.""" + with NamedTemporaryFile() as fp: + config = { + 'panel_custom': { + 'name': 'todomvc', + 'webcomponent_path': fp.name, + 'sidebar_title': 'Sidebar Title', + 'sidebar_icon': 'mdi:iconicon', + 'url_path': 'nice_url', + 'config': 5, + } + } + + with patch('os.path.isfile', return_value=False): + assert not bootstrap.setup_component(self.hass, 'panel_custom', + config) + assert not mock_register.called + + assert bootstrap.setup_component(self.hass, 'panel_custom', config) + assert mock_register.called + args = mock_register.mock_calls[0][1] + kwargs = mock_register.mock_calls[0][2] + assert args == (self.hass, 'todomvc', fp.name) + assert kwargs == { + 'config': 5, + 'url_path': 'nice_url', + 'sidebar_icon': 'mdi:iconicon', + 'sidebar_title': 'Sidebar Title' + }