Merge pull request #8090 from guerler/new_toolshed

Convert repository client to vue
This commit is contained in:
Marius van den Beek
2019-06-29 09:17:36 +02:00
committed by GitHub
27 changed files with 987 additions and 106 deletions
-1
View File
@@ -136,7 +136,6 @@ GalaxyApp.prototype._patchGalaxy = function _patchGalaxy(patchWith) {
/** set up the metrics logger (utils/metrics-logger) and pass loggerOptions */
GalaxyApp.prototype._initLogger = function _initLogger(loggerOptions) {
// default to console logging at the debug level if the debug flag is set
if (this.config.debug) {
loggerOptions.consoleLogger = loggerOptions.consoleLogger || console;
@@ -0,0 +1,67 @@
<template>
<div>
<div v-if="loading">
<span class="fa fa-spinner fa-spin mb-4 mr-1" />
<span>Loading categories...</span>
</div>
<b-table v-else striped :items="categories" :fields="fields">
<template slot="name" slot-scope="data">
<b-link href="#" class="font-weight-bold" @click="onCategory(data.value)">
{{ data.value }}
</b-link>
</template>
</b-table>
</div>
</template>
<script>
import { Services } from "./services.js";
export default {
props: ["toolshedUrl", "loading"],
data() {
return {
categories: [],
fields: {
name: {
label: "Category",
sortable: true
},
description: {
sortable: false
},
repositories: {
sortable: true
}
}
};
},
created() {
this.services = new Services();
this.load();
},
watch: {
toolshedUrl() {
this.load();
}
},
methods: {
load() {
this.$emit("onLoading", true);
this.services
.getCategories(this.toolshedUrl)
.then(categories => {
this.categories = categories;
const reducer = (value, entry) => value + entry.repositories;
this.$emit("onTotal", this.categories.reduce(reducer, 0));
this.$emit("onLoading", false);
})
.catch(errorMessage => {
this.$emit("onError", errorMessage);
this.$emit("onLoading", false);
});
},
onCategory(category) {
this.$emit("onCategory", category);
}
}
};
</script>
@@ -0,0 +1,118 @@
<template>
<div class="overflow-auto h-100 p-1" @scroll="onScroll">
<div v-if="error" class="alert alert-danger">{{ error }}</div>
<div v-else>
<b-input
class="mb-3"
placeholder="search repositories"
v-model="queryInput"
@input="delayQuery"
@change="setQuery"
/>
<serverselection
:toolshedUrl="toolshedUrl"
:toolshedUrls="toolshedUrls"
:total="total"
:loading="loading"
@onToolshed="setToolshed"
/>
<repositories
:query="query"
:scrolled="scrolled"
:toolshedUrl="toolshedUrl"
@onError="setError"
v-if="!queryEmpty"
/>
<categories
:toolshedUrl="toolshedUrl"
:loading="loading"
@onCategory="setQuery"
@onTotal="setTotal"
@onError="setError"
@onLoading="setLoading"
v-show="queryEmpty"
/>
</div>
</div>
</template>
<script>
import { getGalaxyInstance } from "app";
import Categories from "./Categories.vue";
import Repositories from "./Repositories.vue";
import ServerSelection from "./ServerSelection.vue";
export default {
components: {
categories: Categories,
repositories: Repositories,
serverselection: ServerSelection
},
data() {
return {
toolshedUrl: null,
toolshedUrls: [],
queryInput: null,
queryDelay: 1000,
queryTimer: null,
queryLength: 3,
query: null,
scrolled: false,
loading: false,
total: 0,
error: null
};
},
created() {
this.configureToolsheds();
},
computed: {
queryEmpty() {
return !this.query || this.query.length < this.queryLength;
}
},
methods: {
configureToolsheds() {
const galaxy = getGalaxyInstance();
this.toolshedUrls = galaxy.config.tool_shed_urls;
if (!this.toolshedUrls || this.toolshedUrls.length == 0) {
this.setError("Toolshed registry is empty, no servers found.");
} else {
this.toolshedUrl = this.toolshedUrls[0];
}
},
clearTimer() {
if (this.queryTimer) {
clearTimeout(this.queryTimer);
}
},
delayQuery(query) {
this.clearTimer();
if (query) {
this.queryTimer = setTimeout(() => {
this.setQuery(query);
}, this.queryDelay);
} else {
this.setQuery(query);
}
},
setError(error) {
this.error = error;
},
setQuery(query) {
this.clearTimer();
this.query = this.queryInput = query;
},
setToolshed(url) {
this.toolshedUrl = url;
},
setTotal(total) {
this.total = total;
},
setLoading(loading) {
this.loading = loading;
},
onScroll: function({ target: { scrollTop, clientHeight, scrollHeight } }) {
this.scrolled = scrollTop + clientHeight >= scrollHeight;
}
}
};
</script>
@@ -0,0 +1,45 @@
<template>
<div>
<b-button v-if="installState" :class="buttonClass" variant="primary" @click="onInstall">
Install
</b-button>
<div v-else>
<b-button v-if="uninstallState" :class="buttonClass" variant="danger" @click="onUninstall">
Uninstall
</b-button>
<b-button v-else :class="buttonClass" @click="onUninstall">
<span v-if="!errorState" class="fa fa-spinner fa-spin" />
<span>{{ status }}</span>
</b-button>
</div>
</div>
</template>
<script>
export default {
props: ["installed", "status"],
data() {
return {
buttonClass: "btn-sm text-nowrap"
};
},
computed: {
installState() {
return !this.status || this.status == "Uninstalled";
},
uninstallState() {
return this.status == "Installed";
},
errorState() {
return this.status == "Error";
}
},
methods: {
onInstall() {
this.$emit("onInstall");
},
onUninstall() {
this.$emit("onUninstall");
}
}
};
</script>
@@ -0,0 +1,138 @@
<template>
<b-modal v-model="modalShow" @ok="onOk" @hide="onHide">
<template slot="modal-header">
<h4 class="m-0">
{{ modalTitle }}
</h4>
</template>
<div class="mb-1">
{{ repo.long_description || repo.description }}
</div>
<div class="text-muted small mb-3">{{ repo.repo_owner_username }} rev. {{ changesetRevision }}</div>
<b-form-group
v-if="requiresPanel"
label="Target Section:"
description="Choose an existing tool panel section or create a new section to contain the installed tools (optional)."
>
<b-form-input list="sectionSelect" v-model="toolSection" />
<datalist id="sectionSelect">
<option v-for="section in toolSections" :key="section.name">
{{ section.name }}
</option>
</datalist>
</b-form-group>
<b-link variant="primary" @click="onAdvanced"> {{ advancedTitle }} advanced settings. </b-link>
<b-collapse id="advanced-collapse" v-model="advancedShow" class="mt-2">
<b-card>
<b-form-group v-if="showConfig" label="Tool Configuration:" description="Choose a tool configuration.">
<b-form-radio
v-for="filename in toolConfigs"
v-model="toolConfig"
:value="filename"
:key="filename"
>
{{ filename }}
</b-form-radio>
</b-form-group>
<b-form-group label="Dependencies:" description="Choose how to handle dependencies.">
<b-form-checkbox v-model="installResolverDependencies">
Install resolvable dependencies
</b-form-checkbox>
<b-form-checkbox v-model="installRepositoryDependencies">
Install repository dependencies
</b-form-checkbox>
<b-form-checkbox v-model="installToolDependencies">
Install tool dependencies
</b-form-checkbox>
</b-form-group>
</b-card>
</b-collapse>
</b-modal>
</template>
<script>
import { getGalaxyInstance } from "app";
import { Services } from "./services.js";
export default {
props: ["repo", "changesetRevision", "requiresPanel", "toolshedUrl"],
data() {
return {
modalShow: true,
advancedShow: false,
installToolDependencies: true,
installRepositoryDependencies: true,
installResolverDependencies: true,
toolConfigs: [],
toolConfig: null,
toolSections: [],
toolSection: null
};
},
computed: {
advancedTitle() {
return this.advancedShow ? "Hide" : "Show";
},
modalTitle() {
return `Installing '${this.repo.name}'`;
},
showConfig() {
return this.toolConfigs.length > 1;
}
},
created() {
this.services = new Services();
this.loadConfig();
},
methods: {
findSection: function(name) {
const result = ['', ''];
if (name) {
const found = this.toolSections.find(s => {
return s.name.toLowerCase().trim() == name.toLowerCase().trim();
});
if (found) {
result[0] = found.id;
} else {
result[1] = name;
}
}
return result;
},
loadConfig: function() {
const galaxy = getGalaxyInstance();
const sections = galaxy.config.toolbox_in_panel;
this.toolSections = sections.filter(x => x.model_class == "ToolSection");
this.toolConfigs = galaxy.config.tool_dynamic_configs || [];
if (this.toolConfigs.length > 0) {
this.toolConfig = this.toolConfigs[0];
}
},
onAdvanced: function() {
this.advancedShow = !this.advancedShow;
},
onOk: function() {
const [sectionId, sectionLabel] = this.findSection(this.toolSection);
this.services
.installRepository({
tool_shed_url: this.toolshedUrl,
name: this.repo.name,
owner: this.repo.repo_owner_username,
changeset_revision: this.changesetRevision,
new_tool_panel_section_label: sectionLabel,
tool_panel_section_id: sectionId,
shed_tool_conf: this.toolConfig,
install_resolver_dependencies: this.installResolverDependencies,
install_tool_dependencies: this.installToolDependencies,
install_repository_dependencies: this.installRepositoryDependencies
})
.then(response => {
window.console.log(response);
this.$emit("ok");
})
.catch(error => {});
},
onHide: function() {
this.$emit("hide");
}
}
};
</script>
@@ -0,0 +1,102 @@
<template>
<div>
<b-table striped :items="repositories" :fields="fields">
<template slot="name" slot-scope="row">
<b-link href="#" class="font-weight-bold" @click="row.toggleDetails">
{{ row.item.name }}
</b-link>
</template>
<template slot="row-details" slot-scope="row">
<repositorydetails :repo="row.item" :toolshedUrl="toolshedUrl" />
</template>
</b-table>
<div v-if="noResultsFound">
No matching repositories found.
</div>
<div v-if="pageLoading">
<span class="fa fa-spinner fa-spin mb-4" />
<span>Loading repositories...</span>
</div>
</div>
</template>
<script>
import RepositoryDetails from "./RepositoryDetails.vue";
import { Services } from "./services.js";
const READY = 0;
const LOADING = 1;
const COMPLETE = 2;
export default {
components: {
repositorydetails: RepositoryDetails
},
props: ["query", "scrolled", "toolshedUrl"],
data() {
return {
repositories: [],
fields: [
{ key: "name" },
{ key: "description" },
{ key: "last_updated", label: "Updated" },
{ key: "repo_owner_username", label: "Owner" },
{ key: "times_downloaded", label: "Downloaded" }
],
page: 1,
pageSize: 10,
pageState: READY,
error: null
};
},
watch: {
toolshedUrl() {
this.load();
},
query() {
this.load();
},
scrolled() {
if (this.scrolled && this.pageState === READY) {
this.load(this.page + 1);
}
}
},
computed: {
pageLoading() {
return this.pageState === LOADING;
},
noResultsFound() {
return this.repositories.length === 0 && !this.pageLoading;
}
},
created() {
this.services = new Services();
this.load();
},
methods: {
load(page = 1) {
this.page = page;
this.pageState = LOADING;
if (page == 1) {
this.repositories = [];
}
this.services
.getRepositories({
tool_shed_url: this.toolshedUrl,
q: this.query,
page: this.page,
page_size: this.pageSize
})
.then(incoming => {
this.repositories = this.repositories.concat(incoming);
if (incoming.length < this.pageSize) {
this.pageState = COMPLETE;
} else {
this.pageState = READY;
}
})
.catch(errorMessage => {
this.$emit("onError", errorMessage);
});
}
}
};
</script>
@@ -0,0 +1,177 @@
<template>
<b-card>
<div class="mb-1">{{ repo.long_description }}</div>
<div class="mb-3">
<b-link :href="repo.repository_url" target="_blank">Show additional details and dependencies.</b-link>
</div>
<div>
<span v-if="loading">
<span class="fa fa-spinner fa-spin" />
Loading repository details...
</span>
<div v-else>
<b-alert v-if="error" variant="danger" show>
{{ error }}
</b-alert>
<div v-else class="border rounded">
<b-table borderless :items="repoTable" :fields="repoFields" class="text-center m-0">
<template slot="numeric_revision" slot-scope="data">
<span class="font-weight-bold">{{ data.value }}</span>
</template>
<template slot="tools" slot-scope="data">
<repositorytools :tools="data.value" />
</template>
<template slot="profile" slot-scope="data">
{{ data.value ? `+${data.value}` : "-" }}
</template>
<template slot="missing_test_components" slot-scope="data">
<span v-if="!data.value" :class="repoChecked" />
<span v-else :class="repoUnchecked" />
</template>
<template slot="actions" slot-scope="row">
<installationbutton
:installed="row.item.installed"
:status="row.item.status"
@onInstall="setupRepository(row.item)"
@onUninstall="uninstallRepository(row.item)"
/>
</template>
</b-table>
<installationsettings
v-if="showSettings"
:repo="repo"
:toolshedUrl="toolshedUrl"
:changesetRevision="selectedChangeset"
:requiresPanel="selectedRequiresPanel"
@hide="onHide"
@ok="onOk"
/>
</div>
</div>
</div>
</b-card>
</template>
<script>
import { Services } from "./services.js";
import InstallationSettings from "./InstallationSettings.vue";
import InstallationButton from "./InstallationButton.vue";
import RepositoryTools from "./RepositoryTools.vue";
export default {
components: {
installationsettings: InstallationSettings,
installationbutton: InstallationButton,
repositorytools: RepositoryTools
},
props: ["repo", "toolshedUrl"],
data() {
return {
repoChecked: "fa fa-check text-success",
repoUnchecked: "fa fa-times text-danger",
selectedChangeset: null,
selectedRequiresPanel: false,
repoTable: [],
repoFields: {
numeric_revision: {
label: "Revision"
},
tools: {
label: "Tools and Versions"
},
profile: {
label: "Requires"
},
missing_test_components: {
label: "Tests"
},
actions: {
label: ""
}
},
showSettings: false,
error: null,
loading: true,
delay: 2000
};
},
created() {
this.services = new Services();
this.loadDetails();
},
destroyed() {
this.clearTimeout();
},
methods: {
clearTimeout() {
if (this.timeout) {
clearTimeout(this.timeout);
}
},
loadDetails() {
this.services
.getDetails(this.toolshedUrl, this.repo.id)
.then(response => {
this.repoTable = response;
this.loadInstalledRepositories();
})
.catch(error => {
this.error = error;
this.loading = false;
});
},
listenInstalledRepositories() {
this.clearTimeout();
this.timeout = setTimeout(() => {
this.loadInstalledRepositories();
}, this.delay);
},
loadInstalledRepositories() {
this.services
.getInstalledRepositories(this.repo)
.then(revisions => {
let changed = false;
this.repoTable.forEach(x => {
const revision = revisions[x.changeset_revision];
if (revision && revision.status !== x.status) {
x.status = revision.status;
x.installed = revision.installed;
changed = true;
return false;
}
});
if (changed) {
this.repoTable = [...this.repoTable];
}
this.listenInstalledRepositories();
this.loading = false;
})
.catch(error => {
this.error = error;
this.loading = false;
});
},
onOk: function() {
this.showSettings = false;
},
onHide: function() {
this.showSettings = false;
},
setupRepository: function(details) {
this.selectedChangeset = details.changeset_revision;
this.selectedRequiresPanel = details.includes_tools_for_display_in_tool_panel || details.repository.type == "repository_suite_definition";
this.showSettings = true;
},
uninstallRepository: function(details) {
this.services
.uninstallRepository({
tool_shed_url: this.toolshedUrl,
name: this.repo.name,
owner: this.repo.repo_owner_username,
changeset_revision: details.changeset_revision
})
.catch(error => {
this.error = error;
});
}
}
};
</script>
@@ -0,0 +1,67 @@
<template>
<table v-if="tools" class="table table-borderless">
<tbody>
<tr v-if="canExpand && expanded" class="bg-transparent">
<td :class="clsFirstColumn">
<a href="#" @click.stop.prevent="onExpand">
<span class="fa fa-angle-double-up" />
<span>Show less</span>
</a>
</td>
<td :class="clsSecondColumn" />
</tr>
<tr v-for="tool in visibleTools" :key="tool.guid" :class="clsRow">
<td :class="clsFirstColumn">
{{ tool.id }}
</td>
<td :class="clsSecondColumn">
{{ tool.version }}
</td>
</tr>
<tr v-if="canExpand && !expanded" class="bg-transparent">
<td :class="clsFirstColumn">
<a href="#" @click.stop.prevent="onExpand">
<span class="fa fa-angle-double-down" />
<span>Show more</span>
</a>
</td>
<td :class="clsSecondColumn" />
</tr>
</tbody>
</table>
<span v-else>-</span>
</template>
<script>
export default {
props: ["tools"],
data() {
return {
clsExpanded: "bg-transparent border-bottom",
clsCollapsed: "bg-transparent",
clsFirstColumn: "p-0 w-50 text-left",
clsSecondColumn: "p-0 w-50 text-center",
expanded: false,
preview: 2
};
},
computed: {
clsRow() {
return this.expanded ? this.clsExpanded : this.clsCollapsed;
},
canExpand() {
return this.tools && this.tools.length > this.preview;
},
visibleTools() {
if (this.canExpand && !this.expanded) {
return this.tools.slice(0, this.preview);
}
return this.tools;
}
},
methods: {
onExpand() {
this.expanded = !this.expanded;
}
}
};
</script>
@@ -0,0 +1,40 @@
<template>
<div v-if="!loading" class="m-1 text-muted">
{{ total }} repositories available at
<span v-if="showDropdown" class="dropdown">
<b-link id="dropdownToolshedUrl" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ toolshedUrl }}
</b-link>
<div class="dropdown-menu" aria-labelledby="dropdownToolshedUrl">
<a
v-for="url in toolshedUrls"
:key="url"
class="dropdown-item"
href="#"
@click.prevent="onToolshed(url)"
>{{ url }}</a
>
</div>
</span>
<span v-else>
<b-link :href="toolshedUrl" target="_blank">
{{ toolshedUrl }}
</b-link>
</span>
</div>
</template>
<script>
export default {
props: ["toolshedUrl", "toolshedUrls", "loading", "total"],
computed: {
showDropdown() {
return this.toolshedUrls.length > 1;
}
},
methods: {
onToolshed(url) {
this.$emit("onToolshed", url);
}
}
};
</script>
@@ -0,0 +1,110 @@
import axios from "axios";
import { getAppRoot } from "onload/loadConfig";
/** Request repositories, categories etc from toolshed server **/
export class Services {
async getCategories(toolshedUrl) {
const paramString = `tool_shed_url=${toolshedUrl}&controller=categories`;
const url = `${getAppRoot()}api/tool_shed/request?${paramString}`;
try {
const response = await axios.get(url);
return response.data;
} catch (e) {
return this._errorMessage(e);
}
}
async getRepositories(params) {
params["controller"] = "repositories";
const paramString = this._getParamString(params);
const url = `${getAppRoot()}api/tool_shed/request?${paramString}`;
try {
const response = await axios.get(url);
const data = response.data;
const incoming = data.hits.map(x => x.repository);
incoming.forEach(x => {
x.times_downloaded = this._formatCount(x.times_downloaded);
x.repository_url = `${data.hostname}repository?repository_id=${x.id}`;
});
return incoming;
} catch (e) {
return this._errorMessage(e);
}
}
async getDetails(toolshedUrl, repository_id) {
const paramString = `tool_shed_url=${toolshedUrl}&id=${repository_id}&controller=repositories&action=metadata`;
const url = `${getAppRoot()}api/tool_shed/request?${paramString}`;
try {
const response = await axios.get(url);
const data = response.data;
const table = Object.keys(data).map(key => data[key]);
table.sort((a, b) => b.numeric_revision - a.numeric_revision);
table.forEach(x => {
if (Array.isArray(x.tools)) {
x.profile = x.tools.reduce(
(value, current) => (current.profile > value ? current.profile : value),
null
);
}
});
return table;
} catch (e) {
return `${this._errorMessage(e)}, ${url}`;
}
}
async getInstalledRepositories(repo) {
const paramsString = `name=${repo.name}&owner=${repo.repo_owner_username}`;
const url = `${getAppRoot()}api/tool_shed_repositories?${paramsString}`;
try {
const response = await axios.get(url);
const data = response.data;
const result = {};
data.forEach(x => {
const d = {
status: x.status,
installed: !x.deleted && !x.uninstalled
};
result[x.changeset_revision] = result[x.installed_changeset_revision] = d;
});
return result;
} catch (e) {
this._errorMessage(e);
}
}
async installRepository(payload) {
const url = `${getAppRoot()}api/tool_shed_repositories`;
try {
const response = await axios.post(url, payload);
return response.data;
} catch (e) {
return this._errorMessage(e);
}
}
async uninstallRepository(params) {
const paramsString = Object.keys(params).reduce(function(previous, key) {
return `${previous}${key}=${params[key]}&`;
}, "");
const url = `${getAppRoot()}api/tool_shed_repositories?${paramsString}`;
try {
const response = await axios.delete(url);
return response.data;
} catch (e) {
return this._errorMessage(e);
}
}
_formatCount(value) {
if (value > 1000) return `>${Math.floor(value / 1000)}k`;
return value;
}
_errorMessage(e) {
let message = "Request failed.";
if (e.response) {
message = e.response.data.err_msg || `${e.response.statusText} (${e.response.status})`;
}
return message;
}
_getParamString(params) {
return Object.keys(params).reduce(function(previous, key) {
return `${previous}${key}=${params[key]}&`;
}, "");
}
}
@@ -13,6 +13,7 @@ import DataManagerRouter from "components/admin/DataManager/DataManagerRouter.vu
import Register from "components/login/Register.vue";
import ErrorStack from "components/admin/ErrorStack.vue";
import DisplayApplications from "components/admin/DisplayApplications.vue";
import Toolshed from "components/Toolshed/Index.vue";
import Vue from "vue";
export const getAdminRouter = (Galaxy, options) => {
@@ -25,6 +26,7 @@ export const getAdminRouter = (Galaxy, options) => {
"(/)admin(/)users(/)create": "show_users_create",
"(/)admin(/)roles": "show_roles",
"(/)admin(/)groups": "show_groups",
"(/)admin(/)toolshed": "show_toolshed",
"(/)admin(/)error_stack": "show_error_stack",
"(/)admin(/)display_applications": "show_display_applications",
"(/)admin(/)tool_versions": "show_tool_versions",
@@ -84,6 +86,10 @@ export const getAdminRouter = (Galaxy, options) => {
this._show_grid_view("admin_toolshed/browse_repositories");
},
show_toolshed: function() {
this._display_vue_helper(Toolshed);
},
show_tool_versions: function() {
this._show_grid_view("admin/tool_versions_list");
},
@@ -88,7 +88,13 @@ const AdminPanel = Backbone.View.extend({
title: _l("Tool Management"),
items: [
{
title: _l("Install new tools"),
title: _l("Install or Uninstall"),
url: "admin/toolshed",
target: "__use_router__",
enabled: this.settings.is_tool_shed_installed
},
{
title: _l("Install new tools (Legacy)"),
url: "admin_toolshed/browse_tool_sheds",
enabled: this.settings.is_tool_shed_installed
},
+24 -24
View File
@@ -113,36 +113,36 @@ const SidePanel = Backbone.View.extend({
},
show: function() {
if (!this.hidden) {
return;
if (this.hidden && !this.motionQueue) {
this.motionQueue = 2;
const animation = {};
const whichSide = this.id;
animation[whichSide] = 0;
this.$el
.css(whichSide, -this.saved_size)
.css("width", this.saved_size)
.animate(animation, "fast", () => this.motionQueue--);
animation[whichSide] = this.saved_size;
this.$center().animate(animation, "fast", () => this.motionQueue--);
this.hidden = false;
this.$toggleButton().removeClass("hidden");
}
const self = this;
const animation = {};
const whichSide = this.id;
animation[whichSide] = 0;
self.$el
.css(whichSide, -this.saved_size)
.show()
.animate(animation, "fast", () => {
self.resize(self.saved_size);
});
self.hidden = false;
self.$toggleButton().removeClass("hidden");
return this;
},
hide: function() {
if (this.hidden) {
return;
if (!this.hidden && !this.motionQueue) {
this.motionQueue = 2;
const animation = {};
const whichSide = this.id;
this.saved_size = this.$el.width();
animation[whichSide] = -this.saved_size;
this.$el.animate(animation, "fast", () => this.motionQueue--);
animation[whichSide] = 0;
this.$center().animate(animation, "fast", () => this.motionQueue--);
this.hidden = true;
this.$toggleButton().addClass("hidden");
}
const animation = {};
const whichSide = this.id;
this.saved_size = this.$el.width();
animation[whichSide] = -this.saved_size;
this.$el.animate(animation, "fast");
this.$center().css(whichSide, 0);
this.hidden = true;
this.$toggleButton().addClass("hidden");
return this;
},
@@ -278,7 +278,7 @@ function _makeSection(output_id, label, options) {
options: extensions,
help: "This action will change the datatype of the output to the indicated datatype.",
onchange: function(new_value) {
if (new_value === '__empty__') {
if (new_value === "__empty__") {
new_value = null;
}
workflow.updateDatatype(node, output_id, new_value);
@@ -66,13 +66,13 @@ var Node = Backbone.Model.extend({
output_terminal.force_datatype = datatype;
output.force_datatype = datatype;
if (datatype) {
this.post_job_actions['ChangeDatatypeAction' + outputName] = {
action_arguments: {newtype: datatype},
this.post_job_actions["ChangeDatatypeAction" + outputName] = {
action_arguments: { newtype: datatype },
action_type: "ChangeDatatypeAction",
output_name: outputName,
output_name: outputName
};
} else {
delete this.post_job_actions['ChangeDatatypeAction' + outputName];
delete this.post_job_actions["ChangeDatatypeAction" + outputName];
}
this.markChanged();
output_terminal.destroyInvalidConnections();
@@ -94,8 +94,8 @@ export default Backbone.View.extend({
const terminalView = new terminalViewClass({
node: this.node,
output: output
})
return terminalView
});
return terminalView;
},
outputViewforOutput: function(output, terminalView) {
@@ -105,7 +105,7 @@ export default Backbone.View.extend({
terminalElement: terminalView.el,
nodeView: this
});
return outputView
return outputView;
},
addDataOutput: function(output) {
@@ -292,7 +292,7 @@ var OutputTerminalView = BaseOutputTerminalView.extend({
var terminal = new Terminals.OutputTerminal({
element: this.el,
datatypes: type,
force_datatype: output.force_datatype,
force_datatype: output.force_datatype
});
return terminal;
}
@@ -309,7 +309,7 @@ var OutputCollectionTerminalView = BaseOutputTerminalView.extend({
collection_type: collection_type,
collection_type_source: collection_type_source,
datatypes: output.extensions,
force_datatype: output.force_datatype,
force_datatype: output.force_datatype
});
return terminal;
}
@@ -73,7 +73,7 @@ QUnit.module("Input terminal model test", {
};
}
return this.input_terminal.canAccept(other).canAccept;
},
}
});
QUnit.test("test update", function(assert) {
@@ -136,12 +136,12 @@ QUnit.test("cannot accept incorrect datatype", function(assert) {
});
QUnit.test("can accept incorrect datatype if converted with PJA", function(assert) {
var other = { node: {}, datatypes: ["binary"], force_datatype: 'txt', name: "out1" }; // Was binary but converted to txt
var other = { node: {}, datatypes: ["binary"], force_datatype: "txt", name: "out1" }; // Was binary but converted to txt
assert.ok(this.test_accept(other));
});
QUnit.test("cannot accept incorrect datatype if converted with PJA to incompatible type", function(assert) {
var other = { node: {}, datatypes: ["binary"], force_datatype: 'bam', name: "out1" };
var other = { node: {}, datatypes: ["binary"], force_datatype: "bam", name: "out1" };
assert.ok(!this.test_accept(other));
});
+34 -43
View File
@@ -140,7 +140,6 @@ body {
}
// Panels
#left,
#center,
#right {
@@ -148,19 +147,22 @@ body {
top: 0;
height: 100%;
overflow: hidden;
background: #fff;
background: white;
}
#left {
left: 0px;
width: $panel-width;
z-index: 200;
border-right: $layout-border;
}
#right {
right: 0px;
width: $panel-width;
border-left: $layout-border;
}
#center {
left: $panel-width;
right: $panel-width;
overflow: hidden;
z-index: 1;
.center-container {
position: absolute;
width: 100%;
@@ -189,21 +191,6 @@ body {
}
}
#right {
width: $panel-width;
right: 0px;
z-index: 200;
border-left: $layout-border;
}
#left,
#right,
.unified-panel {
background: $panel-bg-color;
display: flex;
flex-direction: column;
}
#left,
#right,
.unified-panel {
@@ -216,7 +203,6 @@ body {
@extend .unselectable;
@extend .mx-3;
height: $panel_header_height;
z-index: 1000;
font-size: 1rem;
font-weight: bold;
display: flex;
@@ -255,7 +241,6 @@ body {
height: $panel_footer_height;
line-height: $panel_footer_height;
width: 100%;
z-index: 1000; // Above #dd-helper
border-top: $layout-border;
background: $panel-footer-bg-color;
color: $panel-header-text-color;
@@ -285,42 +270,48 @@ body {
// Needs to be outside panel to show when hidden
.panel-collapse {
@extend .unified-panel-footer;
font-size: 4/3em;
@extend .fa-icon;
&:before {
content: "\f053";
}
&.hidden:before {
content: "\f054";
}
z-index: 201;
$border-color: darken($panel-footer-bg-color, 20%);
font-size: 4/3em;
cursor: pointer;
display: block;
position: fixed;
left: 0;
top: inherit;
bottom: 0;
padding: 0 5px;
text-align: center;
height: $panel_footer_height;
line-height: $panel_footer_height;
width: $panel_footer_height - 5;
background: none;
border-right: $layout-border;
border-top: $layout-border;
width: $panel_footer_height;
background: $panel-footer-bg-color;
border: solid $panel-footer-bg-color 1px;
z-index: 1;
&.right {
left: inherit;
right: 0;
border-right: none;
border-left: $layout_border;
&.right:before {
border-top-left-radius: $border-radius-base;
line-height: inherit;
&:before {
content: "\f054";
}
&.right.hidden:before {
&.hidden:before {
content: "\f053";
}
&.hidden {
border-top-color: $border-color;
border-left-color: $border-color;
}
}
&.left {
left: 0;
border-top-right-radius: $border-radius-base;
line-height: inherit;
&:before {
content: "\f053";
}
&.hidden:before {
content: "\f054";
}
&.hidden {
border-top-color: $border-color;
border-right-color: $border-color;
}
}
}
-11
View File
@@ -29,7 +29,6 @@ from galaxy.job_execution import output_collect
from galaxy.managers.jobs import JobSearch
from galaxy.metadata import get_metadata_compute_strategy
from galaxy.model.tags import GalaxyTagHandler
from galaxy.queue_worker import send_control_task
from galaxy.tool_util.deps import (
CachedDependencyManager,
)
@@ -242,16 +241,6 @@ class ToolBox(BaseGalaxyToolBox):
app=app,
)
def handle_panel_update(self, section_dict):
"""
Sends a panel update to all threads/processes.
"""
send_control_task(self.app, 'create_panel_section', kwargs=section_dict)
# The following local call to self.create_section should be unnecessary
# but occasionally the local ToolPanelElements instance appears to not
# get updated.
self.create_section(section_dict)
def has_reloaded(self, other_toolbox):
return self._reload_count != other_toolbox._reload_count
+1 -8
View File
@@ -98,13 +98,6 @@ class AbstractToolBox(Dictifiable, ManagesIntegratedToolPanelMixin):
self._load_tool_panel()
self._save_integrated_tool_panel()
def handle_panel_update(self, section_dict):
"""Extension-point for Galaxy-app specific reload logic.
This abstract representation of the toolbox shouldn't have details about
interacting with the rest of the Galaxy app or message queues, etc....
"""
def create_tool(self, config_file, tool_shed_repository=None, guid=None, **kwds):
raise NotImplementedError()
@@ -264,7 +257,7 @@ class AbstractToolBox(Dictifiable, ManagesIntegratedToolPanelMixin):
'id': section_id,
'version': '',
}
self.handle_panel_update(section_dict)
self.create_section(section_dict)
tool_section = self._tool_panel[tool_panel_section_key]
self._save_integrated_tool_panel()
else:
+3 -1
View File
@@ -306,7 +306,9 @@ class JSAppLauncher(BaseUIController):
'toolbox' : trans.app.toolbox.to_dict(trans, in_panel=False),
'toolbox_in_panel' : trans.app.toolbox.to_dict(trans),
'message_box_visible' : trans.app.config.message_box_visible,
'show_inactivity_warning' : trans.app.config.user_activation_on and trans.user and not trans.user.active
'show_inactivity_warning' : trans.app.config.user_activation_on and trans.user and not trans.user.active,
'tool_shed_urls' : list(trans.app.tool_shed_registry.tool_sheds.values()) if trans.app.tool_shed_registry else [],
'tool_dynamic_configs' : list(trans.app.toolbox.dynamic_conf_filenames())
}
# TODO: move to user
+24
View File
@@ -12,6 +12,7 @@ from galaxy import (
util,
web
)
from galaxy.exceptions import MessageException
from galaxy.web import expose_api
from galaxy.web.base.controller import BaseAPIController
from tool_shed.util import (
@@ -314,3 +315,26 @@ class ToolShedController(BaseAPIController):
return {}
response = json.loads(util.url_get(tool_shed_url, params=dict(q=q), pathspec=['api', 'repositories']))
return response
@expose_api
@web.require_admin
def request(self, trans, **params):
"""
GET /api/tool_shed/request
"""
tool_shed_url = params.pop("tool_shed_url")
controller = params.pop("controller")
if controller is None:
raise MessageException("Please provide a toolshed controller name.")
tool_shed_registry = trans.app.tool_shed_registry
if tool_shed_registry is None:
raise MessageException("Toolshed registry not available.")
if tool_shed_url in tool_shed_registry.tool_sheds.values():
pathspec = ["api", controller]
if "id" in params:
pathspec.append(params.pop("id"))
if "action" in params:
pathspec.append(params.pop("action"))
return json.loads(util.url_get(tool_shed_url, params=dict(params), pathspec=pathspec))
else:
raise MessageException("Invalid toolshed url.")
+7
View File
@@ -112,6 +112,7 @@ def app_factory(global_conf, load_app_kwds={}, **kwargs):
webapp.add_client_route('/admin/groups', 'admin')
webapp.add_client_route('/admin/repositories', 'admin')
webapp.add_client_route('/admin/tool_versions', 'admin')
webapp.add_client_route('/admin/toolshed', 'admin')
webapp.add_client_route('/admin/quotas', 'admin')
webapp.add_client_route('/admin/form/{form_id}', 'admin')
webapp.add_client_route('/admin/api_keys', 'admin')
@@ -960,6 +961,12 @@ def populate_api_routes(webapp, app):
action='search',
conditions=dict(method=["GET", "POST"]))
webapp.mapper.connect('tool_shed_request',
'/api/tool_shed/request',
controller='toolshed',
action='request',
conditions=dict(method=["GET"]))
webapp.mapper.connect('tool_shed_status',
'/api/tool_shed/status',
controller='toolshed',
+1 -1
View File
@@ -195,7 +195,7 @@
<div id="left">
${self.left_panel()}
<div class="unified-panel-footer">
<div id="left-panel-collapse" class="panel-collapse"></div>
<div id="left-panel-collapse" class="panel-collapse left"></div>
<div id="left-panel-drag" class="drag"></div>
</div>
</div><!--end left-->
@@ -57,6 +57,9 @@
${parent.stylesheets()}
<style type="text/css">
#center {
z-index: 0;
}
canvas { position: absolute; z-index: 10; }
canvas.dragging { position: absolute; z-index: 1000; }
</style>
-3
View File
@@ -562,9 +562,6 @@ class SimplifiedToolBox(ToolBox):
# Need to start thread now for new reload callback to take effect
self.app.watchers.start()
def handle_panel_update(self, section_dict):
self.create_section(section_dict)
def reload_callback(test_case):
test_case.app.tool_cache.cleanup()