Skip to content

Commit

Permalink
Merge pull request #12811 from mvdbeek/show_tool_source_in_tool_form
Browse files Browse the repository at this point in the history
Display tool source in tool form
  • Loading branch information
dannon authored Nov 23, 2021
2 parents 1cf7ca4 + a2af4b1 commit b380c0a
Show file tree
Hide file tree
Showing 19 changed files with 202 additions and 5 deletions.
5 changes: 4 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@johmun/vue-tags-input": "^2.1.0",
"@sentry/browser": "^5.20.1",
"axios": "^0.21.1",
"babel-runtime": "^6.26.0",
"backbone": "1.4.0",
"bootstrap": "4.5.0",
"bootstrap-vue": "^2.21.2",
Expand Down Expand Up @@ -89,6 +90,7 @@
"vue-infinite-scroll": "^2.0.2",
"vue-multiselect": "^2.1.0",
"vue-observe-visibility": "^1.0.0",
"vue-prismjs": "^1.2.0",
"vue-router": "^3.5.2",
"vue-rx": "^6.2.0",
"vue-scrollto": "^2.20.0",
Expand All @@ -97,7 +99,8 @@
"vuex": "^3.4.0",
"vuex-cache": "^3.2.0",
"vuex-persist": "^3.1.3",
"vuex-persistedstate": "^4.1.0"
"vuex-persistedstate": "^4.1.0",
"xml-beautifier": "^0.5.0"
},
"scripts": {
"watch": "gulp && yarn run save-build-hash && yarn run webpack-watch",
Expand Down
3 changes: 3 additions & 0 deletions client/src/components/Tool/ToolCard.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ describe("ToolCard", () => {
messageVariant: "warning",
disabled: false,
},
stubs: {
ToolSourceMenuItem: { template: "<div></div>" },
},
localVue,
});
});
Expand Down
4 changes: 4 additions & 0 deletions client/src/components/Tool/ToolCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<b-dropdown-item v-if="showDownload" @click="onDownload"
><span class="fa fa-download" /><span v-localize>Download</span>
</b-dropdown-item>
<ToolSourceMenuItem :tool-id="id" />
<b-dropdown-item v-if="showLink" @click="onLink"
><span class="fa fa-external-link" /><span v-localize
>See in Tool Shed</span
Expand Down Expand Up @@ -87,6 +88,7 @@
</span>
</div>
</div>

<div class="portlet-content">
<FormMessage :message="errorText" variant="danger" :persistent="true" />
<FormMessage :message="messageText" :variant="messageVariant" />
Expand Down Expand Up @@ -114,6 +116,7 @@ import { copyLink, downloadTool, openLink } from "./utilities";
import FormMessage from "components/Form/FormMessage";
import ToolFooter from "components/Tool/ToolFooter";
import ToolHelp from "components/Tool/ToolHelp";
import ToolSourceMenuItem from "components/Tool/ToolSourceMenuItem";
import Webhooks from "mvc/webhooks";
import { addFavorite, removeFavorite } from "components/Tool/services";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
Expand All @@ -128,6 +131,7 @@ export default {
FormMessage,
ToolFooter,
ToolHelp,
ToolSourceMenuItem,
},
props: {
id: {
Expand Down
30 changes: 30 additions & 0 deletions client/src/components/Tool/ToolSource.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<template>
<ToolSourceProvider :id="toolId" v-slot="{ result, loading, error }">
<loading-span v-if="loading" message="Waiting on tool source" />
<alert v-else-if="error" :message="error" variant="error" />
<prism v-else :language="result.language" :code="result.source" :plugins="['normalize-whitespace']"></prism>
</ToolSourceProvider>
</template>
<script>
import Alert from "components/Alert";
import LoadingSpan from "components/LoadingSpan";
import { ToolSourceProvider } from "components/providers/ToolSourceProvider";
import Prism from "vue-prismjs";
import "prismjs/themes/prism.css";
import "prismjs/plugins/normalize-whitespace/prism-normalize-whitespace.js";
export default {
components: {
Alert,
LoadingSpan,
Prism,
ToolSourceProvider,
},
props: {
toolId: {
type: String,
required: true,
},
},
};
</script>
39 changes: 39 additions & 0 deletions client/src/components/Tool/ToolSourceMenuItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<template>
<CurrentUser v-slot="{ user }">
<ConfigProvider v-slot="{ config }">
<div v-if="config.enable_tool_source_display || (user && user.is_admin)">
<b-dropdown-item v-b-modal.tool-source-viewer
><span class="fa fa-eye" /><span v-localize>View Tool source</span>
</b-dropdown-item>
<b-modal
id="tool-source-viewer"
:title="`Tool Source for ${toolId}`"
size="lg"
ok-only
ok-title="Close"
>
<ToolSource :tool-id="toolId" />
</b-modal>
</div>
</ConfigProvider>
</CurrentUser>
</template>
<script>
import ConfigProvider from "components/providers/ConfigProvider";
import CurrentUser from "components/providers/CurrentUser";
import ToolSource from "components/Tool/ToolSource";
export default {
components: {
ConfigProvider,
CurrentUser,
ToolSource,
},
props: {
toolId: {
type: String,
required: true,
},
},
};
</script>
4 changes: 2 additions & 2 deletions client/src/components/providers/SingleQueryProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import hash from "object-hash";

/**
* Builds a provider that gets its result from a single promise-based query function and
* caches the result of lookup for subsequent instantitations.
* caches the result of lookup for subsequent instantiations.
*
* @param {Function} lookup async function that loads the result, paramters will be an object
* @param {Function} lookup async function that loads the result, parameters will be an object
* whose properties are the attributes assigned to the provider component
* @return {VueComponentOptions} Vue component options definition
*/
Expand Down
24 changes: 24 additions & 0 deletions client/src/components/providers/ToolSourceProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import axios from "axios";
import { getAppRoot } from "onload/loadConfig";
import { SingleQueryProvider } from "components/providers/SingleQueryProvider";
import { rethrowSimple } from "utils/simple-error";
import beautify from "xml-beautifier";

async function toolSource({ id }) {
const url = `${getAppRoot()}api/tools/${id}/raw_tool_source`;
try {
const { data, headers } = await axios.get(url);
const result = {};
result.language = headers.language;
if (headers.language === "xml") {
result.source = beautify(data);
} else {
result.source = data;
}
return result;
} catch (e) {
rethrowSimple(e);
}
}

export const ToolSourceProvider = SingleQueryProvider(toolSource);
36 changes: 34 additions & 2 deletions client/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2729,6 +2729,14 @@ babel-preset-jest@^27.2.0:
babel-plugin-jest-hoist "^27.2.0"
babel-preset-current-node-syntax "^1.0.0"

babel-runtime@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
dependencies:
core-js "^2.4.0"
regenerator-runtime "^0.11.0"

[email protected]:
version "3.0.0-canary-5"
resolved "https://registry.yarnpkg.com/babel-walk/-/babel-walk-3.0.0-canary-5.tgz#f66ecd7298357aee44955f235a6ef54219104b11"
Expand Down Expand Up @@ -3831,6 +3839,11 @@ core-js-pure@^3.0.0:
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.18.3.tgz#7eed77dcce1445ab68fd68715856633e2fb3b90c"
integrity sha512-qfskyO/KjtbYn09bn1IPkuhHl5PlJ6IzJ9s9sraJ1EqcuGyLGKzhSM1cY0zgyL9hx42eulQLZ6WaeK5ycJCkqw==

core-js@^2.4.0:
version "2.6.12"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==

core-js@^3.19.1:
version "3.19.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.19.1.tgz#f6f173cae23e73a7d88fa23b6e9da329276c6641"
Expand Down Expand Up @@ -10070,7 +10083,7 @@ pretty@^2.0.0:
extend-shallow "^2.0.1"
js-beautify "^1.6.12"

prismjs@^1.17.1, prismjs@^1.23.0:
prismjs@^1.17.1, prismjs@^1.23.0, prismjs@^1.6.0:
version "1.25.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756"
integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg==
Expand Down Expand Up @@ -10736,6 +10749,11 @@ regenerate@^1.4.0, regenerate@^1.4.2:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==

regenerator-runtime@^0.11.0:
version "0.11.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==

regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7, regenerator-runtime@^0.13.9:
version "0.13.9"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
Expand Down Expand Up @@ -10867,7 +10885,7 @@ repeat-element@^1.1.2:
resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9"
integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==

repeat-string@^1.0.0, repeat-string@^1.6.1:
repeat-string@1.6.1, repeat-string@^1.0.0, repeat-string@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
Expand Down Expand Up @@ -12933,6 +12951,13 @@ vue-observe-visibility@^1.0.0:
resolved "https://registry.yarnpkg.com/vue-observe-visibility/-/vue-observe-visibility-1.0.0.tgz#17cf1b2caf74022f0f3c95371468ddf2b9573152"
integrity sha512-s5TFh3s3h3Mhd3jaz3zGzkVHKHnc/0C/gNr30olO99+yw2hl3WBhK3ng3/f9OF+qkW4+l7GkmwfAzDAcY3lCFg==

vue-prismjs@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/vue-prismjs/-/vue-prismjs-1.2.0.tgz#b137f5ed958685ce1fd55ca6b068289f3359b8b7"
integrity sha512-1mICbMknMiKw4mfM1AU7vkFT3VX4Cq8ySyDU7IBr9tYW1fzxIPkfLoERkRMbQLBc+J74K3FIiuuZ+WfBjO3SCQ==
dependencies:
prismjs "^1.6.0"

vue-router@^3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.5.2.tgz#5f55e3f251970e36c3e8d88a7cd2d67a350ade5c"
Expand Down Expand Up @@ -13544,6 +13569,13 @@ ws@~7.4.2:
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==

xml-beautifier@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/xml-beautifier/-/xml-beautifier-0.5.0.tgz#476fd7118b972c2ec5863babca6812934d34bb56"
integrity sha512-QG/qiHeolHUd1tAtM+5zHxTzDprb8qvhmIYUYV1E9QK/jTFlrAa1Mz7QQqJPeqc3uuFAGzTOhjvbdx2hOP6bHw==
dependencies:
repeat-string "1.6.1"

xml-name-validator@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
Expand Down
14 changes: 14 additions & 0 deletions doc/source/admin/galaxy_options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3908,6 +3908,20 @@
:Type: bool


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``enable_tool_source_display``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

:Description:
This option allows users to view the tool wrapper source code.
This is safe to enable if you have not hardcoded any secrets in
any of the tool wrappers installed on this Galaxy server. If you
have only installed tool wrappers from public tool sheds and
tools shipped with Galaxy there you can enable this option.
:Default: ``false``
:Type: bool


~~~~~~~~~~~~~~~~~~~~~~~~~~~
``job_metrics_config_file``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
7 changes: 7 additions & 0 deletions lib/galaxy/config/sample/galaxy.yml.sample
Original file line number Diff line number Diff line change
Expand Up @@ -1912,6 +1912,13 @@ galaxy:
# see dataset paths.
#expose_dataset_path: false

# This option allows users to view the tool wrapper source code. This
# is safe to enable if you have not hardcoded any secrets in any of
# the tool wrappers installed on this Galaxy server. If you have only
# installed tool wrappers from public tool sheds and tools shipped
# with Galaxy there you can enable this option.
#enable_tool_source_display: false

# XML config file that contains the job metric collection
# configuration.
# The value of this option will be resolved with respect to
Expand Down
1 change: 1 addition & 0 deletions lib/galaxy/managers/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ def _config_is_truthy(item, key, **context):
'upload_from_form_button': _use_config,
'release_doc_base_url': _use_config,
'expose_user_email': _use_config,
'enable_tool_source_display': _use_config,
'user_library_import_dir_available': lambda item, key, **context: bool(item.get('user_library_import_dir')),
'welcome_directory': _use_config,
}
Expand Down
2 changes: 2 additions & 0 deletions lib/galaxy/tool_util/parser/cwl.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

class CwlToolSource(ToolSource):

language = 'yaml'

def __init__(self, tool_file, strict_cwl_validation=True):
self._cwl_tool_file = tool_file
self._id, _ = os.path.splitext(os.path.basename(tool_file))
Expand Down
1 change: 1 addition & 0 deletions lib/galaxy/tool_util/parser/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class ToolSource(metaclass=ABCMeta):
information from.
"""
default_is_multi_byte = False
language: str

@abstractmethod
def parse_id(self):
Expand Down
2 changes: 2 additions & 0 deletions lib/galaxy/tool_util/parser/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class XmlToolSource(ToolSource):
""" Responsible for parsing a tool from classic Galaxy representation.
"""

language = 'xml'

def __init__(self, xml_tree, source_path=None, macro_paths=None):
self.xml_tree = xml_tree
self.root = xml_tree.getroot()
Expand Down
2 changes: 2 additions & 0 deletions lib/galaxy/tool_util/parser/yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

class YamlToolSource(ToolSource):

language = 'yaml'

def __init__(self, root_dict, source_path=None):
self.root_dict = root_dict
self._source_path = source_path
Expand Down
10 changes: 10 additions & 0 deletions lib/galaxy/webapps/galaxy/api/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
expose_api_anonymous_and_sessionless,
expose_api_raw_anonymous_and_sessionless,
)
from galaxy.web.framework.decorators import expose_api_raw
from galaxy.webapps.base.controller import UsesVisualizationMixin
from galaxy.webapps.base.webapp import GalaxyWebTransaction
from . import BaseGalaxyAPIController, depends
Expand Down Expand Up @@ -490,6 +491,15 @@ def download(self, trans: GalaxyWebTransaction, id, **kwds):
trans.response.headers["Content-Disposition"] = f'attachment; filename="{id}.tgz"'
return download_file

@expose_api_raw
def raw_tool_source(self, trans: GalaxyWebTransaction, id, **kwds):
"""Returns tool source. ``language`` is included in the response header."""
if not trans.app.config.enable_tool_source_display and not trans.user_is_admin:
raise exceptions.InsufficientPermissionsException("Only administrators may display tool sources on this Galaxy server.")
tool = self._get_tool(id, user=trans.user, tool_version=kwds.get('tool_version'))
trans.response.headers['language'] = tool.tool_source.language
return tool.tool_source.to_string()

@expose_api_anonymous
def fetch(self, trans: GalaxyWebTransaction, payload, **kwd):
"""Adapt clean API to tool-constrained API.
Expand Down
1 change: 1 addition & 0 deletions lib/galaxy/webapps/galaxy/buildapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ def populate_api_routes(webapp, app):
webapp.mapper.connect('/api/tools/{tool_id:.+?}/convert', action='conversion', controller="tools", conditions=dict(method=["POST"]))
webapp.mapper.connect('/api/tools/{id:.+?}/xrefs', action='xrefs', controller="tools")
webapp.mapper.connect('/api/tools/{id:.+?}/download', action='download', controller="tools")
webapp.mapper.connect('/api/tools/{id:.+?}/raw_tool_source', action='raw_tool_source', controller="tools")
webapp.mapper.connect('/api/tools/{id:.+?}/requirements', action='requirements', controller="tools")
webapp.mapper.connect('/api/tools/{id:.+?}/install_dependencies', action='install_dependencies', controller="tools", conditions=dict(method=["POST"]))
webapp.mapper.connect('/api/tools/{id:.+?}/dependencies', action='install_dependencies', controller="tools", conditions=dict(method=["POST"]))
Expand Down
11 changes: 11 additions & 0 deletions lib/galaxy/webapps/galaxy/config_schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2821,6 +2821,17 @@ mapping:
Details" option in the history. This option also exposes the command line to
non-administrative users. Administrators can always see dataset paths.
enable_tool_source_display:
type: bool
default: false
required: false
desc: |
This option allows users to view the tool wrapper source code. This is
safe to enable if you have not hardcoded any secrets in any of the tool
wrappers installed on this Galaxy server. If you have only installed tool
wrappers from public tool sheds and tools shipped with Galaxy there you
can enable this option.
job_metrics_config_file:
type: str
default: job_metrics_conf.xml
Expand Down
Loading

0 comments on commit b380c0a

Please sign in to comment.