diff --git a/config/plugins/visualizations/common/templates/script_entry_point.mako b/config/plugins/visualizations/common/templates/script_entry_point.mako
index dfdf4137666b..80ad34908930 100644
--- a/config/plugins/visualizations/common/templates/script_entry_point.mako
+++ b/config/plugins/visualizations/common/templates/script_entry_point.mako
@@ -1,10 +1,36 @@
# -*- coding: utf-8 -*-
<%inherit file="visualization_base.mako"/>
-## No stylesheets
-<%def name="stylesheets()">%def>
-## No javascript libraries
-<%def name="late_javascripts()">
- <% tag_attrs = ' '.join([ '{0}="{1}"'.format( key, attr ) for key, attr in script_attributes.items() ]) %>
-
+## Add stylesheet
+<%def name="stylesheets()">
+ <% script_css = script_attributes.get("css") %>
+ %if script_css is not None:
+ <% script_css = script_css if h.is_url(script_css) else f"{static_url}{script_css}" %>
+
+ %endif
+%def>
+
+## Create a container, attach data and import script file
+<%def name="get_body()">
+ ## Collect incoming data
+ <%
+ from markupsafe import escape
+ data_incoming = {
+ "root": h.url_for("/"),
+ "visualization_id": visualization_id,
+ "visualization_name": visualization_name,
+ "visualization_plugin": visualization_plugin,
+ "visualization_title": escape(title),
+ "visualization_config": config }
+ %>
+
+ ## Create a container with default identifier `app`
+ <% container = script_attributes.get("container") or "app" %>
+
+
+ ## Add script tag
+ <% script_src = script_attributes.get("src") %>
+ <% script_src = script_src if h.is_url(script_src) else f"{static_url}{script_src}" %>
+ <% script_type = script_attributes.get("type") or "module" %>
+
%def>
diff --git a/config/plugins/visualizations/example/config/example.xml b/config/plugins/visualizations/example/config/example.xml
index cc2dd898fb79..4fe2e0f38ff5 100644
--- a/config/plugins/visualizations/example/config/example.xml
+++ b/config/plugins/visualizations/example/config/example.xml
@@ -1,45 +1,33 @@
-
- This is a developer example which demonstrates how to implement and configure a basic d3-based plugin for charts.
+
+ Welcome to the Minimal JS-Based Example Plugin.
HistoryDatasetAssociation
tabular.Tabular
- tabular.CSV
dataset_id
dataset_id
-
+
- data_dialog
-
- data_dialog
- false
+ setting_input
+ setting help
+ setting_type
- x
-
- data_column
- true
-
-
- y
-
- data_column
- true
-
-
- z
-
- data_column
- true
+ group_input
+ group help
+ group_type
-
\ No newline at end of file
+
+ spec_value
+
+
diff --git a/config/plugins/visualizations/example/package.json b/config/plugins/visualizations/example/package.json
deleted file mode 100644
index 199cab67b083..000000000000
--- a/config/plugins/visualizations/example/package.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "name": "visualization",
- "version": "0.2.0",
- "keywords": [
- "galaxy",
- "visualization"
- ],
- "license": "AFL-3.0",
- "dependencies": {
- "@galaxyproject/charts": "^0.1.0",
- "babel-preset-env": "^1.6.1",
- "backbone": "^1.3.3",
- "bootstrap": "^3.3.7",
- "d3": "^4.12.2",
- "jquery": "^3.1.1",
- "underscore": "^1.8.3"
- },
- "scripts": {
- "build": "parcel build src/script.js --dist-dir static"
- },
- "devDependencies": {
- "parcel": "2.11.0"
- }
-}
diff --git a/config/plugins/visualizations/example/src/script.js b/config/plugins/visualizations/example/src/script.js
deleted file mode 100644
index 4fda38c2380d..000000000000
--- a/config/plugins/visualizations/example/src/script.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import * as d3 from "d3";
-import * as _ from "underscore";
-import { request as requestDatasets } from "@galaxyproject/charts/lib/utilities/datasets";
-
-_.extend(window.bundleEntries || {}, {
- load: function(options) {
- var chart = options.chart;
- var root = options.root;
- requestDatasets({
- root: root,
- dataset_id: chart.get("dataset_id"),
- dataset_groups: chart.groups,
- success: function(groups) {
- var colors = d3.scaleOrdinal(d3.schemeCategory20);
- var error = null;
- _.each(groups, function(group, group_index) {
- try {
- $("#" + options.target).append("");
- var svg = d3.select("#myexample");
- var height = parseInt(svg.style("height"));
- var width = parseInt(svg.style("width"));
- var maxValue = d3.max(group.values, function(d) {
- return Math.max(d.x, d.y);
- });
- svg
- .selectAll("bubbles")
- .data(group.values)
- .enter()
- .append("circle")
- .attr("r", function(d) {
- return Math.abs(d.z) * 20 / maxValue;
- })
- .attr("cy", function(d, i) {
- return height * d.y / maxValue;
- })
- .attr("cx", function(d) {
- return width * d.x / maxValue;
- })
- .style("stroke", colors(group_index))
- .style("fill", "white");
- } catch (err) {
- error = err;
- }
- });
- if (error) {
- chart.state("failed", error);
- } else {
- chart.state("ok", "Workshop chart has been drawn.");
- }
- options.process.resolve();
- }
- });
- }
-});
diff --git a/config/plugins/visualizations/example/static/script.js b/config/plugins/visualizations/example/static/script.js
new file mode 100644
index 000000000000..e17270b74957
--- /dev/null
+++ b/config/plugins/visualizations/example/static/script.js
@@ -0,0 +1,33 @@
+const { visualization_config, visualization_plugin, root } = JSON.parse(document.getElementById("app").dataset.incoming);
+
+const div = Object.assign(document.createElement("div"), {
+ style: "border: 2px solid #25537b; border-radius: 1rem; padding: 1rem"
+});
+
+const img = Object.assign(document.createElement("img"), {
+ src: root + visualization_plugin.logo,
+ style: "height: 3rem"
+});
+div.appendChild(img);
+
+Object.entries(visualization_plugin).forEach(([key, value]) => {
+ const row = document.createElement("div");
+ const spanKey = Object.assign(document.createElement("span"), {
+ innerText: `${key}: `,
+ style: "font-weight: bold"
+ });
+ const spanValue = Object.assign(document.createElement("span"), {
+ innerText: JSON.stringify(value)
+ });
+ row.appendChild(spanKey);
+ row.appendChild(spanValue);
+ div.appendChild(row);
+});
+
+const dataset = Object.assign(document.createElement("div"), {
+ innerText: `You have selected dataset: ${visualization_config.dataset_id}.`,
+ style: "font-weight: bold; padding-top: 1rem;"
+});
+div.appendChild(dataset);
+
+document.body.appendChild(div);
diff --git a/lib/galaxy/app_unittest_utils/galaxy_mock.py b/lib/galaxy/app_unittest_utils/galaxy_mock.py
index 95715c042543..0fcec287878f 100644
--- a/lib/galaxy/app_unittest_utils/galaxy_mock.py
+++ b/lib/galaxy/app_unittest_utils/galaxy_mock.py
@@ -430,8 +430,17 @@ def remove(self):
class MockTemplateHelpers:
- def js(*js_files):
+ def css(*css_files):
pass
- def css(*css_files):
+ def dumps(*kwargs):
+ return {}
+
+ def js(*js_files):
pass
+
+ def is_url(*kwargs):
+ return True
+
+ def url_for(*kwargs):
+ return "/"
diff --git a/lib/galaxy/visualization/plugins/config_parser.py b/lib/galaxy/visualization/plugins/config_parser.py
index 85b1ccc79be9..160f52acef2c 100644
--- a/lib/galaxy/visualization/plugins/config_parser.py
+++ b/lib/galaxy/visualization/plugins/config_parser.py
@@ -221,7 +221,7 @@ def parse(self, xml_tree):
# when no tests are given, default to isinstance( object, model_class )
returned["tests"] = self.parse_tests(xml_tree.findall("test"))
- # to_params (optional, 0 or more) - tells the registry to set certain params based on the model_clas, tests
+ # to_params (optional, 0 or more) - tells the registry to set certain params based on the model_class, tests
returned["to_params"] = {}
if to_params := self.parse_to_params(xml_tree.findall("to_param")):
returned["to_params"] = to_params
diff --git a/lib/galaxy/visualization/plugins/plugin.py b/lib/galaxy/visualization/plugins/plugin.py
index dfe625e53dcb..c53f7977834a 100644
--- a/lib/galaxy/visualization/plugins/plugin.py
+++ b/lib/galaxy/visualization/plugins/plugin.py
@@ -161,7 +161,7 @@ def _build_render_vars(self, config: Dict[str, Any], trans=None, **kwargs) -> Di
render_vars.update(
visualization_name=self.name,
visualization_display_name=self.config["name"],
- title=kwargs.get("title", None),
+ title=kwargs.get("title", "Unnamed Visualization"),
saved_visualization=None,
visualization_id=None,
visualization_plugin=self.to_dict(),
diff --git a/lib/galaxy/web/framework/helpers/__init__.py b/lib/galaxy/web/framework/helpers/__init__.py
index 7fde6906cf19..f6f90bc54820 100644
--- a/lib/galaxy/web/framework/helpers/__init__.py
+++ b/lib/galaxy/web/framework/helpers/__init__.py
@@ -6,6 +6,7 @@
GalaxyWebTransaction in galaxy/webapps/base/webapp.py
"""
+import re
from datetime import (
datetime,
timedelta,
@@ -105,6 +106,22 @@ def is_true(val):
return val is True or val in ["True", "true", "T", "t"]
+def is_url(val):
+ """
+ Regular expression to match common URL protocols
+
+ >>> assert is_url(None) == False
+ >>> assert is_url("is_url") == False
+ >>> assert is_url("http://is_url") == True
+ >>> assert is_url("https://is_url") == True
+ """
+ if val is not None:
+ url_pattern = re.compile(r"^(https?:\/\/|ftp:\/\/)")
+ return bool(url_pattern.match(val))
+ else:
+ return False
+
+
def to_js_bool(val):
"""
Prints javascript boolean for passed value.
diff --git a/test/unit/app/visualizations/plugins/test_VisualizationPlugin.py b/test/unit/app/visualizations/plugins/test_VisualizationPlugin.py
index baf72fe065c8..772c54bb925e 100644
--- a/test/unit/app/visualizations/plugins/test_VisualizationPlugin.py
+++ b/test/unit/app/visualizations/plugins/test_VisualizationPlugin.py
@@ -74,7 +74,7 @@ def test_build_render_vars_default(self):
render_vars = plugin._build_render_vars(config)
assert render_vars["visualization_name"] == plugin.name
assert render_vars["visualization_display_name"] == plugin.config["name"]
- assert render_vars["title"] is None
+ assert render_vars["title"] == "Unnamed Visualization"
assert render_vars["saved_visualization"] is None
assert render_vars["visualization_id"] is None
assert render_vars["query"] == {}
diff --git a/test/unit/app/visualizations/plugins/test_VisualizationsRegistry.py b/test/unit/app/visualizations/plugins/test_VisualizationsRegistry.py
index 1c7f9707f047..6f5ce4274e70 100644
--- a/test/unit/app/visualizations/plugins/test_VisualizationsRegistry.py
+++ b/test/unit/app/visualizations/plugins/test_VisualizationsRegistry.py
@@ -15,7 +15,7 @@
glx_dir = galaxy_directory()
template_cache_dir = os.path.join(glx_dir, "database", "compiled_templates")
-addtional_templates_dir = os.path.join(glx_dir, "config", "plugins", "visualizations", "common", "templates")
+additional_templates_dir = os.path.join(glx_dir, "config", "plugins", "visualizations", "common", "templates")
vis_reg_path = "config/plugins/visualizations"
config1 = """\
@@ -144,7 +144,7 @@ def test_script_entry(self):
HistoryDatasetAssociation
-
+
"""
)
@@ -167,11 +167,11 @@ def test_script_entry(self):
assert script_entry.serves_templates
trans = galaxy_mock.MockTrans()
- script_entry._set_up_template_plugin(mock_app_dir.root_path, [addtional_templates_dir])
- response = script_entry._render({}, trans=trans, embedded=True)
- assert 'src="bler"' in response
- assert 'type="text/javascript"' in response
- assert 'data-main="one"' in response
+ script_entry._set_up_template_plugin(mock_app_dir.root_path, [additional_templates_dir])
+ response = script_entry.render(trans=trans, embedded=True)
+ assert '