diff --git a/platypush/backend/http/webapp/src/components/panels/Execute/Index.vue b/platypush/backend/http/webapp/src/components/panels/Execute/Index.vue
index 2700ddf674..52a9a231f6 100644
--- a/platypush/backend/http/webapp/src/components/panels/Execute/Index.vue
+++ b/platypush/backend/http/webapp/src/components/panels/Execute/Index.vue
@@ -30,8 +30,10 @@
Action documentation
-
-
+
+
+
+
-
-
+
+
+
+
@@ -87,15 +91,17 @@
Attribute:
-
-
+
+
+
+
+
-
+
@@ -163,6 +169,7 @@ export default {
return {
loading: false,
running: false,
+ docLoading: false,
structuredInput: true,
actionChanged: false,
selectedDoc: undefined,
@@ -175,11 +182,11 @@ export default {
response: undefined,
error: undefined,
- htmlDoc: false,
rawRequest: undefined,
actions: {},
plugins: {},
procedures: {},
+ actionDocsCache: {},
action: {
name: undefined,
args: {},
@@ -195,15 +202,12 @@ export default {
try {
this.procedures = await this.request('inspect.get_procedures')
- this.plugins = await this.request('inspect.get_all_plugins', {html_doc: false})
+ this.plugins = await this.request('inspect.get_all_plugins')
} finally {
this.loading = false
}
for (const plugin of Object.values(this.plugins)) {
- if (plugin.html_doc)
- this.htmlDoc = true
-
for (const action of Object.values(plugin.actions)) {
action.name = plugin.name + '.' + action.name
action.supportsExtraArgs = !!action.has_kwargs
@@ -213,20 +217,20 @@ export default {
}
const self = this
- autocomplete(this.$refs.actionName, Object.keys(this.actions).sort(), (evt, value) => {
+ autocomplete(this.$refs.actionName, Object.keys(this.actions).sort(), (_, value) => {
this.action.name = value
self.updateAction()
})
},
- updateAction() {
+ async updateAction() {
if (!(this.action.name in this.actions))
this.selectedDoc = undefined
if (!this.actionChanged || !(this.action.name in this.actions))
return
- this.loading = true
+ this.docLoading = true
try {
this.action = {
...this.actions[this.action.name],
@@ -241,32 +245,27 @@ export default {
extraArgs: [],
}
} finally {
- this.loading = false
+ this.docLoading = false
}
- this.selectedDoc = this.parseDoc(this.action.doc)
+ this.selectedDoc =
+ this.actionDocsCache[this.action.name]?.html ||
+ await this.parseDoc(this.action.doc)
+
+ if (!this.actionDocsCache[this.action.name])
+ this.actionDocsCache[this.action.name] = {}
+ this.actionDocsCache[this.action.name].html = this.selectedDoc
+
this.actionChanged = false
this.response = undefined
this.error = undefined
},
- parseDoc(docString) {
- if (!docString?.length || this.htmlDoc)
+ async parseDoc(docString) {
+ if (!docString?.length)
return docString
- let lineNo = 0
- let trailingSpaces = 0
-
- return docString.split('\n').reduce((doc, line) => {
- if (++lineNo === 2)
- trailingSpaces = line.match(/^(\s*)/)[1].length
-
- if (line.trim().startsWith('.. code-block'))
- return doc
-
- doc += line.slice(trailingSpaces).replaceAll('``', '') + '\n'
- return doc
- }, '')
+ return await this.request('utils.rst_to_html', {text: docString})
},
updateProcedure(name, event) {
@@ -308,11 +307,16 @@ export default {
this.action.extraArgs.pop(i)
},
- selectAttrDoc(name) {
- this.response = undefined
- this.error = undefined
+ async selectAttrDoc(name) {
this.selectedAttr = name
- this.selectedAttrDoc = this.parseDoc(this.action.args[name].doc)
+ this.selectedAttrDoc =
+ this.actionDocsCache[this.action.name]?.[name]?.html ||
+ await this.parseDoc(this.action.args[name].doc)
+
+ if (!this.actionDocsCache[this.action.name])
+ this.actionDocsCache[this.action.name] = {}
+
+ this.actionDocsCache[this.action.name][name] = {html: this.selectedAttrDoc}
},
resetAttrDoc() {
@@ -450,6 +454,7 @@ $params-tablet-width: 20em;
}
.action-form {
+ background: $default-bg-2;
padding: 1em .5em;
}
@@ -544,12 +549,26 @@ $params-tablet-width: 20em;
display: flex;
margin-bottom: .5em;
+ label {
+ margin-left: 0.25em;
+ }
+
+ .buttons {
+ display: flex;
+ flex-grow: 1;
+ justify-content: right;
+ }
+
.action-extra-param-del {
border: 0;
text-align: right;
padding: 0 .5em;
}
+ input[type=text] {
+ width: 100%;
+ }
+
.buttons {
display: flex;
align-items: center;
@@ -569,11 +588,6 @@ $params-tablet-width: 20em;
.doc-container,
.output-container {
margin-top: .5em;
- .doc {
- &.raw {
- white-space: pre;
- }
- }
}
.output-container {
@@ -590,7 +604,6 @@ $params-tablet-width: 20em;
}
.doc {
- white-space: pre-line;
width: 100%;
overflow: auto;
}
@@ -613,11 +626,6 @@ $params-tablet-width: 20em;
.attr-doc-container {
.doc {
padding: 1em !important;
-
- &.raw {
- font-family: monospace;
- font-size: .8em;
- }
}
}
@@ -747,11 +755,14 @@ $params-tablet-width: 20em;
}
.run-btn {
- border-radius: 2em;
- padding: .5em .75em;
+ background: none;
+ border-radius: .25em;
+ padding: .5em 1.5em;
+ box-shadow: $primary-btn-shadow;
&:hover {
- opacity: .8;
+ background: $hover-bg;
+ box-shadow: none;
}
}
}
diff --git a/platypush/backend/http/webapp/src/components/panels/Execute/vars.scss b/platypush/backend/http/webapp/src/components/panels/Execute/vars.scss
index 76cd76d613..c120a666c0 100644
--- a/platypush/backend/http/webapp/src/components/panels/Execute/vars.scss
+++ b/platypush/backend/http/webapp/src/components/panels/Execute/vars.scss
@@ -7,7 +7,7 @@ $response-bg: #edfff2;
$response-border: 1px dashed #98ff98;
$error-bg: #ffbcbc;
$error-border: 1px dashed #ff5353;
-$doc-bg: #e8feff;
-$doc-border: 1px dashed #84f9ff;
+$doc-bg: $background-color;
+$doc-border: 1px dashed $border-color-2;
$procedure-submit-btn-bg: #ebffeb;
$section-title-bg: rgba(0, 0, 0, .04);
diff --git a/platypush/backend/http/webapp/src/style/themes/light.scss b/platypush/backend/http/webapp/src/style/themes/light.scss
index a148852dee..3810ab2572 100644
--- a/platypush/backend/http/webapp/src/style/themes/light.scss
+++ b/platypush/backend/http/webapp/src/style/themes/light.scss
@@ -56,6 +56,7 @@ $border-shadow-right: 2.5px 0 4px 0 $default-shadow-color;
$border-shadow-bottom-right: 2.5px 2.5px 3px 0 $default-shadow-color;
$header-shadow: 0px 1px 3px 1px #bbb !default;
$group-shadow: 3px -2px 6px 1px #98b0a0;
+$primary-btn-shadow: 1px 1px 0.5px 0.75px #32b64640 !default;
//// Modals
$modal-header-bg: #e0e0e0 !default;
@@ -81,6 +82,7 @@ $default-hover-fg: #35b870 !default;
$default-hover-fg-2: #38cf80 !default;
$hover-fg: $default-hover-fg !default;
$hover-bg: linear-gradient(90deg, rgba(190,246,218,1) 0%, rgba(229,251,240,1) 100%) !default;
+$hover-bg-2: rgb(190,246,218) !default;
$active-bg: #8fefb7 !default;
/// Disabled
diff --git a/platypush/plugins/inspect/__init__.py b/platypush/plugins/inspect/__init__.py
index ccbb34dc06..abac0df1c9 100644
--- a/platypush/plugins/inspect/__init__.py
+++ b/platypush/plugins/inspect/__init__.py
@@ -36,7 +36,6 @@ class InspectPlugin(Plugin):
self._backends_lock = threading.RLock()
self._events_lock = threading.RLock()
self._responses_lock = threading.RLock()
- self._html_doc = False
def _get_modules(self, parent_class: type):
for mf_file in scan_manifests(parent_class):
@@ -57,9 +56,7 @@ class InspectPlugin(Plugin):
for module in self._get_modules(Plugin):
plugin_name = '.'.join(module.__name__.split('.')[2:])
plugin_class = get_plugin_class_by_name(plugin_name)
- model = PluginModel(
- plugin=plugin_class, prefix=prefix, html_doc=self._html_doc
- )
+ model = PluginModel(plugin=plugin_class, prefix=prefix)
if model.name:
self._plugins[model.name] = model
@@ -70,9 +67,7 @@ class InspectPlugin(Plugin):
for module in self._get_modules(Backend):
for _, obj in inspect.getmembers(module):
if inspect.isclass(obj) and issubclass(obj, Backend):
- model = BackendModel(
- backend=obj, prefix=prefix, html_doc=self._html_doc
- )
+ model = BackendModel(backend=obj, prefix=prefix)
if model.name:
self._backends[model.name] = model
@@ -85,9 +80,7 @@ class InspectPlugin(Plugin):
continue
if inspect.isclass(obj) and issubclass(obj, Event) and obj != Event:
- event = EventModel(
- event=obj, html_doc=self._html_doc, prefix=prefix
- )
+ event = EventModel(event=obj, prefix=prefix)
if event.package not in self._events:
self._events[event.package] = {event.name: event}
else:
@@ -106,24 +99,19 @@ class InspectPlugin(Plugin):
and issubclass(obj, Response)
and obj != Response
):
- response = ResponseModel(
- response=obj, html_doc=self._html_doc, prefix=prefix
- )
+ response = ResponseModel(response=obj, prefix=prefix)
if response.package not in self._responses:
self._responses[response.package] = {response.name: response}
else:
self._responses[response.package][response.name] = response
@action
- def get_all_plugins(self, html_doc: Optional[bool] = None):
+ def get_all_plugins(self):
"""
- :param html_doc: If True then the docstring will be parsed into HTML (default: False)
+ Get information about all the available plugins.
"""
with self._plugins_lock:
- if not self._plugins or (
- html_doc is not None and html_doc != self._html_doc
- ):
- self._html_doc = html_doc
+ if not self._plugins:
self._init_plugins()
return json.dumps(
@@ -131,15 +119,12 @@ class InspectPlugin(Plugin):
)
@action
- def get_all_backends(self, html_doc: Optional[bool] = None):
+ def get_all_backends(self):
"""
- :param html_doc: If True then the docstring will be parsed into HTML (default: False)
+ Get information about all the available backends.
"""
with self._backends_lock:
- if not self._backends or (
- html_doc is not None and html_doc != self._html_doc
- ):
- self._html_doc = html_doc
+ if not self._backends:
self._init_backends()
return json.dumps(
@@ -147,15 +132,12 @@ class InspectPlugin(Plugin):
)
@action
- def get_all_events(self, html_doc: Optional[bool] = None):
+ def get_all_events(self):
"""
- :param html_doc: If True then the docstring will be parsed into HTML (default: False)
+ Get information about all the available events.
"""
with self._events_lock:
- if not self._events or (
- html_doc is not None and html_doc != self._html_doc
- ):
- self._html_doc = html_doc
+ if not self._events:
self._init_events()
return json.dumps(
@@ -166,15 +148,12 @@ class InspectPlugin(Plugin):
)
@action
- def get_all_responses(self, html_doc: Optional[bool] = None):
+ def get_all_responses(self):
"""
- :param html_doc: If True then the docstring will be parsed into HTML (default: False)
+ Get information about all the available responses.
"""
with self._responses_lock:
- if not self._responses or (
- html_doc is not None and html_doc != self._html_doc
- ):
- self._html_doc = html_doc
+ if not self._responses:
self._init_responses()
return json.dumps(
diff --git a/platypush/plugins/inspect/_model.py b/platypush/plugins/inspect/_model.py
index b3e10d6b61..12824d4328 100644
--- a/platypush/plugins/inspect/_model.py
+++ b/platypush/plugins/inspect/_model.py
@@ -2,7 +2,6 @@ from abc import ABC, abstractmethod
import inspect
import json
import re
-from typing import Optional
from platypush.utils import get_decorators
@@ -14,16 +13,6 @@ class Model(ABC):
def __repr__(self):
return json.dumps(dict(self))
- @staticmethod
- def to_html(doc):
- try:
- import docutils.core # type: ignore
- except ImportError:
- # docutils not found
- return doc
-
- return docutils.core.publish_parts(doc, writer_name='html')['html_body']
-
@abstractmethod
def __iter__(self):
raise NotImplementedError()
@@ -40,42 +29,29 @@ class ProcedureEncoder(json.JSONEncoder):
class BackendModel(Model):
- def __init__(self, backend, prefix='', html_doc: Optional[bool] = False):
+ def __init__(self, backend, prefix=''):
self.name = backend.__module__[len(prefix) :]
- self.html_doc = html_doc
- self.doc = (
- self.to_html(backend.__doc__)
- if html_doc and backend.__doc__
- else backend.__doc__
- )
+ self.doc = backend.__doc__
def __iter__(self):
- for attr in ['name', 'doc', 'html_doc']:
+ for attr in ['name', 'doc']:
yield attr, getattr(self, attr)
class PluginModel(Model):
- def __init__(self, plugin, prefix='', html_doc: Optional[bool] = False):
- self.name = plugin.__module__[len(prefix) :]
- self.html_doc = html_doc
- self.doc = (
- self.to_html(plugin.__doc__)
- if html_doc and plugin.__doc__
- else plugin.__doc__
- )
+ def __init__(self, plugin, prefix=''):
+ self.name = re.sub(r'\._plugin$', '', plugin.__module__[len(prefix) :])
+ self.doc = plugin.__doc__
self.actions = {
- action_name: ActionModel(
- getattr(plugin, action_name), html_doc=html_doc or False
- )
+ action_name: ActionModel(getattr(plugin, action_name))
for action_name in get_decorators(plugin, climb_class_hierarchy=True).get(
'action', []
)
}
def __iter__(self):
- for attr in ['name', 'actions', 'doc', 'html_doc']:
+ for attr in ['name', 'actions', 'doc']:
if attr == 'actions':
- # noinspection PyShadowingNames
yield attr, {
name: dict(action) for name, action in self.actions.items()
},
@@ -84,40 +60,31 @@ class PluginModel(Model):
class EventModel(Model):
- def __init__(self, event, prefix='', html_doc: Optional[bool] = False):
+ def __init__(self, event, prefix=''):
self.package = event.__module__[len(prefix) :]
self.name = event.__name__
- self.html_doc = html_doc
- self.doc = (
- self.to_html(event.__doc__) if html_doc and event.__doc__ else event.__doc__
- )
+ self.doc = event.__doc__
def __iter__(self):
- for attr in ['name', 'doc', 'html_doc']:
+ for attr in ['name', 'doc']:
yield attr, getattr(self, attr)
class ResponseModel(Model):
- def __init__(self, response, prefix='', html_doc: Optional[bool] = False):
+ def __init__(self, response, prefix=''):
self.package = response.__module__[len(prefix) :]
self.name = response.__name__
- self.html_doc = html_doc
- self.doc = (
- self.to_html(response.__doc__)
- if html_doc and response.__doc__
- else response.__doc__
- )
+ self.doc = response.__doc__
def __iter__(self):
- for attr in ['name', 'doc', 'html_doc']:
+ for attr in ['name', 'doc']:
yield attr, getattr(self, attr)
class ActionModel(Model):
- # noinspection PyShadowingNames
- def __init__(self, action, html_doc: bool = False):
+ def __init__(self, action):
self.name = action.__name__
- self.doc, argsdoc = self._parse_docstring(action.__doc__, html_doc=html_doc)
+ self.doc, argsdoc = self._parse_docstring(action.__doc__)
self.args = {}
self.has_kwargs = False
@@ -134,7 +101,7 @@ class ActionModel(Model):
}
@classmethod
- def _parse_docstring(cls, docstring: str, html_doc: bool = False):
+ def _parse_docstring(cls, docstring: str):
new_docstring = ''
params = {}
cur_param = None
@@ -147,11 +114,7 @@ class ActionModel(Model):
m = re.match(r'^\s*:param ([^:]+):\s*(.*)', line)
if m:
if cur_param:
- params[cur_param] = (
- cls.to_html(cur_param_docstring)
- if html_doc
- else cur_param_docstring
- )
+ params[cur_param] = cur_param_docstring
cur_param = m.group(1)
cur_param_docstring = m.group(2)
@@ -160,11 +123,7 @@ class ActionModel(Model):
else:
if cur_param:
if not line.strip():
- params[cur_param] = (
- cls.to_html(cur_param_docstring)
- if html_doc
- else cur_param_docstring
- )
+ params[cur_param] = cur_param_docstring
cur_param = None
cur_param_docstring = ''
else:
@@ -173,14 +132,9 @@ class ActionModel(Model):
new_docstring += line.rstrip() + '\n'
if cur_param:
- params[cur_param] = (
- cls.to_html(cur_param_docstring) if html_doc else cur_param_docstring
- )
+ params[cur_param] = cur_param_docstring
- return (
- new_docstring.strip() if not html_doc else cls.to_html(new_docstring),
- params,
- )
+ return new_docstring.strip(), params
def __iter__(self):
for attr in ['name', 'args', 'doc', 'has_kwargs']: