From 452afefbb0e52b7d4d36057f5be9ad63fb8b6db5 Mon Sep 17 00:00:00 2001 From: Aurélien Bompard Date: Oct 12 2017 12:43:09 +0000 Subject: [PATCH 1/2] A couple improvements related to react-universal-component Use top-level components to make HMR easier one day. --- diff --git a/hubs/static/client/app/components/AddWidget/AddWidgetDialog.js b/hubs/static/client/app/components/AddWidget/AddWidgetDialog.js index 068d1de..efce753 100644 --- a/hubs/static/client/app/components/AddWidget/AddWidgetDialog.js +++ b/hubs/static/client/app/components/AddWidget/AddWidgetDialog.js @@ -9,6 +9,16 @@ import { addWidget } from '../../core/actions/widget'; import { makeLoadable } from '../../core/utils'; +const AsyncComponent = makeLoadable( + (props) => import( + /* webpackChunkName: "[request]" */ + `../../widgets/${props.widget.name}/Config` + ), + "Loading widget configuration...", + "Sorry, there was a problem loading the widget configuration." +); + + class AddWidgetDialog extends React.Component { constructor(props) { @@ -117,11 +127,6 @@ class AddWidgetDialog extends React.Component { // Second step: configure the widget title = "Adding widget " + this.state.selectedWidget.label; if (this.state.selectedWidget.isReact) { - const AsyncComponent = makeLoadable( - () => import(`../../widgets/${this.state.selectedWidget.name}/Config`), - "Loading widget configuration...", - "Sorry, there was a problem loading the widget configuration." - ); contents = ( { this.state.isLoading && -
Loading...
+
Loading...
||
} diff --git a/hubs/static/client/app/components/Widget.js b/hubs/static/client/app/components/Widget.js index 96c5d5b..f2951b6 100644 --- a/hubs/static/client/app/components/Widget.js +++ b/hubs/static/client/app/components/Widget.js @@ -5,6 +5,16 @@ import { makeLoadable } from '../core/utils'; import SimpleWidget from '../components/SimpleWidget'; +const AsyncComponent = makeLoadable( + (props) => import( + /* webpackChunkName: "[request]" */ + `../widgets/${props.widget.component}/Widget` + ), + "Loading widget...", + "Sorry, there was a problem loading the widget." +); + + class Widget extends React.PureComponent { propTypes: { @@ -20,11 +30,6 @@ class Widget extends React.PureComponent { render() { let widgetComponent; if (!this.props.editMode && this.props.widget.isReact) { - const AsyncComponent = makeLoadable( - () => import(`../widgets/${this.props.widget.component}/Widget`), - "Loading widget...", - "Sorry, there was a problem loading the widget." - ); widgetComponent = ( ); diff --git a/hubs/static/client/app/components/WidgetConfigDialog.js b/hubs/static/client/app/components/WidgetConfigDialog.js index 1a8bd33..58987da 100644 --- a/hubs/static/client/app/components/WidgetConfigDialog.js +++ b/hubs/static/client/app/components/WidgetConfigDialog.js @@ -11,6 +11,16 @@ import SimpleWidgetConfig from '../components/SimpleWidgetConfig'; import Modal from '../components/Modal'; +const AsyncComponent = makeLoadable( + (props) => import( + /* webpackChunkName: "[request]" */ + `../widgets/${props.widget.name}/Config` + ), + "Loading widget configuration...", + "Sorry, there was a problem loading the widget configuration." +); + + class WidgetConfigDialog extends React.Component { propTypes: { @@ -64,14 +74,8 @@ class WidgetConfigDialog extends React.Component { if (!this.props.widget) { return null; } - const widgetName = this.props.widget.name; let contents; if (this.props.widget.isReact) { - const AsyncComponent = makeLoadable( - () => import(`../widgets/${widgetName}/Config`), - "Loading widget configuration...", - "Sorry, there was a problem loading the widget configuration." - ); contents = ( import(/* webpackChunkName: "page-hub" */ '../components/HubPage'), "Loading...", "Sorry, there was a problem loading the page." ); -let Streams = makeLoadable( +const Streams = makeLoadable( () => import(/* webpackChunkName: "page-streams" */ '../components/StreamsPage'), "Loading...", "Sorry, there was a problem loading the page." diff --git a/hubs/static/client/app/index.js b/hubs/static/client/app/index.js index cccae63..4b97cc6 100644 --- a/hubs/static/client/app/index.js +++ b/hubs/static/client/app/index.js @@ -1,6 +1,8 @@ // Read the public path from the backend // https://webpack.js.org/guides/public-path/ -__webpack_public_path__ = window.resourceBaseUrl; +if(window.resourceBaseUrl) { + __webpack_public_path__ = window.resourceBaseUrl; +} import React from 'react'; import ReactDOM from 'react-dom'; From f6a511bf7af19fca44e7fa80872664e8a9412a1e Mon Sep 17 00:00:00 2001 From: Aurélien Bompard Date: Oct 13 2017 17:26:09 +0000 Subject: [PATCH 2/2] Allow using other tags than input in the widget config --- diff --git a/docs/api/widgets.rst b/docs/api/widgets.rst index f828673..f1102bc 100644 --- a/docs/api/widgets.rst +++ b/docs/api/widgets.rst @@ -17,6 +17,11 @@ Widget class .. automodule:: hubs.widgets.base :private-members: +Widget parameters +----------------- + +.. automodule:: hubs.widgets.parameters + Widget parameter validators --------------------------- diff --git a/hubs/static/client/app/components/SimpleWidgetConfig.js b/hubs/static/client/app/components/SimpleWidgetConfig.js index ca817dd..8228438 100644 --- a/hubs/static/client/app/components/SimpleWidgetConfig.js +++ b/hubs/static/client/app/components/SimpleWidgetConfig.js @@ -14,9 +14,11 @@ export default class SimpleWidgetConfig extends React.Component { return (
{field.label} - { diff --git a/hubs/tests/test_widget_base.py b/hubs/tests/test_widget_base.py index cb834d1..d416897 100644 --- a/hubs/tests/test_widget_base.py +++ b/hubs/tests/test_widget_base.py @@ -159,7 +159,10 @@ class WidgetTest(APPTest): {'default': 'I am a Fedora user, and this is my about', 'help': 'Text about a user.', 'label': 'Text', - 'name': 'text'}, + 'name': 'text', + 'renderTag': 'input', + 'renderType': 'text', + }, ], 'position': 'right', 'selfUrl': '/api/hubs/ralph/widgets/{}/'.format(widget.idx), diff --git a/hubs/tests/widgets/test_halp.py b/hubs/tests/widgets/test_halp.py index 715fa78..673ae66 100644 --- a/hubs/tests/widgets/test_halp.py +++ b/hubs/tests/widgets/test_halp.py @@ -128,11 +128,15 @@ class HalpViewsTestCase(WidgetTest): 'help': 'A comma-separated list of hubs to monitor.', 'label': 'Hubs', 'name': 'hubs', + 'renderTag': 'input', + 'renderType': 'text', }, {'default': 4, 'help': 'The number of requests per page to display.', 'label': 'Requests per page', 'name': 'per_page', + 'renderTag': 'input', + 'renderType': 'text', }, ], 'position': 'right', diff --git a/hubs/widgets/base.py b/hubs/widgets/base.py index db63192..bd10afc 100644 --- a/hubs/widgets/base.py +++ b/hubs/widgets/base.py @@ -9,50 +9,12 @@ import six from importlib import import_module from .caching import CachedFunction from .view import WidgetView +from .parameters import WidgetParameter log = logging.getLogger(__name__) -class WidgetParameter(object): - """ - Configuration option for a widget. - - A widget can be configured differently for each instance of this widget in - a hub. The list of configuration options is described by the list of - :py:class:`WidgetParameter` objects returned by the widget's - :py:meth:`~Widget.get_parameters` method. - - The value of the parameter is stored in the database as the value returned - by the validator's call. It can thus be a string, an integer, a list, a - dict, or any JSON-serializable value. - - Attributes: - name (str): The name of the parameter. - label (str): A humanized name of the parameter, which will be shown in - the UI. - default: The default value if this parameter is not set. - validator (callable): A validator function that will be used to convert - the parameter value to a JSON-serializable value, raising an - exception if it is invalid. - help (str): A help text that will be shown in the UI. - secret (bool): If True, this parameter will not be exposed to users - viewing the widget, it will only be available in the widget - configuration. - """ - - _attrs = ('name', 'label', 'default', 'validator', 'help', 'secret') - - def __init__(self, **kwargs): - for name in self._attrs: - setattr(self, name, kwargs.pop(name, None)) - for name in kwargs: - raise TypeError("Invalid attribute: %s" % name) - # Set default validator - if self.validator is None: - self.validator = lambda x: x - - class Widget(object): """ The main widget class, you must subclass it to create a widget. @@ -254,16 +216,10 @@ class Widget(object): hiddenIfEmpty=self.hidden_if_empty, cssClass=self.display_css, isReact=self.is_react, - params=[], + params=[ + param.to_dict() for param in self.get_parameters() + ], ) - for param in self.get_parameters(): - param_data = dict( - name=param.name, - label=param.label, - default=param.default, - help=param.help, - ) - props["params"].append(param_data) if instance is not None: props.update({ "idx": instance.idx, diff --git a/hubs/widgets/parameters.py b/hubs/widgets/parameters.py new file mode 100644 index 0000000..43c672e --- /dev/null +++ b/hubs/widgets/parameters.py @@ -0,0 +1,65 @@ +from __future__ import unicode_literals, absolute_import + +from .validators import Noop + + +class WidgetParameter(object): + """ + Configuration option for a widget. + + A widget can be configured differently for each instance of this widget in + a hub. The list of configuration options is described by the list of + :py:class:`WidgetParameter` objects returned by the widget's + :py:meth:`~Widget.get_parameters` method. + + The value of the parameter is stored in the database as the value returned + by the validator's call. It can thus be a string, an integer, a list, a + dict, or any JSON-serializable value. + + Attributes: + name (str): The name of the parameter. + label (str): A humanized name of the parameter, which will be shown in + the UI. + default: The default value if this parameter is not set. + validator (callable): A validator function that will be used to convert + the parameter value to a JSON-serializable value, raising an + exception if it is invalid. + help (str): A help text that will be shown in the UI. + secret (bool): If True, this parameter will not be exposed to users + viewing the widget, it will only be available in the widget + configuration. + render_tag (str): The HTML tag to use when rendering. + render_type (str): The HTML type attribute to use when rendering if the + tag is ``input``. + """ + + _defaults = { + "label": None, + "default": None, + "help": None, + "validator": Noop, + "secret": False, + "render_tag": "input", + "render_type": "text", + } + + def __init__(self, name, **kwargs): + self.name = name + for attr, default in self._defaults.items(): + setattr(self, attr, kwargs.pop(attr, default)) + for kw in kwargs: + raise TypeError("Invalid argument: %s" % kw) + + def to_dict(self): + """ + Returns: + (dict): A JS-ready representation of the parameter. + """ + return dict( + name=self.name, + label=self.label, + default=self.default, + renderTag=self.render_tag, + renderType=self.render_type, + help=self.help, + ) diff --git a/hubs/widgets/validators.py b/hubs/widgets/validators.py index f8c5b8f..85424fb 100644 --- a/hubs/widgets/validators.py +++ b/hubs/widgets/validators.py @@ -18,6 +18,11 @@ import requests import six +def Noop(value): + """Does no validation, just return the value.""" + return value + + def Required(value): """Raises an error if the value is ``False``-like.""" if not bool(value):