Merge remote-tracking branch 'upstream/dev' into fri18n

This commit is contained in:
Dannon Baker
2017-12-22 13:17:05 -05:00
386 changed files with 6870 additions and 5554 deletions
+34
View File
@@ -0,0 +1,34 @@
#!/bin/sh
if command -v ack-grep >/dev/null; then
ACK=ack-grep
else
ACK=ack
fi
export ACK_OPTIONS=" --type python \
--ignore-dir=.git/ \
--ignore-dir=.tox/ \
--ignore-dir=.venv/ \
--ignore-dir=client/node_modules/ \
--ignore-dir=database/ \
--ignore-dir=doc/build/ \
--ignore-dir=eggs/ \
--ignore-dir=static/maps/ \
--ignore-dir=static/scripts/"
PYTHON2_ONLY_MODULES="__builtin__ _winreg BaseHTTPServer CGIHTTPServer \
ConfigParser Cookie cookielib copy_reg cPickle cStringIO Dialog dummy_thread \
FileDialog gdbm htmlentitydefs HTMLParser httplib Queue robotparser \
ScrolledText SimpleDialog SimpleHTTPServer SimpleXMLRPCServer SocketServer \
StringIO thread Tix tkColorChooser tkCommonDialog Tkconstants Tkdnd tkFont \
Tkinter tkFileDialog tkMessageBox tkSimpleDialog ttk urllib urllib2 urlparse \
xmlrpclib"
ret=0
for mod in $PYTHON2_ONLY_MODULES; do
$ACK "^import $mod(\n|\.)|^from $mod import "
if [ $? -eq 0 ]; then ret=1; fi
done
exit $ret
+2 -2
View File
@@ -10,7 +10,7 @@ bash run.sh --daemon && \
sleep 1
i=$((i + 1))
done
kill -9 "$(cat paster.pid)"
kill -9 "$(cat galaxy.pid)"
echo "exit code:$EXIT_CODE, showing startup log:"
cat paster.log
cat galaxy.log
exit $EXIT_CODE
-94
View File
@@ -1,94 +0,0 @@
client/galaxy/style/source_material/circle.py
contrib/
cron/
doc/parse_gx_xsd.py
doc/patch.py
lib/galaxy/actions/
lib/galaxy/auth/
lib/galaxy/config.py
lib/galaxy/containers/
lib/galaxy/dataset_collections/
lib/galaxy/datatypes/
lib/galaxy/dependencies/
lib/galaxy/eggs/
lib/galaxy/exceptions/
lib/galaxy/external_services/
lib/galaxy/forms/
lib/galaxy/jobs/
lib/galaxy/managers/
lib/galaxy/model/
lib/galaxy/objectstore/
lib/galaxy/openid/
lib/galaxy/quota/
lib/galaxy/sample_tracking/
lib/galaxy/security/
lib/galaxy/tags/
lib/galaxy/tools/
lib/galaxy/tours/
lib/galaxy/util/
lib/galaxy/visualization/
lib/galaxy/web/base/
lib/galaxy/web/buildapp.py
lib/galaxy/web/framework/base.py
lib/galaxy/web/framework/decorators.py
lib/galaxy/web/framework/helpers/grids.py
lib/galaxy/web/framework/__init__.py
lib/galaxy/web/framework/middleware/error.py
lib/galaxy/web/framework/middleware/static.py
lib/galaxy/web/framework/middleware/statsd.py
lib/galaxy/web/framework/webapp.py
lib/galaxy/web/__init__.py
lib/galaxy/webapps/galaxy/api/histories.py
lib/galaxy/webapps/galaxy/api/library_datasets.py
lib/galaxy/webapps/galaxy/api/tours.py
lib/galaxy/webapps/galaxy/api/users.py
lib/galaxy/webapps/galaxy/api/workflows.py
lib/galaxy/webapps/galaxy/buildapp.py
lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py
lib/galaxy/webapps/galaxy/controllers/external_services.py
lib/galaxy/webapps/galaxy/controllers/forms.py
lib/galaxy/webapps/galaxy/controllers/history.py
lib/galaxy/webapps/galaxy/controllers/library_common.py
lib/galaxy/webapps/galaxy/controllers/search.py
lib/galaxy/webapps/galaxy/controllers/userskeys.py
lib/galaxy/webapps/reports/config.py
lib/galaxy/webapps/reports/controllers/tools.py
lib/galaxy/webapps/reports/__init__.py
lib/galaxy/webapps/tool_shed/config.py
lib/galaxy/webapps/tool_shed/controllers/groups.py
lib/galaxy/webapps/tool_shed/controllers/user.py
lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py
lib/galaxy/webapps/tool_shed/__init__.py
lib/galaxy/webapps/tool_shed/model/__init__.py
lib/galaxy/webapps/tool_shed/search/repo_search.py
lib/galaxy/webapps/tool_shed/util/ratings_util.py
lib/galaxy/webhooks/
lib/galaxy/work/
lib/galaxy/workflow/
lib/galaxy_ext/
lib/log_tempfile.py
lib/mimeparse.py
lib/psyco_full.py
lib/tool_shed/
scripts/api/
scripts/auth/
scripts/bootstrap_history.py
scripts/build_toolbox.py
scripts/check_eggs.py
scripts/check_galaxy.py
scripts/check_python.py
scripts/cleanup_datasets/
scripts/communication/
scripts/data_libraries/build_whoosh_index.py
scripts/db_shell.py
scripts/drmaa_external_runner.py
scripts/metagenomics/
scripts/microbes/BeautifulSoup.py
scripts/microbes/harvest_bacteria.py
scripts/secret_decoder_ring.py
scripts/tool_shed/api/common.py
scripts/tool_shed/build_ts_whoosh_index.py
scripts/tool_shed/deprecate_repositories_without_metadata.py
test/
tool_list.py
tools/
+11 -5
View File
@@ -9,18 +9,24 @@ env:
- TOX_ENV=py27-unit
- TOX_ENV=qunit
- TOX_ENV=first_startup
- TOX_ENV=validate-test-tools
- TOX_ENV=py27-lint-docstring-include-list
- TOX_ENV=check-python-dependencies
matrix:
include:
- env: TOX_ENV=validate-test-tools
addons:
apt:
packages:
- libxml2-utils
- env: TOX_ENV=check_py3_compatibility
addons:
apt:
packages:
- ack-grep
allow_failures:
- env: TOX_ENV=check-python-dependencies
addons:
apt:
packages:
- libxml2-utils
install:
- pip install tox
+9 -22
View File
@@ -1,26 +1,13 @@
module.exports = function(grunt) {
"use strict";
var GALAXY_PATHS = {
dist : '../static/scripts',
maps : '../static/maps',
// this symlink allows us to serve uncompressed scripts in DEV_PATH for use with sourcemaps
srcSymlink : '../static/src',
},
TOOLSHED_PATHS = {
dist : '../static/toolshed/scripts',
maps : '../static/toolshed/maps',
srcSymlink : '../static/toolshed/src',
};
grunt.config.set( 'app', 'galaxy' );
grunt.config.set( 'paths', GALAXY_PATHS );
if( grunt.option( 'app' ) === 'toolshed' ){
grunt.config.set( 'app', grunt.option( 'app' ) );
grunt.config.set( 'paths', TOOLSHED_PATHS );
}
dist: "../static/scripts",
maps: "../static/maps",
// this symlink allows us to serve uncompressed scripts in DEV_PATH for use with sourcemaps
srcSymlink: "../static/src"
};
grunt.config.set("app", "galaxy");
grunt.config.set("paths", GALAXY_PATHS);
// see the sub directory grunt-tasks/ for individual task definitions
grunt.loadTasks( 'grunt-tasks' );
grunt.registerTask( 'default', [ 'uglify' ] );
grunt.loadTasks("grunt-tasks");
grunt.registerTask("default", ["style"]);
};
-10
View File
@@ -66,13 +66,3 @@ As mentioned above, 'make client' will also rebuild styles. If you *only* want
to run the style task, use the following command from the `client` directory:
yarn run style
The Toolshed Client Build
=========================
The toolshed client is not tightly integrated with the rest of the build
system. To build the toolshed client, execute the following command from the
`client` directory.
yarn run build-toolshed
+22 -22
View File
@@ -5,13 +5,13 @@ import GalaxyApp from "galaxy";
import AdminPanel from "./panels/admin-panel";
import FormWrapper from "mvc/form/form-wrapper";
import GridView from "mvc/grid/grid-view";
import Ui from "mvc/ui/ui-misc";
import QueryStringParsing from "utils/query-string-parsing";
import Router from "layout/router";
import Utils from "utils/utils";
import Page from "layout/page";
import Vue from "libs/vue";
import UserAPIKeys from "components/UserAPIKeys.vue";
import UserAPIKeys from "components/admin/UserAPIKeys.vue";
import DataTables from "components/admin/DataTables.vue";
import Vue from "vue";
window.app = function app(options, bootstrapped) {
window.Galaxy = new GalaxyApp.GalaxyApp(options, bootstrapped);
@@ -28,10 +28,11 @@ window.app = function app(options, bootstrapped) {
"(/)admin(/)repositories": "show_repositories",
"(/)admin(/)forms": "show_forms",
"(/)admin(/)form(/)(:form_id)": "show_form",
"(/)admin/api_keys": "show_user_api_keys"
"(/)admin/api_keys": "show_user_api_keys",
"(/)admin/data_tables": "show_data_tables"
},
authenticate: function(args, name) {
authenticate: function() {
return Galaxy.user && Galaxy.user.id && Galaxy.user.get("is_admin");
},
@@ -39,8 +40,7 @@ window.app = function app(options, bootstrapped) {
this.page.display(
new GridView({
url_base: `${Galaxy.root}admin/users_list`,
url_data: Galaxy.params,
dict_format: true
url_data: Galaxy.params
})
);
},
@@ -49,8 +49,7 @@ window.app = function app(options, bootstrapped) {
this.page.display(
new GridView({
url_base: `${Galaxy.root}admin/roles_list`,
url_data: Galaxy.params,
dict_format: true
url_data: Galaxy.params
})
);
},
@@ -59,8 +58,7 @@ window.app = function app(options, bootstrapped) {
this.page.display(
new GridView({
url_base: `${Galaxy.root}admin/groups_list`,
url_data: Galaxy.params,
dict_format: true
url_data: Galaxy.params
})
);
},
@@ -69,8 +67,7 @@ window.app = function app(options, bootstrapped) {
this.page.display(
new GridView({
url_base: `${Galaxy.root}admin_toolshed/browse_repositories`,
url_data: Galaxy.params,
dict_format: true
url_data: Galaxy.params
})
);
},
@@ -79,8 +76,7 @@ window.app = function app(options, bootstrapped) {
this.page.display(
new GridView({
url_base: `${Galaxy.root}admin/tool_versions_list`,
url_data: Galaxy.params,
dict_format: true
url_data: Galaxy.params
})
);
},
@@ -89,24 +85,28 @@ window.app = function app(options, bootstrapped) {
this.page.display(
new GridView({
url_base: `${Galaxy.root}admin/quotas_list`,
url_data: Galaxy.params,
dict_format: true
url_data: Galaxy.params
})
);
},
show_user_api_keys: function() {
var vuemount = document.createElement("div");
this.page.display(vuemount);
new Vue(UserAPIKeys).$mount(vuemount);
var vueMount = document.createElement("div");
this.page.display(vueMount);
new Vue(UserAPIKeys).$mount(vueMount);
},
show_data_tables: function() {
var vueMount = document.createElement("div");
this.page.display(vueMount);
new Vue(DataTables).$mount(vueMount);
},
show_forms: function() {
this.page.display(
new GridView({
url_base: `${Galaxy.root}forms/forms_list`,
url_data: Galaxy.params,
dict_format: true
url_data: Galaxy.params
})
);
},
+13 -6
View File
@@ -22,7 +22,7 @@ import Ui from "mvc/ui/ui-misc";
import DatasetError from "mvc/dataset/dataset-error";
import DatasetEditAttributes from "mvc/dataset/dataset-edit-attributes";
import Citations from "components/Citations.vue";
import Vue from "libs/vue";
import Vue from "vue";
/** define the 'Analyze Data'/analysis/main/home page for Galaxy
* * has a masthead
@@ -47,6 +47,7 @@ window.app = function app(options, bootstrapped) {
"(/)tours(/)(:tour_id)": "show_tours",
"(/)user(/)": "show_user",
"(/)user(/)(:form_id)": "show_user_form",
"(/)openids(/)list": "show_openids",
"(/)pages(/)create(/)": "show_pages_create",
"(/)pages(/)edit(/)": "show_pages_edit",
"(/)pages(/)(:action_id)": "show_pages",
@@ -76,7 +77,7 @@ window.app = function app(options, bootstrapped) {
show_tours: function(tour_id) {
if (tour_id) {
Tours.giveTour(tour_id);
Tours.giveTourById(tour_id);
} else {
this.page.display(new Tours.ToursView());
}
@@ -115,8 +116,7 @@ window.app = function app(options, bootstrapped) {
show_workflows_published: function() {
this.page.display(
new GridView({
url_base: `${Galaxy.root}workflow/list_published`,
dict_format: true
url_base: `${Galaxy.root}workflow/list_published`
})
);
},
@@ -157,11 +157,18 @@ window.app = function app(options, bootstrapped) {
);
},
show_openids: function() {
this.page.display(
new GridView({
url_base: `${Galaxy.root}user/openids_list`
})
);
},
show_datasets: function() {
this.page.display(
new GridView({
url_base: `${Galaxy.root}dataset/list`,
dict_format: true
url_base: `${Galaxy.root}dataset/list`
})
);
},
+150 -2
View File
@@ -1,11 +1,37 @@
import _l from "utils/localization";
import GalaxyApp from "galaxy";
import WorkflowView from "mvc/workflow/workflow-view";
import Trackster from "viz/trackster";
import Circster from "viz/circster";
import Phyloviz from "viz/phyloviz";
import Sweepster from "viz/sweepster";
import GalaxyLibrary from "galaxy.library";
import AdminToolshed from "admin.toolshed";
import Masthead from "layout/masthead";
import user from "mvc/user/user-model";
import Modal from "mvc/ui/ui-modal";
import pagesEditorOnload from "galaxy.pages";
import Data from "mvc/dataset/data";
import History from "mvc/history/history-model";
import StructureView from "mvc/history/history-structure-view";
import HistoryContents from "mvc/history/history-contents";
import MultiPanel from "mvc/history/multi-panel";
import HistoryView from "mvc/history/history-view";
import HistoryViewEdit from "mvc/history/history-view-edit";
import HistoryViewAnnotated from "mvc/history/history-view-annotated";
import HistoryCopyDialog from "mvc/history/copy-dialog";
import HDAListItemEdit from "mvc/history/hda-li-edit";
import HDAModel from "mvc/history/hda-model";
import addLogging from "utils/add-logging";
import LegacyGridView from "legacy/grid/grid-view";
import * as run_stats from "reports/run_stats";
import ToolshedGroups from "toolshed/toolshed.groups";
if (window.Galaxy && window.Galaxy.debug === undefined) {
//TODO: (kind of a temporary hack?) Must have Galaxy.logging for some of the imports
//here; remove when imports are all fixed.
addLogging(window.Galaxy, "GalaxyApp");
}
export function mastheadEntry(options) {
if (!Galaxy.user) {
@@ -34,17 +60,139 @@ export function workflowEntry(options) {
new WorkflowView(options);
}
function libraryEntry(options) {
export function libraryEntry(options) {
new GalaxyLibrary.GalaxyApp(options);
}
export function historyEntry(options) {
$("#toggle-deleted").modeButton({
initialMode: options.initialModeDeleted,
modes: [
{ mode: "showing_deleted", html: _l("Exclude deleted") },
{ mode: "not_showing_deleted", html: _l("Include deleted") }
]
});
$("#toggle-hidden").modeButton({
initialMode: options.initialModeHidden,
modes: [
{ mode: "showing_hidden", html: _l("Exclude hidden") },
{ mode: "not_showing_hidden", html: _l("Include hidden") }
]
});
$("#switch").click(function() {
//##HACK:ity hack hack
//##TODO: remove when out of iframe
var hview =
Galaxy.currHistoryPanel || (window.top.Galaxy && window.top.Galaxy.currHistoryPanel)
? window.top.Galaxy.currHistoryPanel
: null;
if (hview) {
hview.switchToHistory("${ history[ 'id' ] }");
} else {
window.location = "${ switch_to_url }";
}
});
// use_panels effects where the the center_panel() is rendered:
// w/o it renders to the body, w/ it renders to #center - we need to adjust a few things for scrolling to work
if (options.hasMasthead) {
$("#center").addClass("flex-vertical-container");
}
let viewClass = options.userIsOwner ? HistoryViewEdit.HistoryViewEdit : HistoryView.HistoryView;
let historyModel = new History.History(options.historyJSON);
// attach the copy dialog to the import button now that we have a history
$("#import").click(function() {
HistoryCopyDialog(historyModel, {
useImport: true,
// use default datasets option to match the toggle-deleted button
allDatasets: $("#toggle-deleted").modeButton("getMode").mode === "showing_deleted"
}).done(function() {
if (window === window.parent) {
window.location = Galaxy.root;
} else if (Galaxy.currHistoryPanel) {
Galaxy.currHistoryPanel.loadCurrentHistory();
}
});
});
let historyView = new viewClass({
el: $("#history-" + options.historyJSON.id),
className: viewClass.prototype.className + " wide",
$scrollContainer: options.hasMasthead
? function() {
return this.$el.parent();
}
: undefined,
model: historyModel,
show_deleted: options.showDeletedJson,
show_hidden: options.showHiddenJson,
purgeAllowed: options.allow_user_dataset_purge
});
historyView.trigger("loading");
historyModel
.fetchContents({ silent: true })
.fail(function() {
alert("Galaxy history failed to load");
})
.done(function() {
historyView.trigger("loading-done");
historyView.render();
});
$("#toggle-deleted").on("click", function() {
historyView.toggleShowDeleted();
});
$("#toggle-hidden").on("click", function() {
historyView.toggleShowHidden();
});
}
export function multiHistoryEntry(options) {
let histories = new History.HistoryCollection([], {
includeDeleted: options.includingDeleted,
order: options.order,
limitOnFirstFetch: options.limit,
limitPerFetch: options.limit,
currentHistoryId: options.current_history_id
});
let multipanel = new MultiPanel.MultiPanelColumns({
el: $("#center").get(0),
histories: histories
});
histories.fetchFirst({ silent: true }).done(function() {
multipanel.createColumns();
multipanel.render(0);
});
}
export const bundleEntries = {
library: libraryEntry,
masthead: mastheadEntry,
workflow: workflowEntry,
trackster: tracksterEntry,
circster: circsterEntry,
adminToolshed: adminToolshedEntry
adminToolshed: adminToolshedEntry,
pages: pagesEditorOnload,
phyloviz: Phyloviz.PhylovizView,
createTabularDatasetChunkedView: Data.createTabularDatasetChunkedView,
multiHistory: multiHistoryEntry,
history: historyEntry,
History: History.History,
HistoryContents: HistoryContents.HistoryContents,
SweepsterVisualization: Sweepster.SweepsterVisualization,
SweepsterVisualizationView: Sweepster.SweepsterVisualizationView,
HistoryCopyDialog,
HistoryViewAnnotated,
Trackster,
StructureView,
HDAListItemEdit,
HDAModel,
GalaxyApp,
LegacyGridView,
run_stats,
ToolshedGroups
};
window.bundleEntries = bundleEntries;
@@ -22,7 +22,8 @@ var AdminPanel = Backbone.View.extend({
},
{
title: _l("Data tables"),
url: "admin/view_tool_data_tables"
url: "admin/data_tables",
target: "__use_router__"
},
{
title: _l("Display applications"),
@@ -59,7 +59,7 @@ var ToolPanel = Backbone.View.extend({
self.$("#internal-workflows").append(
self._templateAllWorkflow({
title: _l("All workflows"),
href: "workflow"
href: "workflows/list"
})
);
_.each(this.stored_workflow_menu_entries, menu_entry => {
@@ -0,0 +1,20 @@
<template>
<div v-if="message" :class="[status + 'message']">{{ message }}</div>
</template>
<script>
export default {
name: 'Message',
props: {
message: {
type: String,
required: true
},
status: {
type: String,
default: 'done'
}
}
}
</script>
@@ -0,0 +1,43 @@
<template>
<div class="toolForm">
<div class="toolFormTitle">
<slot name="title">{{ title }}</slot>
</div>
<div class="toolFormBody">
<table class="manage-table colored" border="0" cellspacing="0" cellpadding="0" width="100%">
<slot name="columns">
<th v-for="column in columns" bgcolor="#D8D8D8">{{ column.text }}</th>
</slot>
<slot name="rows">
<template v-for="(row, index) in rows">
<tr :class="[index % 2 === 0 ? 'tr' : 'odd_row']">
<td v-for="column in columns">{{ row[column.dataIndex] }}</td>
</tr>
</template>
</slot>
</table>
<div v-if="isLoaded !== undefined && !isLoaded" :style="{ textAlign: 'center', padding: '7px 0' }">
Loading...
</div>
</div>
</div>
</template>
<script>
export default {
props: {
isLoaded: {
type: Boolean
},
title: {
type: String
},
columns: {
type: Array
},
rows: {
type: Array
}
}
}
</script>
@@ -0,0 +1,47 @@
<template>
<table class="tabletip">
<thead>
<tr>
<th :colspan="dataManagerColumns.length" style="font-size:120%">
Data Manager: {{ dataManagerTableName }}
<a class="icon-btn" href="javascript:void(0)"
@click="handleReloadButtonClick"
:title="`Reload ${dataManagerTableName} tool data table`"
><span class="fa fa-refresh"></span></a>
</th>
</tr>
<tr>
<th v-for="column in dataManagerColumns">{{ column }}</th>
</tr>
</thead>
<tbody v-for="item in dataManagerItems">
<tr>
<td v-for="column in dataManagerColumns">{{ item[column] }}</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
props: {
dataManagerTableName: {
type: String,
required: true
},
dataManagerColumns: {
type: Array,
required: true
},
dataManagerItems: {
type: Array,
required: true
}
},
methods: {
handleReloadButtonClick(event) {
this.$emit('reloaddatamanager', this.dataManagerTableName);
}
}
}
</script>
@@ -0,0 +1,117 @@
<template>
<div>
<message :message="message" :status="status"></message>
<component
:is="currentView"
v-if="status !== 'error'"
v-bind="currentProps"
@changeview="showDataManager"
@reloaddatamanager="reloadDataManager">
</component>
</div>
</template>
<script>
import axios from 'axios';
import Message from '../Message.vue';
import DataTablesGrid from './DataTablesGrid.vue';
import DataManagerGrid from './DataManagerGrid.vue';
export default {
data() {
return {
currentView: 'data-tables-grid',
isLoaded: false,
dataTables: [],
dataManagerTableName: '',
dataManagerColumns: [],
dataManagerItems: [],
message: '',
status: ''
}
},
components: {
message: Message,
'data-tables-grid': DataTablesGrid,
'data-manager-grid': DataManagerGrid
},
computed: {
currentProps() {
let props;
if (this.currentView === 'data-tables-grid') {
props = {
isLoaded: this.isLoaded,
rows: this.dataTables
};
} else {
props = {
dataManagerTableName: this.dataManagerTableName,
dataManagerColumns: this.dataManagerColumns,
dataManagerItems: this.dataManagerItems
};
}
return props;
}
},
methods: {
showDataManager(dataManagerTableName) {
axios.get(`${Galaxy.root}data_manager/tool_data_table_items`, {
params: {
table_name: dataManagerTableName
}
})
.then((response) => {
this.message = response.data.message;
this.status = response.data.status;
if (response.data.status !== 'error' && response.data.status !== 'warning') {
this.dataManagerTableName = dataManagerTableName;
this.dataManagerColumns = response.data.data.columns;
this.dataManagerItems = response.data.data.items;
this.currentView = 'data-manager-grid';
}
})
.catch((error) => {
console.error(error);
});
},
reloadDataManager(dataManagerTableName) {
axios.get(`${Galaxy.root}data_manager/reload_tool_data_table`, {
params: {
table_name: dataManagerTableName
}
})
.then((response) => {
this.message = response.data.message;
this.status = response.data.status;
if (response.data.status !== 'error' && response.data.status !== 'warning') {
this.dataManagerItems = response.data.data.items;
}
})
.catch((error) => {
console.error(error);
});
}
},
created() {
axios.get(`${Galaxy.root}admin/data_tables_list`)
.then((response) => {
this.isLoaded = true;
this.dataTables = response.data.data;
this.message = response.data.message;
this.status = response.data.status;
})
.catch((error) => {
console.error(error);
});
}
}
</script>
@@ -0,0 +1,53 @@
<template>
<base-grid :is-loaded="isLoaded" :columns="columns">
<template slot="title">
Current data table registry contains {{ rows.length }} data tables
</template>
<template slot="rows" v-for="(row, index) in rows">
<tr :class="[index % 2 === 0 ? 'tr' : 'odd_row']">
<td><a href="javascript:void(0)" @click="handleTableNameClick">{{ row.name }}</a></td>
<td>{{ row.filename }}</td>
<td>{{ row.tool_data_path }}</td>
<td>{{ row.errors }}</td>
</tr>
</template>
</base-grid>
</template>
<script>
import BaseGrid from './BaseGrid.vue';
export default {
props: {
isLoaded: {
type: Boolean,
required: true
},
rows: {
type: Array,
required: true
}
},
data() {
return {
columns: [
{ text: 'Name', dataIndex: 'name' },
{ text: 'Filename', dataIndex: 'filename' },
{ text: 'Tool data path', dataIndex: 'tool_data_path' },
{ text: 'Errors', dataIndex: 'errors' }
]
}
},
components: {
'base-grid': BaseGrid
},
methods: {
handleTableNameClick(event) {
this.$emit('changeview', event.target.text);
}
}
}
</script>
@@ -6,9 +6,7 @@
export function append_notebook(url) {
clear_main_area();
$("#main").append(
`<iframe frameBorder="0" seamless="seamless" style="width: 100%; height: 100%; overflow:hidden;" scrolling="no" src="${
url
}"></iframe>`
`<iframe frameBorder="0" seamless="seamless" style="width: 100%; height: 100%; overflow:hidden;" scrolling="no" src="${url}"></iframe>`
);
}
@@ -21,9 +19,7 @@ export function clear_main_area() {
export function display_spinner() {
$("#main").append(
`<img id="spinner" src="${
galaxy_root
}static/style/largespinner.gif" style="position:absolute;margin:auto;top:0;left:0;right:0;bottom:0;">`
`<img id="spinner" src="${galaxy_root}static/style/largespinner.gif" style="position:absolute;margin:auto;top:0;left:0;right:0;bottom:0;">`
);
}
+3
View File
@@ -256,6 +256,9 @@ GalaxyApp.prototype.toString = function toString() {
return `GalaxyApp(${userEmail})`;
};
// This is not great. Rework to be a singleton-style Galaxy app accessible/created by import
window.Galaxy = window.Galaxy || new GalaxyApp();
// ============================================================================
export default {
GalaxyApp: GalaxyApp
+1 -1
View File
@@ -176,7 +176,7 @@ var GalaxyLibrary = Backbone.View.extend({
mod_toastr.info("You must select at least one dataset to download");
Galaxy.libraries.library_router.navigate(`folders/${folder_id}`, { trigger: true, replace: true });
} else {
Galaxy.libraries.folderToolbarView.download(folder_id, format);
Galaxy.libraries.folderToolbarView.download(format);
Galaxy.libraries.library_router.navigate(`folders/${folder_id}`, { trigger: false, replace: true });
}
});
+4 -8
View File
@@ -5,6 +5,8 @@ import "libs/jquery/jquery.wymeditor";
import "libs/jquery/jquery.autocomplete";
import GridView from "mvc/grid/grid-view";
var WYMeditor = window.WYMeditor;
var CONTROLS = {
// Item types.
ITEM_HISTORY: "item_history",
@@ -154,11 +156,7 @@ WYMeditor.editor.prototype.dialog = function(dialogType, dialogFeatures, bodyHtm
}
show_modal(
"Create Link",
`<div><div><label id='link_attribute_label'>URL <span style='float: right; font-size: 90%'><a href='#' id='set_link_id'>Create in-page anchor</a></span></label><br><input type='text' class='wym_href' value='${
curURL
}' size='40' /></div><div><label>Title</label><br><input type='text' class='wym_title' value='${
curTitle
}' size='40' /></div><div>`,
`<div><div><label id='link_attribute_label'>URL <span style='float: right; font-size: 90%'><a href='#' id='set_link_id'>Create in-page anchor</a></span></label><br><input type='text' class='wym_href' value='${curURL}' size='40' /></div><div><label>Title</label><br><input type='text' class='wym_title' value='${curTitle}' size='40' /></div><div>`,
{
"Make link": function() {
// Get URL, name/title.
@@ -332,7 +330,6 @@ WYMeditor.editor.prototype.dialog = function(dialogType, dialogFeatures, bodyHtm
var grid = new GridView({
url_base: item_info.list_ajax_url,
dict_format: true,
embedded: true
});
Galaxy.modal.show({
@@ -426,7 +423,6 @@ WYMeditor.editor.prototype.dialog = function(dialogType, dialogFeatures, bodyHtm
var grid = new GridView({
url_base: item_info.list_ajax_url,
dict_format: true,
embedded: true
});
Galaxy.modal.show({
@@ -493,7 +489,7 @@ WYMeditor.editor.prototype.dialog = function(dialogType, dialogFeatures, bodyHtm
}
};
export default function editor_onload() {
export default function pagesEditorOnload() {
// Generic error handling
$(document).ajaxError((e, x) => {
// console.log( e, x );
+3 -3
View File
@@ -154,9 +154,9 @@ function show_in_overlay(options) {
closeButton: true,
title: "&nbsp;",
body: $(
`<div style='margin: -5px;'><iframe style='margin: 0; padding: 0;' src='${options.url}' width='${
width
}' height='${height}' scrolling='${scroll}' frameborder='0'></iframe></div>`
`<div style='margin: -5px;'><iframe style='margin: 0; padding: 0;' src='${
options.url
}' width='${width}' height='${height}' scrolling='${scroll}' frameborder='0'></iframe></div>`
)
});
modal.show({ backdrop: true });
+2 -4
View File
@@ -17,10 +17,8 @@ var Router = Backbone.Router.extend({
data.__identifer = Math.random()
.toString(36)
.substr(2);
if (!$.isEmptyObject(data)) {
url += url.indexOf("?") == -1 ? "?" : "&";
url += $.param(data, true);
}
url += url.indexOf("?") == -1 ? "?" : "&";
url += $.param(data, true);
Galaxy.params = data;
this.navigate(url, { trigger: true });
},
@@ -0,0 +1,114 @@
// legacy grid model, used by reports and toolshed
export default Backbone.Model.extend({
defaults: {
url_base: "",
async: false,
async_ops: [],
categorical_filters: [],
filters: {},
sort_key: null,
show_item_checkboxes: false,
advanced_search: false,
cur_page: 1,
num_pages: 1,
operation: undefined,
item_ids: undefined
},
/**
* Return true if operation can be done asynchronously.
*/
can_async_op: function(op) {
return _.indexOf(this.attributes.async_ops, op) !== -1;
},
/**
* Add filtering criterion.
*/
add_filter: function(key, value, append) {
// Update URL arg with new condition.
if (append) {
// Update or append value.
var cur_val = this.attributes.filters[key];
var new_val;
if (cur_val === null || cur_val === undefined) {
new_val = value;
} else if (typeof cur_val == "string") {
if (cur_val == "All" || cur_val == value) {
new_val = value;
} else {
// Replace string with array.
var values = [];
values[0] = cur_val;
values[1] = value;
new_val = values;
}
} else {
// Current value is an array.
new_val = cur_val;
if (new_val.indexOf(value) === -1) {
new_val.push(value);
}
}
this.attributes.filters[key] = new_val;
} else {
// Replace value.
this.attributes.filters[key] = value;
}
},
/**
* Remove filtering criterion.
*/
remove_filter: function(key, condition) {
var cur_val = this.attributes.filters[key];
if (cur_val === null || cur_val === undefined) {
return false;
}
if (typeof cur_val === "string") {
// overwrite/remove condition.
this.attributes.filters[key] = "";
} else {
// filter contains an array of conditions.
var condition_index = _.indexOf(cur_val, condition);
if (condition_index !== -1) {
cur_val[condition_index] = "";
}
}
},
/**
* Returns URL data for obtaining a new grid.
*/
get_url_data: function() {
var url_data = {
async: this.attributes.async,
sort: this.attributes.sort_key,
page: this.attributes.cur_page,
show_item_checkboxes: this.attributes.show_item_checkboxes,
advanced_search: this.attributes.advanced_search
};
// Add operation, item_ids only if they have values.
if (this.attributes.operation) {
url_data.operation = this.attributes.operation;
}
if (this.attributes.item_ids) {
url_data.id = this.attributes.item_ids;
}
// Add filter arguments to data, placing "f-" in front of all arguments.
var self = this;
_.each(_.pairs(self.attributes.filters), k => {
url_data[`f-${k[0]}`] = k[1];
});
return url_data;
},
// Return URL for obtaining a new grid
get_url: function(args) {
return `${this.get("url_base")}?${$.param(this.get_url_data())}&${$.param(args)}`;
}
});
@@ -0,0 +1,547 @@
// dependencies
import Utils from "utils/utils";
// grid view templates
export default {
// template
grid: function(options) {
var tmpl = "";
if (options.embedded) {
tmpl = this.grid_header(options) + this.grid_table(options);
} else {
tmpl = `<div class="loading-elt-overlay"></div><table><tr><td width="75%">${this.grid_header(
options
)}</td><td></td><td></td></tr><tr><td width="100%" id="grid-message" valign="top"></td><td></td><td></td></tr></table>${this.grid_table(
options
)}`;
}
// add info text
if (options.info_text) {
tmpl += `<br><div class="toolParamHelp" style="clear: both;">${options.info_text}</div>`;
}
// return
return tmpl;
},
// template
grid_table: function() {
return `
<form method="post" onsubmit="return false;">
<table id="grid-table" class="grid">
<thead id="grid-table-header"></thead>
<tbody id="grid-table-body"></tbody>
<tfoot id="grid-table-footer"></tfoot>
</table>
</form>`;
},
// template
grid_header: function(options) {
var tmpl = '<div class="grid-header">';
if (!options.embedded) {
tmpl += `<h2>${options.title}</h2>`;
}
if (options.global_actions) {
tmpl += '<ul class="manage-table-actions">';
var show_popup = options.global_actions.length >= 3;
if (show_popup) {
tmpl +=
'<li><a class="action-button" id="popup-global-actions" class="menubutton">Actions</a></li>' +
'<div popupmenu="popup-global-actions">';
}
for (let action of options.global_actions) {
tmpl += `<li><a class="action-button use-target" target="${action.target}" href="${
action.url_args
}" onclick="return false;" >${action.label}</a></li>`;
}
if (show_popup) {
tmpl += "</div>";
}
tmpl += "</ul>";
}
if (options.insert) {
tmpl += options.insert;
}
// add grid filters
tmpl += this.grid_filters(options);
tmpl += "</div>";
// return template
return tmpl;
},
// template
header: function(options) {
// start
var tmpl = "<tr>";
// add checkbox
if (options.show_item_checkboxes) {
tmpl += "<th>";
if (options.items.length > 0) {
tmpl +=
'<input type="checkbox" id="check_all" name=select_all_checkbox value="true">' +
'<input type="hidden" name=select_all_checkbox value="true">';
}
tmpl += "</th>";
}
// create header elements
for (let column of options.columns) {
if (column.visible) {
tmpl += `<th id="${column.key}-header">`;
if (column.href) {
tmpl += `<a href="${column.href}" class="sort-link" sort_key="${column.key}">${column.label}</a>`;
} else {
tmpl += column.label;
}
tmpl += `<span class="sort-arrow">${column.extra}</span></th>`;
}
}
// finalize
tmpl += "</tr>";
// return template
return tmpl;
},
// template
body: function(options) {
// initialize
var tmpl = "";
var items_length = options.items.length;
// empty grid?
if (items_length === 0) {
// No results.
tmpl += '<tr><td colspan="100"><em>No Items</em></td></tr>';
}
// create rows
for (let item of options.items) {
// Tag current
tmpl += "<tr ";
if (options.current_item_id == item.id) {
tmpl += 'class="current"';
}
tmpl += ">";
// Item selection column
if (options.show_item_checkboxes) {
tmpl += `<td style="width: 1.5em;"><input type="checkbox" name="id" value="${item.encode_id}" id="${
item.encode_id
}" class="grid-row-select-checkbox" /></td>`;
}
// Data columns
for (let column of options.columns) {
if (column.visible) {
// Nowrap
var nowrap = "";
if (column.nowrap) {
nowrap = 'style="white-space:nowrap;"';
}
// get column settings
var column_settings = item.column_config[column.label];
// load attributes
var link = column_settings.link;
var value = column_settings.value;
var target = column_settings.target;
// unescape value
if (jQuery.type(value) === "string") {
value = value.replace(/\/\//g, "/");
}
// Attach popup menu?
var id = "";
var cls = "";
if (column.attach_popup) {
id = `grid-${item.encode_id}-popup`;
cls = "menubutton";
if (link !== "") {
cls += " split";
}
cls += " popup";
}
// Check for row wrapping
tmpl += `<td ${nowrap}>`;
// Link
if (link) {
if (options.operations.length !== 0) {
tmpl += `<div id="${id}" class="${cls}" style="float: left;">`;
}
tmpl += `<a class="menubutton-label use-target" target="${target}" href="${link}" onclick="return false;">${value}</a>`;
if (options.operations.length !== 0) {
tmpl += "</div>";
}
} else {
tmpl += `<div id="${id}" class="${cls}"><label id="${column.label_id_prefix}${
item.encode_id
}" for="${item.encode_id}">${value || ""}</label></div>`;
}
tmpl += "</td>";
}
}
tmpl += "</tr>";
}
return tmpl;
},
// template
footer: function(options) {
// create template string
var tmpl = "";
// paging
if (options.use_paging && options.num_pages > 1) {
// get configuration
var num_page_links = options.num_page_links;
var cur_page_num = options.cur_page_num;
var num_pages = options.num_pages;
// First pass on min page.
var page_link_range = num_page_links / 2;
var min_page = cur_page_num - page_link_range;
var min_offset = 0;
if (min_page <= 0) {
// Min page is too low.
min_page = 1;
min_offset = page_link_range - (cur_page_num - min_page);
}
// Set max page.
var max_range = page_link_range + min_offset;
var max_page = cur_page_num + max_range;
var max_offset;
if (max_page <= num_pages) {
// Max page is fine.
max_offset = 0;
} else {
// Max page is too high.
max_page = num_pages;
// +1 to account for the +1 in the loop below.
max_offset = max_range - (max_page + 1 - cur_page_num);
}
// Second and final pass on min page to add any unused
// offset from max to min.
if (max_offset !== 0) {
min_page -= max_offset;
if (min_page < 1) {
min_page = 1;
}
}
// template header
tmpl += '<tr id="page-links-row">';
if (options.show_item_checkboxes) {
tmpl += "<td></td>";
}
tmpl += '<td colspan="100">' + '<span id="page-link-container">' + "Page:";
if (min_page > 1) {
tmpl +=
'<span class="page-link" id="page-link-1"><a href="javascript:void(0);" page_num="1" onclick="return false;">1</a></span> ...';
}
// create page urls
for (var page_index = min_page; page_index < max_page + 1; page_index++) {
if (page_index == options.cur_page_num) {
tmpl += `<span class="page-link inactive-link" id="page-link-${page_index}">${page_index}</span>`;
} else {
tmpl += `<span class="page-link" id="page-link-${page_index}"><a href="javascript:void(0);" onclick="return false;" page_num="${page_index}">${page_index}</a></span>`;
}
}
// show last page
if (max_page < num_pages) {
tmpl += `...<span class="page-link" id="page-link-${num_pages}"><a href="javascript:void(0);" onclick="return false;" page_num="${num_pages}">${num_pages}</a></span>`;
}
tmpl += "</span>";
// Show all link
tmpl += `
<span class="page-link" id="show-all-link-span"> | <a href="javascript:void(0);" onclick="return false;" page_num="all">Show All</a></span>
</td>
</tr>`;
}
// Grid operations for multiple items.
if (options.show_item_checkboxes) {
// start template
tmpl += `
<tr>
<input type="hidden" id="operation" name="operation" value="">
<td></td>
<td colspan="100">
For <span class="grid-selected-count"></span> selected items:
`;
// configure buttons for operations
for (let operation of options.operations) {
if (operation.allow_multiple) {
tmpl += `<input type="button" value="${
operation.label
}" class="operation-button action-button">&nbsp;`;
}
}
// finalize template
tmpl += "</td>" + "</tr>";
}
// count global operations
var found_global = false;
for (let operation of options.operations) {
if (operation.global_operation) {
found_global = true;
break;
}
}
// add global operations
if (found_global) {
tmpl += "<tr>" + '<td colspan="100">';
for (let operation of options.operations) {
if (operation.global_operation) {
tmpl += `<a class="action-button" href="${operation.global_operation}">${operation.label}</a>`;
}
}
tmpl += "</td>" + "</tr>";
}
// add legend
if (options.legend) {
tmpl += `<tr><td colspan="100">${options.legend}</td></tr>`;
}
// return
return tmpl;
},
// template
message: function(options) {
var status = options.status;
if (["success", "ok"].indexOf(status) != -1) {
status = "done";
}
return `<p><div class="${status}message transient-message">${_.escape(
options.message
)}</div><div style="clear: both"></div></p>`;
},
// template
grid_filters: function(options) {
// get filters
var default_filter_dict = options.default_filter_dict;
var filters = options.filters;
// show advanced search if flag set or if there are filters for advanced search fields
var advanced_search_display = "none";
if (options.advanced_search) {
advanced_search_display = "block";
}
// identify columns with advanced filtering
var show_advanced_search_link = false;
for (let column of options.columns) {
if (column.filterable == "advanced") {
var column_key = column.key;
var f_key = filters[column_key];
var d_key = default_filter_dict[column_key];
if (f_key && d_key && f_key != d_key) {
advanced_search_display = "block";
}
show_advanced_search_link = true;
}
}
// hide standard search if advanced is shown
var standard_search_display = "block";
if (advanced_search_display == "block") {
standard_search_display = "none";
}
//
// standard search
//
var tmpl = `<div id="standard-search" style="display: ${standard_search_display};"><table><tr><td style="padding: 0;"><table>`;
// add standard filters
for (let column of options.columns) {
if (column.filterable == "standard") {
tmpl += this.grid_column_filter(options, column);
}
}
// finalize standard search
tmpl += "</table>" + "</td>" + "</tr>" + "<tr>" + "<td>";
// show advanced search link in standard display
if (show_advanced_search_link) {
tmpl += '<a href="" class="advanced-search-toggle">Advanced Search</a>';
}
// finalize standard search display
tmpl += "</td>" + "</tr>" + "</table>" + "</div>";
//
// advanced search
//
tmpl += `<div id="advanced-search" style="display: ${advanced_search_display}; margin-top: 5px; border: 1px solid #ccc;"><table><tr><td style="text-align: left" colspan="100"><a href="" class="advanced-search-toggle">Close Advanced Search</a></td></tr>`;
// add advanced filters
for (let column of options.columns) {
if (column.filterable == "advanced") {
tmpl += this.grid_column_filter(options, column);
}
}
// finalize advanced search template
tmpl += "</table>" + "</div>";
// return template
return tmpl;
},
// template
grid_column_filter: function(options, column) {
// collect parameters
var filters = options.filters;
var column_label = column.label;
var column_key = column.key;
if (column.filterable == "advanced") {
column_label = column_label.toLowerCase();
}
// start
var tmpl = "<tr>";
if (column.filterable == "advanced") {
tmpl += `<td align="left" style="padding-left: 10px">${column_label}:</td>`;
}
tmpl += '<td style="padding-bottom: 1px;">';
if (column.is_text) {
tmpl += `<form class="text-filter-form" column_key="${column_key}" action="${options.url}" method="get" >`;
// Carry forward filtering criteria with hidden inputs.
for (let column of options.columns) {
var filter_value = filters[column.key];
if (filter_value) {
if (filter_value != "All") {
if (column.is_text) {
filter_value = JSON.stringify(filter_value);
}
tmpl += `<input type="hidden" id="${column.key}" name="f-${
column.key
}" value="${filter_value}"/>`;
}
}
}
// Print current filtering criteria and links to delete.
tmpl += `<span id="${column_key}-filtering-criteria">`;
// add filters
var column_filter = filters[column_key];
if (column_filter) {
// identify type
var type = jQuery.type(column_filter);
// single filter value
if (type == "string") {
if (column_filter != "All") {
// append template
tmpl += this.filter_element(column_key, column_filter);
}
}
// multiple filter values
if (type == "array") {
for (let i in column_filter) {
// copy filters and remove entry
var params = column_filter;
params = params.slice(i);
// append template
tmpl += this.filter_element(column_key, column_filter[i]);
}
}
}
// close span
tmpl += "</span>";
// Set value, size of search input field. Minimum size is 20 characters.
var value = "";
var size = 20;
if (column.filterable == "standard") {
value = column.label.toLowerCase();
if (value.length < 20) {
size = value.length;
}
// +4 to account for space after placeholder
size = size + 4;
}
// print input field for column
tmpl += `
<span class="search-box">
<input class="search-box-input" id="input-${column_key}-filter" name="f-${column_key}" type="text" placeholder="${value}" size="${size}"/>
<button type="submit" style="background: transparent; border: none; padding: 4px; margin: 0px;">
<i class="fa fa-search"></i>
</button>
</span>
</form>`;
} else {
// filter criteria
tmpl += `<span id="${column_key}-filtering-criteria">`;
// add category filters
var seperator = false;
for (var cf_label in options.categorical_filters[column_key]) {
// get category filter
var cf = options.categorical_filters[column_key][cf_label];
// each filter will have only a single argument, so get that single argument
var cf_key = "";
var cf_arg = "";
for (var key in cf) {
cf_key = key;
cf_arg = cf[key];
}
// add seperator
if (seperator) {
tmpl += " | ";
}
seperator = true;
// add category
var filter = filters[column_key];
if (filter && cf[column_key] && filter == cf_arg) {
tmpl += `<span class="categorical-filter ${column_key}-filter current-filter">${cf_label}</span>`;
} else {
tmpl += `<span class="categorical-filter ${column_key}-filter"><a href="javascript:void(0);" filter_key="${cf_key}" filter_val="${cf_arg}">${cf_label}</a></span>`;
}
}
tmpl += "</span>";
}
tmpl += "</td>" + "</tr>";
// return template
return tmpl;
},
// template for filter items
filter_element: function(filter_key, filter_value) {
filter_value = Utils.sanitize(filter_value);
return `<span class="text-filter-val">${filter_value}<a href="javascript:void(0);" filter_key="${filter_key}" filter_val="${filter_value}"><i class="fa fa-times" style="padding-left: 5px; padding-bottom: 6px;"/></a></span>`;
}
};
@@ -0,0 +1,677 @@
// This is necessary so that, when nested arrays are used in ajax/post/get methods, square brackets ('[]') are
// not appended to the identifier of a nested array.
jQuery.ajaxSettings.traditional = true;
// dependencies
import Utils from "utils/utils";
import GridModel from "legacy/grid/grid-model";
import Templates from "legacy/grid/grid-template";
import PopupMenu from "mvc/ui/popup-menu";
// grid view
export default Backbone.View.extend({
// model
grid: null,
// Initialize
initialize: function(grid_config) {
this.grid = new GridModel();
this.dict_format = grid_config.dict_format;
this.title = grid_config.title;
var self = this;
window.add_tag_to_grid_filter = (tag_name, tag_value) => {
// Put tag name and value together.
var tag = tag_name + (tag_value !== undefined && tag_value !== "" ? `:${tag_value}` : "");
var advanced_search = $("#advanced-search").is(":visible");
if (!advanced_search) {
$("#standard-search").slideToggle("fast");
$("#advanced-search").slideToggle("fast");
}
self.add_filter_condition("tags", tag);
};
// set element
if (this.dict_format) {
this.setElement("<div/>");
if (grid_config.url_base && !grid_config.items) {
var url_data = grid_config.url_data || {};
_.each(grid_config.filters, (v, k) => {
url_data[`f-${k}`] = v;
});
$.ajax({
url: `${grid_config.url_base}?${$.param(url_data)}`,
success: function(response) {
response.embedded = grid_config.embedded;
response.filters = grid_config.filters || {};
self.init_grid(response);
}
});
} else {
this.init_grid(grid_config);
}
} else {
this.setElement("#grid-container");
this.init_grid(grid_config);
}
// fix padding
if (grid_config.use_panels) {
$("#center").css({
padding: "10px",
overflow: "auto"
});
}
},
// refresh frames
handle_refresh: function(refresh_frames) {
if (refresh_frames) {
if ($.inArray("history", refresh_frames) > -1) {
if (window.top.Galaxy && window.top.Galaxy.currHistoryPanel) {
window.top.Galaxy.currHistoryPanel.loadCurrentHistory();
}
}
}
},
// Initialize
init_grid: function(grid_config) {
this.grid.set(grid_config);
// get options
var options = this.grid.attributes;
if (this.allow_title_display && options.title) {
Utils.setWindowTitle(options.title);
}
// handle refresh requests
this.handle_refresh(options.refresh_frames);
// strip protocol and domain
var url = this.grid.get("url_base");
url = url.replace(/^.*\/\/[^\/]+/, "");
this.grid.set("url_base", url);
// append main template
this.$el.html(Templates.grid(options));
// update div contents
this.$el.find("#grid-table-header").html(Templates.header(options));
this.$el.find("#grid-table-body").html(Templates.body(options));
this.$el.find("#grid-table-footer").html(Templates.footer(options));
// update message
if (options.message) {
this.$el.find("#grid-message").html(Templates.message(options));
var self = this;
if (options.use_hide_message) {
setTimeout(() => {
self.$el.find("#grid-message").html("");
}, 5000);
}
}
// configure elements
this.init_grid_elements();
this.init_grid_controls();
// attach global event handler
// TODO: redundant (the onload/standard page handlers do this) - but needed because these are constructed after page ready
if (window.init_refresh_on_change) {
window.init_refresh_on_change();
}
},
// Initialize grid controls
init_grid_controls: function() {
// link
var self = this;
// Initialize grid operation button.
this.$el.find(".operation-button").each(function() {
$(this).off();
$(this).click(function() {
self.submit_operation(this);
return false;
});
});
// Initialize text filters to select text on click and use normal font when user is typing.
this.$el.find("input[type=text]").each(function() {
$(this).off();
$(this)
.click(function() {
$(this).select();
})
.keyup(function() {
$(this).css("font-style", "normal");
});
});
// Initialize sort links.
this.$el.find(".sort-link").each(function() {
$(this).off();
$(this).click(function() {
self.set_sort_condition($(this).attr("sort_key"));
return false;
});
});
// Initialize text filters.
this.$el.find(".text-filter-form").each(function() {
$(this).off();
$(this).submit(function() {
var column_key = $(this).attr("column_key");
var text_input_obj = $(`#input-${column_key}-filter`);
var text_input = text_input_obj.val();
text_input_obj.val("");
self.add_filter_condition(column_key, text_input);
return false;
});
});
// Initialize categorical filters.
this.$el.find(".text-filter-val > a").each(function() {
$(this).off();
$(this).click(function() {
// Remove visible element.
$(this)
.parent()
.remove();
// Remove filter condition.
self.remove_filter_condition($(this).attr("filter_key"), $(this).attr("filter_val"));
// Return
return false;
});
});
// Initialize categorical filters.
this.$el.find(".categorical-filter > a").each(function() {
$(this).off();
$(this).click(function() {
self.set_categorical_filter($(this).attr("filter_key"), $(this).attr("filter_val"));
return false;
});
});
// Initialize standard, advanced search toggles.
this.$el.find(".advanced-search-toggle").each(function() {
$(this).off();
$(this).click(() => {
self.$el.find("#standard-search").slideToggle("fast");
self.$el.find("#advanced-search").slideToggle("fast");
return false;
});
});
// Add event to check all box
this.$el.find("#check_all").off();
this.$el.find("#check_all").on("click", () => {
self.check_all_items();
});
},
// Initialize grid elements.
init_grid_elements: function() {
// Initialize grid selection checkboxes.
this.$el.find(".grid").each(function() {
var checkboxes = $(this).find("input.grid-row-select-checkbox");
var check_count = $(this).find("span.grid-selected-count");
var update_checked = () => {
check_count.text($(checkboxes).filter(":checked").length);
};
$(checkboxes).each(function() {
$(this).change(update_checked);
});
update_checked();
});
// Initialize ratings.
if (this.$el.find(".community_rating_star").length !== 0) this.$el.find(".community_rating_star").rating({});
// get options
var options = this.grid.attributes;
var self = this;
//
// add page click events
//
this.$el.find(".page-link > a").each(function() {
$(this).click(function() {
self.set_page($(this).attr("page_num"));
return false;
});
});
//
// add inbound/outbound events
//
this.$el.find(".use-target").each(function() {
$(this).click(function() {
self.execute({
href: $(this).attr("href"),
target: $(this).attr("target")
});
return false;
});
});
// empty grid?
var items_length = options.items.length;
if (items_length === 0) {
return;
}
// add operation popup menus
_.each(options.items, (item, index) => {
var button = self.$(`#grid-${item.encode_id}-popup`).off();
var popup = new PopupMenu(button);
_.each(options.operations, operation => {
self._add_operation(popup, operation, item);
});
});
},
/** Add an operation to the items menu */
_add_operation: function(popup, operation, item) {
var self = this;
var settings = item.operation_config[operation.label];
if (settings.allowed && operation.allow_popup) {
popup.addItem({
html: operation.label,
href: settings.url_args,
target: settings.target,
confirmation_text: operation.confirm,
func: function(e) {
e.preventDefault();
var label = $(e.target).html();
if (operation.onclick) {
operation.onclick(item.encode_id);
} else {
self.execute(this.findItemByHtml(label));
}
}
});
}
},
// Add a condition to the grid filter; this adds the condition and refreshes the grid.
add_filter_condition: function(name, value) {
// Do nothing is value is empty.
if (value === "") {
return false;
}
// Add condition to grid.
this.grid.add_filter(name, value, true);
// Add button that displays filter and provides a button to delete it.
var t = $(Templates.filter_element(name, value));
var self = this;
t.click(function() {
// Remove visible element.
$(this).remove();
// Remove filter condition.
self.remove_filter_condition(name, value);
});
// append to container
var container = this.$el.find(`#${name}-filtering-criteria`);
container.append(t);
// execute
this.go_page_one();
this.execute();
},
// Remove a condition to the grid filter; this adds the condition and refreshes the grid.
remove_filter_condition: function(name, value) {
// Remove filter condition.
this.grid.remove_filter(name, value);
// Execute
this.go_page_one();
this.execute();
},
// Set sort condition for grid.
set_sort_condition: function(col_key) {
// Set new sort condition. New sort is col_key if sorting new column; if reversing sort on
// currently sorted column, sort is reversed.
var cur_sort = this.grid.get("sort_key");
var new_sort = col_key;
if (cur_sort.indexOf(col_key) !== -1) {
// Reverse sort.
if (cur_sort.substring(0, 1) !== "-") {
new_sort = `-${col_key}`;
}
}
// Remove sort arrows elements.
this.$el.find(".sort-arrow").remove();
// Add sort arrow element to new sort column.
var sort_arrow = new_sort.substring(0, 1) == "-" ? "&uarr;" : "&darr;";
var t = $(`<span>${sort_arrow}</span>`).addClass("sort-arrow");
// Add to header
this.$el.find(`#${col_key}-header`).append(t);
// Update grid.
this.grid.set("sort_key", new_sort);
this.go_page_one();
this.execute();
},
// Set new value for categorical filter.
set_categorical_filter: function(name, new_value) {
// Update filter hyperlinks to reflect new filter value.
var category_filter = this.grid.get("categorical_filters")[name];
var cur_value = this.grid.get("filters")[name];
var self = this;
this.$el.find(`.${name}-filter`).each(function() {
var text = $.trim($(this).text());
var filter = category_filter[text];
var filter_value = filter[name];
if (filter_value == new_value) {
// Remove filter link since grid will be using this filter. It is assumed that
// this element has a single child, a hyperlink/anchor with text.
$(this).empty();
$(this).addClass("current-filter");
$(this).append(text);
} else if (filter_value == cur_value) {
// Add hyperlink for this filter since grid will no longer be using this filter. It is assumed that
// this element has a single child, a hyperlink/anchor.
$(this).empty();
var t = $(`<a href="#">${text}</a>`);
t.click(() => {
self.set_categorical_filter(name, filter_value);
});
$(this).removeClass("current-filter");
$(this).append(t);
}
});
// Update grid.
this.grid.add_filter(name, new_value);
this.go_page_one();
this.execute();
},
// Set page to view.
set_page: function(new_page) {
// Update page hyperlink to reflect new page.
var self = this;
this.$el.find(".page-link").each(function() {
var id = $(this).attr("id");
var // Id has form 'page-link-<page_num>
page_num = parseInt(id.split("-")[2], 10);
var cur_page = self.grid.get("cur_page");
var text;
if (page_num === new_page) {
// Remove link to page since grid will be on this page. It is assumed that
// this element has a single child, a hyperlink/anchor with text.
text = $(this)
.children()
.text();
$(this).empty();
$(this).addClass("inactive-link");
$(this).text(text);
} else if (page_num === cur_page) {
// Add hyperlink to this page since grid will no longer be on this page. It is assumed that
// this element has a single child, a hyperlink/anchor.
text = $(this).text();
$(this).empty();
$(this).removeClass("inactive-link");
var t = $(`<a href="#">${text}</a>`);
t.click(() => {
self.set_page(page_num);
});
$(this).append(t);
}
});
if (new_page === "all") {
this.grid.set("cur_page", new_page);
} else {
this.grid.set("cur_page", parseInt(new_page, 10));
}
this.execute();
},
// confirmation/submission of operation request
submit_operation: function(operation_button, confirmation_text) {
// identify operation
var operation_name = $(operation_button).val();
// verify any item is selected
var number_of_checked_ids = this.$el.find('input[name="id"]:checked').length;
if (number_of_checked_ids < 1) {
return false;
}
// Check to see if there's grid confirmation text for this operation
var operation = _.findWhere(this.grid.attributes.operations, {
label: operation_name
});
if (operation && !confirmation_text) {
confirmation_text = operation.confirm || "";
}
// collect ids
var item_ids = [];
this.$el.find("input[name=id]:checked").each(function() {
item_ids.push($(this).val());
});
// execute operation
var options = {
operation: operation_name,
id: item_ids,
confirmation_text: confirmation_text
};
if (operation.target == "top" || operation.target == "center") {
options = _.extend(options, {
href: operation.href,
target: operation.target
});
}
this.execute(options);
return true;
},
check_all_items: function() {
var check = this.$(".grid-row-select-checkbox");
var state = this.$("#check_all").prop("checked");
_.each(check, c => {
$(c).prop("checked", state);
});
this.init_grid_elements();
},
// Go back to page one; this is useful when a filter is applied.
go_page_one: function() {
// Need to go back to page 1 if not showing all.
var cur_page = this.grid.get("cur_page");
if (cur_page !== null && cur_page !== undefined && cur_page !== "all") {
this.grid.set("cur_page", 1);
}
},
//
// execute operations and hyperlink requests
//
execute: function(options) {
// get url
var id = null;
var href = null;
var operation = null;
var confirmation_text = null;
var target = null;
// check for options
if (options) {
// get options
href = options.href;
operation = options.operation;
id = options.id;
confirmation_text = options.confirmation_text;
target = options.target;
// check if input contains the operation tag
if (href !== undefined && href.indexOf("operation=") != -1) {
// Get operation, id in hyperlink's href.
var href_parts = href.split("?");
if (href_parts.length > 1) {
var href_parms_str = href_parts[1];
var href_parms = href_parms_str.split("&");
for (var index = 0; index < href_parms.length; index++) {
if (href_parms[index].indexOf("operation") != -1) {
// Found operation parm; get operation value.
operation = href_parms[index].split("=")[1];
operation = operation.replace(/\+/g, " ");
} else if (href_parms[index].indexOf("id") != -1) {
// Found id parm; get id value.
id = href_parms[index].split("=")[1];
}
}
}
}
}
// check for operation details
if (operation && id) {
// show confirmation box
if (
confirmation_text &&
confirmation_text !== "" &&
confirmation_text != "None" &&
confirmation_text != "null"
)
if (!confirm(confirmation_text)) return false;
// use small characters for operation?!
operation = operation.toLowerCase();
// Update grid.
this.grid.set({
operation: operation,
item_ids: id
});
// Do operation. If operation cannot be performed asynchronously, redirect to location.
if (target == "top") {
window.top.location = `${href}?${$.param(this.grid.get_url_data())}`;
} else if (target == "center") {
$("#galaxy_main").attr("src", `${href}?${$.param(this.grid.get_url_data())}`);
} else if (this.grid.can_async_op(operation) || this.dict_format) {
this.update_grid();
} else {
this.go_to(target, href);
}
// done
return false;
}
// refresh grid
if (href) {
this.go_to(target, href);
return false;
}
// refresh grid
if (this.grid.get("async") || this.dict_format) {
this.update_grid();
} else {
this.go_to(target, href);
}
// done
return false;
},
// go to url
go_to: function(target, href) {
// get aysnc status
var async = this.grid.get("async");
this.grid.set("async", false);
// get slide status
var advanced_search = this.$el.find("#advanced-search").is(":visible");
this.grid.set("advanced_search", advanced_search);
// get default url
if (!href) {
href = `${this.grid.get("url_base")}?${$.param(this.grid.get_url_data())}`;
}
// clear grid of transient request attributes.
this.grid.set({
operation: undefined,
item_ids: undefined,
async: async
});
switch (target) {
case "center":
$("#galaxy_main").attr("src", href);
break;
case "top":
window.top.location = href;
break;
default:
window.location = href;
}
},
// Update grid.
update_grid: function() {
// If there's an operation, do POST; otherwise, do GET.
var method = this.grid.get("operation") ? "POST" : "GET";
// Show overlay to indicate loading and prevent user actions.
this.$el.find(".loading-elt-overlay").show();
var self = this;
$.ajax({
type: method,
url: self.grid.get("url_base"),
data: self.grid.get_url_data(),
error: function() {
alert("Grid refresh failed");
},
success: function(response_text) {
// backup
var embedded = self.grid.get("embedded");
var insert = self.grid.get("insert");
var advanced_search = self.$el.find("#advanced-search").is(":visible");
// request new configuration
var json = self.dict_format ? response_text : $.parseJSON(response_text);
// update
json.embedded = embedded;
json.insert = insert;
json.advanced_search = advanced_search;
// Initialize new grid config
self.init_grid(json);
// Hide loading overlay.
self.$el.find(".loading-elt-overlay").hide();
},
complete: function() {
// Clear grid of transient request attributes.
self.grid.set({
operation: undefined,
item_ids: undefined
});
}
});
}
});
@@ -4754,3 +4754,5 @@ WYMeditor.WymClassSafari.prototype.getTagForStyle = function(style) {
if(/super/.test(style)) return 'sup';
return false;
};
window.WYMeditor = WYMeditor;
@@ -278,8 +278,8 @@ var ListCollectionCreator = Backbone.View.extend(BASE_MVC.LoggableMixin)
/** describe what is wrong with a particular element if anything */
_isElementInvalid: function(element) {
if (element.history_content_type !== "dataset") {
return _l("is not a dataset");
if (element.history_content_type === "dataset_collection") {
return _l("is a collection, this is not allowed");
}
var validState = element.state === STATES.OK || _.contains(STATES.NOT_READY_STATES, element.state);
if (!validState) {
@@ -1796,7 +1796,8 @@ var pairedCollectionCreatorModal = function _pairedCollectionCreatorModal(datase
oncreate: function(creator, response) {
Galaxy.modal.hide();
deferred.resolve(response);
}
},
title: _l("Create a collection of paired datasets")
});
if (!window.Galaxy || !Galaxy.modal) {
@@ -1805,7 +1806,7 @@ var pairedCollectionCreatorModal = function _pairedCollectionCreatorModal(datase
creator = new PairedCollectionCreator(options);
Galaxy.modal.show({
title: _l("Create a collection of paired datasets"),
title: options.title,
body: creator.$el,
width: "80%",
height: "800px",
+1 -3
View File
@@ -1,8 +1,6 @@
import _l from "utils/localization";
// Additional dependencies: jQuery, underscore.
import Modal from "mvc/ui/ui-modal";
import Frames from "mvc/ui/ui-frames";
import mod_icon_btn from "mvc/ui/icon-button";
// Additional dependencies: jQuery, underscore.
/**
* Dataset metedata.
*/
+29 -37
View File
@@ -286,47 +286,39 @@ var DatasetListItemView = _super.extend(
return this._renderMetaFileDownloadButton();
}
return $(
[
'<a class="download-btn icon-btn" ',
'href="',
this.model.urls.download,
`" title="${_l("Download")}" download>`,
'<span class="fa fa-floppy-o"></span>',
"</a>"
].join("")
);
return $(`
<a class="download-btn icon-btn" href="${this.model.urls.download}" title="${_l("Download")}">
<span class="fa fa-floppy-o"></span>
</a>`);
},
/** Render the download button which opens a dropdown with links to download assoc. meta files (indeces, etc.) */
/** Render the download button which opens a dropdown with links to download assoc. meta files (indices, etc.) */
_renderMetaFileDownloadButton: function() {
var urls = this.model.urls;
return $(
[
'<div class="metafile-dropdown dropdown">',
'<a class="download-btn icon-btn" href="javascript:void(0)" data-toggle="dropdown"',
` title="${_l("Download")}">`,
'<span class="fa fa-floppy-o"></span>',
"</a>",
'<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">',
`<li><a href="${urls.download}" download>`,
_l("Download dataset"),
"</a></li>",
_.map(this.model.get("meta_files"), meta_file =>
[
'<li><a href="',
urls.meta_download + meta_file.file_type,
'">',
_l("Download"),
" ",
meta_file.file_type,
"</a></li>"
].join("")
).join("\n"),
"</ul>",
"</div>"
].join("\n")
);
return $(`
<div class="metafile-dropdown dropdown">
<a class="download-btn icon-btn" href="${urls.download}" data-toggle="dropdown" title="${_l(
"Download"
)}">
<span class="fa fa-floppy-o"></span>
</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
<li>
<a href="${urls.download}">
${_l("Download dataset")}
</a>
</li>
${_.map(
this.model.get("meta_files"),
meta_file =>
`<li>
<a href="${urls.meta_download + meta_file.file_type}">
${_l("Download")} ${meta_file.file_type}
</a>
</li>`
)}
</ul>
</div>`);
},
_renderNametags: function() {
@@ -17,13 +17,6 @@ export default Backbone.Model.extend({
item_ids: undefined
},
/**
* Return true if operation can be done asynchronously.
*/
can_async_op: function(op) {
return _.indexOf(this.attributes.async_ops, op) !== -1;
},
/**
* Add filtering criterion.
*/
@@ -10,7 +10,6 @@ var View = Backbone.View.extend({
$.ajax({
url: `${Galaxy.root + this.item}/${this.model.get("action_id")}?${$.param(Galaxy.params)}`,
success: function(response) {
response["dict_format"] = true;
self.model.set(response);
self.render();
}
+67 -107
View File
@@ -25,16 +25,15 @@ export default {
},
// template
grid_table: function(options) {
return (
'<form method="post" onsubmit="return false;">' +
'<table id="grid-table" class="grid">' +
'<thead id="grid-table-header"></thead>' +
'<tbody id="grid-table-body"></tbody>' +
'<tfoot id="grid-table-footer"></tfoot>' +
"</table>" +
"</form>"
);
grid_table: function() {
return `
<form method="post" onsubmit="return false;">
<table id="grid-table" class="grid">
<thead id="grid-table-header"></thead>
<tbody id="grid-table-body"></tbody>
<tfoot id="grid-table-footer"></tfoot>
</table>
</form>`;
},
// template
@@ -51,8 +50,7 @@ export default {
'<li><a class="action-button" id="popup-global-actions" class="menubutton">Actions</a></li>' +
'<div popupmenu="popup-global-actions">';
}
for (var i in options.global_actions) {
var action = options.global_actions[i];
for (let action of options.global_actions) {
tmpl += `<li><a class="action-button use-target" target="${action.target}" href="${
action.url_args
}" onclick="return false;" >${action.label}</a></li>`;
@@ -91,8 +89,7 @@ export default {
}
// create header elements
for (var i in options.columns) {
var column = options.columns[i];
for (let column of options.columns) {
if (column.visible) {
tmpl += `<th id="${column.key}-header">`;
if (column.href) {
@@ -115,23 +112,16 @@ export default {
body: function(options) {
// initialize
var tmpl = "";
var num_rows_rendered = 0;
var items_length = options.items.length;
// empty grid?
if (items_length == 0) {
if (items_length === 0) {
// No results.
tmpl += '<tr><td colspan="100"><em>No Items</em></td></tr>';
num_rows_rendered = 1;
}
// create rows
for (var i in options.items) {
// encode ids
var item = options.items[i];
var encoded_id = item.encode_id;
var popupmenu_id = `grid-${i}-popup`;
for (let item of options.items) {
// Tag current
tmpl += "<tr ";
if (options.current_item_id == item.id) {
@@ -141,14 +131,13 @@ export default {
// Item selection column
if (options.show_item_checkboxes) {
tmpl += `<td style="width: 1.5em;"><input type="checkbox" name="id" value="${encoded_id}" id="${
encoded_id
tmpl += `<td style="width: 1.5em;"><input type="checkbox" name="id" value="${item.encode_id}" id="${
item.encode_id
}" class="grid-row-select-checkbox" /></td>`;
}
// Data columns
for (var j in options.columns) {
var column = options.columns[j];
for (let column of options.columns) {
if (column.visible) {
// Nowrap
var nowrap = "";
@@ -173,9 +162,9 @@ export default {
var id = "";
var cls = "";
if (column.attach_popup) {
id = `grid-${i}-popup`;
id = `grid-${item.encode_id}-popup`;
cls = "menubutton";
if (link != "") {
if (link !== "") {
cls += " split";
}
cls += " popup";
@@ -186,25 +175,22 @@ export default {
// Link
if (link) {
if (options.operations.length != 0) {
if (options.operations.length !== 0) {
tmpl += `<div id="${id}" class="${cls}" style="float: left;">`;
}
tmpl += `<a class="menubutton-label use-target" target="${target}" href="${
link
}" onclick="return false;">${value}</a>`;
if (options.operations.length != 0) {
tmpl += `<a class="menubutton-label use-target" target="${target}" href="${link}" onclick="return false;">${value}</a>`;
if (options.operations.length !== 0) {
tmpl += "</div>";
}
} else {
tmpl += `<div id="${id}" class="${cls}"><label id="${column.label_id_prefix}${
encoded_id
}" for="${encoded_id}">${value || ""}</label></div>`;
item.encode_id
}" for="${item.encode_id}">${value || ""}</label></div>`;
}
tmpl += "</td>";
}
}
tmpl += "</tr>";
num_rows_rendered++;
}
return tmpl;
},
@@ -247,7 +233,7 @@ export default {
// Second and final pass on min page to add any unused
// offset from max to min.
if (max_offset != 0) {
if (max_offset !== 0) {
min_page -= max_offset;
if (min_page < 1) {
min_page = 1;
@@ -271,44 +257,36 @@ export default {
if (page_index == options.cur_page_num) {
tmpl += `<span class="page-link inactive-link" id="page-link-${page_index}">${page_index}</span>`;
} else {
tmpl += `<span class="page-link" id="page-link-${
page_index
}"><a href="javascript:void(0);" onclick="return false;" page_num="${page_index}">${
page_index
}</a></span>`;
tmpl += `<span class="page-link" id="page-link-${page_index}"><a href="javascript:void(0);" onclick="return false;" page_num="${page_index}">${page_index}</a></span>`;
}
}
// show last page
if (max_page < num_pages) {
tmpl += `...<span class="page-link" id="page-link-${
num_pages
}"><a href="javascript:void(0);" onclick="return false;" page_num="${num_pages}">${
num_pages
}</a></span>`;
tmpl += `...<span class="page-link" id="page-link-${num_pages}"><a href="javascript:void(0);" onclick="return false;" page_num="${num_pages}">${num_pages}</a></span>`;
}
tmpl += "</span>";
// Show all link
tmpl +=
'<span class="page-link" id="show-all-link-span"> | <a href="javascript:void(0);" onclick="return false;" page_num="all">Show All</a></span>' +
"</td>" +
"</tr>";
tmpl += `
<span class="page-link" id="show-all-link-span"> | <a href="javascript:void(0);" onclick="return false;" page_num="all">Show All</a></span>
</td>
</tr>`;
}
// Grid operations for multiple items.
if (options.show_item_checkboxes) {
// start template
tmpl +=
"<tr>" +
'<input type="hidden" id="operation" name="operation" value="">' +
"<td></td>" +
'<td colspan="100">' +
'For <span class="grid-selected-count"></span> selected items: ';
tmpl += `
<tr>
<input type="hidden" id="operation" name="operation" value="">
<td></td>
<td colspan="100">
For <span class="grid-selected-count"></span> selected items:
`;
// configure buttons for operations
for (var i in options.operations) {
var operation = options.operations[i];
for (let operation of options.operations) {
if (operation.allow_multiple) {
tmpl += `<input type="button" value="${
operation.label
@@ -322,8 +300,8 @@ export default {
// count global operations
var found_global = false;
for (i in options.operations) {
if (options.operations[i].global_operation) {
for (let operation of options.operations) {
if (operation.global_operation) {
found_global = true;
break;
}
@@ -332,8 +310,7 @@ export default {
// add global operations
if (found_global) {
tmpl += "<tr>" + '<td colspan="100">';
for (i in options.operations) {
var operation = options.operations[i];
for (let operation of options.operations) {
if (operation.global_operation) {
tmpl += `<a class="action-button" href="${operation.global_operation}">${operation.label}</a>`;
}
@@ -375,8 +352,7 @@ export default {
// identify columns with advanced filtering
var show_advanced_search_link = false;
for (var i in options.columns) {
var column = options.columns[i];
for (let column of options.columns) {
if (column.filterable == "advanced") {
var column_key = column.key;
var f_key = filters[column_key];
@@ -397,13 +373,10 @@ export default {
//
// standard search
//
var tmpl = `<div id="standard-search" style="display: ${
standard_search_display
};"><table><tr><td style="padding: 0;"><table>`;
var tmpl = `<div id="standard-search" style="display: ${standard_search_display};"><table><tr><td style="padding: 0;"><table>`;
// add standard filters
for (var i in options.columns) {
var column = options.columns[i];
for (let column of options.columns) {
if (column.filterable == "standard") {
tmpl += this.grid_column_filter(options, column);
}
@@ -423,13 +396,10 @@ export default {
//
// advanced search
//
tmpl += `<div id="advanced-search" style="display: ${
advanced_search_display
}; margin-top: 5px; border: 1px solid #ccc;"><table><tr><td style="text-align: left" colspan="100"><a href="" class="advanced-search-toggle">Close Advanced Search</a></td></tr>`;
tmpl += `<div id="advanced-search" style="display: ${advanced_search_display}; margin-top: 5px; border: 1px solid #ccc;"><table><tr><td style="text-align: left" colspan="100"><a href="" class="advanced-search-toggle">Close Advanced Search</a></td></tr>`;
// add advanced filters
for (var i in options.columns) {
var column = options.columns[i];
for (let column of options.columns) {
if (column.filterable == "advanced") {
tmpl += this.grid_column_filter(options, column);
}
@@ -445,7 +415,6 @@ export default {
// template
grid_column_filter: function(options, column) {
// collect parameters
var default_filter_dict = options.default_filter_dict;
var filters = options.filters;
var column_label = column.label;
var column_key = column.key;
@@ -463,17 +432,16 @@ export default {
if (column.is_text) {
tmpl += `<form class="text-filter-form" column_key="${column_key}" action="${options.url}" method="get" >`;
// Carry forward filtering criteria with hidden inputs.
for (i in options.columns) {
var temp_column = options.columns[i];
var filter_value = filters[temp_column.key];
for (let column of options.columns) {
var filter_value = filters[column.key];
if (filter_value) {
if (filter_value != "All") {
if (temp_column.is_text) {
if (column.is_text) {
filter_value = JSON.stringify(filter_value);
}
tmpl += `<input type="hidden" id="${temp_column.key}" name="f-${temp_column.key}" value="${
filter_value
}"/>`;
tmpl += `<input type="hidden" id="${column.key}" name="f-${
column.key
}" value="${filter_value}"/>`;
}
}
}
@@ -496,16 +464,13 @@ export default {
// multiple filter values
if (type == "array") {
for (var i in column_filter) {
// get filter
var filter = column_filter[i];
for (let i in column_filter) {
// copy filters and remove entry
var params = column_filter;
params = params.slice(i);
// append template
tmpl += this.filter_element(column_key, filter);
tmpl += this.filter_element(column_key, column_filter[i]);
}
}
}
@@ -515,22 +480,25 @@ export default {
// Set value, size of search input field. Minimum size is 20 characters.
var value = "";
var size = 20;
if (column.filterable == "standard") {
value = column.label.toLowerCase();
var size = value.length;
if (size < 20) {
size = 20;
if (value.length < 20) {
size = value.length;
}
// +4 to account for space after placeholder
size = size + 4;
}
// print input field for column
tmpl += `<span class="search-box"><input class="search-box-input" id="input-${column_key}-filter" name="f-${
column_key
}" type="text" placeholder="${value}" size="${
size
}"/><button type="submit" style="background: transparent; border: none; padding: 4px; margin: 0px;"><i class="fa fa-search"></i></button></span></form>`;
tmpl += `
<span class="search-box">
<input class="search-box-input" id="input-${column_key}-filter" name="f-${column_key}" type="text" placeholder="${value}" size="${size}"/>
<button type="submit" style="background: transparent; border: none; padding: 4px; margin: 0px;">
<i class="fa fa-search"></i>
</button>
</span>
</form>`;
} else {
// filter criteria
tmpl += `<span id="${column_key}-filtering-criteria">`;
@@ -560,11 +528,7 @@ export default {
if (filter && cf[column_key] && filter == cf_arg) {
tmpl += `<span class="categorical-filter ${column_key}-filter current-filter">${cf_label}</span>`;
} else {
tmpl += `<span class="categorical-filter ${
column_key
}-filter"><a href="javascript:void(0);" filter_key="${cf_key}" filter_val="${cf_arg}">${
cf_label
}</a></span>`;
tmpl += `<span class="categorical-filter ${column_key}-filter"><a href="javascript:void(0);" filter_key="${cf_key}" filter_val="${cf_arg}">${cf_label}</a></span>`;
}
}
tmpl += "</span>";
@@ -578,10 +542,6 @@ export default {
// template for filter items
filter_element: function(filter_key, filter_value) {
filter_value = Utils.sanitize(filter_value);
return `<span class="text-filter-val">${filter_value}<a href="javascript:void(0);" filter_key="${
filter_key
}" filter_val="${
filter_value
}"><i class="fa fa-times" style="padding-left: 5px; padding-bottom: 6px;"/></a></span>`;
return `<span class="text-filter-val">${filter_value}<a href="javascript:void(0);" filter_key="${filter_key}" filter_val="${filter_value}"><i class="fa fa-times" style="padding-left: 5px; padding-bottom: 6px;"/></a></span>`;
}
};
+29 -44
View File
@@ -15,7 +15,6 @@ export default Backbone.View.extend({
// Initialize
initialize: function(grid_config) {
this.grid = new GridModel();
this.dict_format = grid_config.dict_format;
this.title = grid_config.title;
var self = this;
window.add_tag_to_grid_filter = (tag_name, tag_value) => {
@@ -30,26 +29,21 @@ export default Backbone.View.extend({
};
// set element
if (this.dict_format) {
this.setElement("<div/>");
if (grid_config.url_base && !grid_config.items) {
var url_data = grid_config.url_data || {};
_.each(grid_config.filters, (v, k) => {
url_data[`f-${k}`] = v;
});
$.ajax({
url: `${grid_config.url_base}?${$.param(url_data)}`,
success: function(response) {
response.embedded = grid_config.embedded;
response.filters = grid_config.filters || {};
self.init_grid(response);
}
});
} else {
this.init_grid(grid_config);
}
this.setElement("<div/>");
if (grid_config.url_base && !grid_config.items) {
var url_data = grid_config.url_data || {};
_.each(grid_config.filters, (v, k) => {
url_data[`f-${k}`] = v;
});
$.ajax({
url: `${grid_config.url_base}?${$.param(url_data)}`,
success: function(response) {
response.embedded = grid_config.embedded;
response.filters = grid_config.filters || {};
self.init_grid(response);
}
});
} else {
this.setElement("#grid-container");
this.init_grid(grid_config);
}
@@ -116,7 +110,9 @@ export default Backbone.View.extend({
// attach global event handler
// TODO: redundant (the onload/standard page handlers do this) - but needed because these are constructed after page ready
init_refresh_on_change();
if (window.init_refresh_on_change) {
window.init_refresh_on_change();
}
},
// Initialize grid controls
@@ -247,7 +243,7 @@ export default Backbone.View.extend({
// add inbound/outbound events
//
this.$el.find(".use-target").each(function() {
$(this).click(function(e) {
$(this).click(function() {
self.execute({
href: $(this).attr("href"),
target: $(this).attr("target")
@@ -258,15 +254,15 @@ export default Backbone.View.extend({
// empty grid?
var items_length = options.items.length;
if (items_length == 0) {
if (items_length === 0) {
return;
}
// add operation popup menus
_.each(options.items, (item, index) => {
var button = self.$(`#grid-${index}-popup`).off();
var button = self.$(`#grid-${item.encode_id}-popup`).off();
var popup = new PopupMenu(button);
_.each(options["operations"], operation => {
_.each(options.operations, operation => {
self._add_operation(popup, operation, item);
});
});
@@ -448,9 +444,9 @@ export default Backbone.View.extend({
// identify operation
var operation_name = $(operation_button).val();
// verify in any item is selected
// verify any item is selected
var number_of_checked_ids = this.$el.find('input[name="id"]:checked').length;
if (!number_of_checked_ids > 0) {
if (number_of_checked_ids < 1) {
return false;
}
@@ -548,7 +544,7 @@ export default Backbone.View.extend({
// show confirmation box
if (
confirmation_text &&
confirmation_text != "" &&
confirmation_text !== "" &&
confirmation_text != "None" &&
confirmation_text != "null"
)
@@ -568,10 +564,8 @@ export default Backbone.View.extend({
window.top.location = `${href}?${$.param(this.grid.get_url_data())}`;
} else if (target == "center") {
$("#galaxy_main").attr("src", `${href}?${$.param(this.grid.get_url_data())}`);
} else if (this.grid.can_async_op(operation) || this.dict_format) {
this.update_grid();
} else {
this.go_to(target, href);
this.update_grid();
}
// done
@@ -585,11 +579,7 @@ export default Backbone.View.extend({
}
// refresh grid
if (this.grid.get("async") || this.dict_format) {
this.update_grid();
} else {
this.go_to(target, href);
}
this.update_grid();
// done
return false;
@@ -597,10 +587,6 @@ export default Backbone.View.extend({
// go to url
go_to: function(target, href) {
// get aysnc status
var async = this.grid.get("async");
this.grid.set("async", false);
// get slide status
var advanced_search = this.$el.find("#advanced-search").is(":visible");
this.grid.set("advanced_search", advanced_search);
@@ -613,8 +599,7 @@ export default Backbone.View.extend({
// clear grid of transient request attributes.
this.grid.set({
operation: undefined,
item_ids: undefined,
async: async
item_ids: undefined
});
switch (target) {
case "center":
@@ -640,7 +625,7 @@ export default Backbone.View.extend({
type: method,
url: self.grid.get("url_base"),
data: self.grid.get_url_data(),
error: function(response) {
error: function() {
alert("Grid refresh failed");
},
success: function(response_text) {
@@ -650,7 +635,7 @@ export default Backbone.View.extend({
var advanced_search = self.$el.find("#advanced-search").is(":visible");
// request new configuration
var json = self.dict_format ? response_text : $.parseJSON(response_text);
var json = response_text;
// update
json.embedded = embedded;
+1 -3
View File
@@ -75,9 +75,7 @@ var HDCAListItemView = _super.extend(
} else if (!jobStatesSummary || !jobStatesSummary.hasDetails()) {
return `
<div class="progress state-progress">
<span class="note">Loading job data for ${
collectionTypeDescription
}.<span class="blinking">..</span></span>
<span class="note">Loading job data for ${collectionTypeDescription}.<span class="blinking">..</span></span>
<div class="progress-bar info" style="width:100%">
</div>`;
} else {
@@ -1,15 +1,14 @@
function _labelIfName(tag) {
if (tag.indexOf("name:") == 0) {
return `<span class="label label-info">${_.escape(tag.slice(5))}</span>`;
} else {
return "";
}
function _templateNametag(tag) {
return `<span class="label label-info">${_.escape(tag.slice(5))}</span>`;
}
function nametagTemplate(historyItem) {
return `<span class="nametags">${_.sortBy(_.uniq(historyItem.tags))
.map(_labelIfName)
.join("")}</span>`;
let uniqueNametags = _.filter(_.uniq(historyItem.tags), t => t.indexOf("name:") === 0);
let nametagsDisplay = _.sortBy(uniqueNametags).map(_templateNametag);
return `
<div class="nametags" title="${uniqueNametags.length} nametags">
${nametagsDisplay.join("")}
</div>`;
}
export default {
@@ -4,34 +4,8 @@ import Utils from "utils/utils";
import GridView from "mvc/grid/grid-view";
import HistoryModel from "mvc/history/history-model";
import historyCopyDialog from "mvc/history/copy-dialog";
var View = Backbone.View.extend({
title: _l("Histories"),
initialize: function(options) {
var self = this;
this.setElement($("<div/>"));
this.model = new Backbone.Model();
Utils.get({
url: `${Galaxy.root}history/${options.action_id}?${$.param(Galaxy.params)}`,
success: function(response) {
response["dict_format"] = true;
_.each(response["operations"], operation => {
if (operation.label == "Copy") {
operation.onclick = id => {
self._showCopyDialog(id);
};
}
});
self.model.set(response);
self.render();
}
});
},
render: function() {
var grid = new GridView(this.model.attributes);
this.$el.empty().append(grid.$el);
},
var HistoryGridView = GridView.extend({
_showCopyDialog: function(id) {
var history = new HistoryModel.History({ id: id });
history
@@ -47,6 +21,54 @@ var View = Backbone.View.extend({
window.location.reload(true);
});
});
},
/** Add an operation to the items menu */
_add_operation: function(popup, operation, item) {
var self = this;
var settings = item.operation_config[operation.label];
if (operation.label == "Copy") {
operation.onclick = id => {
self._showCopyDialog(id);
};
}
if (settings.allowed && operation.allow_popup) {
popup.addItem({
html: operation.label,
href: settings.url_args,
target: settings.target,
confirmation_text: operation.confirm,
func: function(e) {
e.preventDefault();
var label = $(e.target).html();
if (operation.onclick) {
operation.onclick(item.encode_id);
} else {
self.execute(this.findItemByHtml(label));
}
}
});
}
}
});
var View = Backbone.View.extend({
title: _l("Histories"),
initialize: function(options) {
var self = this;
this.setElement($("<div/>"));
this.model = new Backbone.Model();
Utils.get({
url: `${Galaxy.root}history/${options.action_id}?${$.param(Galaxy.params)}`,
success: function(response) {
self.model.set(response);
self.render();
}
});
},
render: function() {
var grid = new HistoryGridView(this.model.attributes);
this.$el.empty().append(grid.$el);
}
});
File diff suppressed because it is too large Load Diff
@@ -124,9 +124,9 @@ var LibraryView = Backbone.View.extend({
placeholder: "Click to select a role",
container: self.$el.find(`#${id}`),
ajax: {
url: `${Galaxy.root}api/libraries/${self.id}/permissions?scope=available&is_library_access=${
is_library_access
}`,
url: `${Galaxy.root}api/libraries/${
self.id
}/permissions?scope=available&is_library_access=${is_library_access}`,
dataType: "json",
quietMillis: 100,
data: function(term, page) {
@@ -7,7 +7,7 @@ import Deferred from "utils/deferred";
import Ui from "mvc/ui/ui-misc";
import FormBase from "mvc/form/form-view";
import Citations from "components/Citations.vue";
import Vue from "libs/vue";
import Vue from "vue";
export default FormBase.extend({
initialize: function(options) {
var self = this;
+1 -3
View File
@@ -269,9 +269,7 @@ var View = Backbone.View.extend({
} else if (batch_n !== n) {
this.form.highlight(
input_id,
`Please make sure that you select the same number of inputs for all batch mode fields. This field contains <b>${
n
}</b> selection(s) while a previous field contains <b>${batch_n}</b>.`
`Please make sure that you select the same number of inputs for all batch mode fields. This field contains <b>${n}</b> selection(s) while a previous field contains <b>${batch_n}</b>.`
);
return false;
}
@@ -48,7 +48,7 @@ var ToolShedRepositoryView = Backbone.View.extend({
this.options.tps_select_options = this.templatePanelSelectOptions;
this.options.tool_dependencies = models.get("tool_dependencies");
this.options.shed_tool_conf = this.templateShedToolConf({
stc_html: models.get("shed_conf")
shed_tool_confs: models.get("shed_conf")
});
this.options.panel_section_dict = models.get("panel_section_dict");
this.options.api_url = `${Galaxy.root}api/tool_shed_repositories/install?async=True`;
@@ -474,7 +474,11 @@ var ToolShedRepositoryView = Backbone.View.extend({
'<div class="toolFormTitle">Shed tool configuration file:</div>',
'<div class="toolFormBody">',
'<div class="form-row">',
"<%= stc_html %>",
'<select name="shed_tool_conf">',
"<% _.each(shed_tool_confs.options, function(conf) { %>",
'<option value="<%= conf.value %>"><%= conf.label %></option>',
"<% }); %>",
"</select>",
'<div class="toolParamHelp" style="clear: both;">Select the file whose <b>tool_path</b> setting you want used for installing repositories.</div>',
"</div>",
"</div>"
+53 -29
View File
@@ -4,7 +4,10 @@ import _l from "utils/localization";
* rendering a tour menu.
*/
import * as BootstrapTour from "libs/bootstrap-tour";
// bootstrap-tour configures a window.Tour object; keep a local ref.
import "libs/bootstrap-tour";
let Tour = window.Tour;
var gxy_root = typeof Galaxy === "undefined" ? "/" : Galaxy.root;
var tourpage_template = `<h2>Galaxy Tours</h2>
@@ -93,31 +96,8 @@ var Tours = Backbone.Collection.extend({
model: TourItem
});
var giveTour = tour_id => {
var url = `${gxy_root}api/tours/${tour_id}`;
$.getJSON(url, data => {
// Set hooks for additional click and data entry actions.
var tourdata = hooked_tour_from_data(data);
sessionStorage.setItem("activeGalaxyTour", JSON.stringify(data));
// Store tour steps in sessionStorage to easily persist w/o hackery.
var tour = new Tour(
_.extend(
{
steps: tourdata.steps
},
tour_opts
)
);
// Always clean restart, since this is a new, explicit giveTour execution.
tour.init();
tour.goTo(0);
tour.restart();
});
};
var ToursView = Backbone.View.extend({
export var ToursView = Backbone.View.extend({
title: _l("Tours"),
// initialize
initialize: function() {
var self = this;
this.setElement("<div/>");
@@ -165,7 +145,7 @@ var ToursView = Backbone.View.extend({
)
.on("click", ".tourItem", function(e) {
e.preventDefault();
giveTour($(this).data("tour.id"));
giveTourById($(this).data("tour.id"));
})
.on("click", ".tag-selector-button", e => {
var elem = $(e.target);
@@ -183,9 +163,53 @@ var ToursView = Backbone.View.extend({
}
});
export function giveTourWithData(data) {
let hookedTourData = hooked_tour_from_data(data);
sessionStorage.setItem("activeGalaxyTour", JSON.stringify(data));
// Store tour steps in sessionStorage to easily persist w/o hackery.
let tour = new Tour(_.extend({ steps: hookedTourData.steps }, tour_opts));
// Always clean restart, since this is a new, explicit execution.
tour.init();
tour.goTo(0);
tour.restart();
return tour;
}
export function giveTourById(tour_id) {
var url = `${gxy_root}api/tours/${tour_id}`;
$.getJSON(url, data => {
giveTourWithData(data);
});
}
export function activeGalaxyTourRunner() {
var et = JSON.parse(sessionStorage.getItem("activeGalaxyTour"));
if (et) {
et = hooked_tour_from_data(et);
if (et && et.steps) {
if (window && window.self === window.top) {
// Only kick off a new tour if this is the toplevel window (non-iframe). This
// functionality actually *could* be useful, but we'd need to handle it better and
// come up with some design guidelines for tours jumping between windows.
// Disabling for now.
var tour = new Tour(
_.extend(
{
steps: et.steps
},
tour_opts
)
);
tour.init();
tour.restart();
}
}
}
}
export default {
ToursView: ToursView,
hooked_tour_from_data: hooked_tour_from_data,
tour_opts: tour_opts,
giveTour: giveTour
giveTourWithData: giveTourWithData,
giveTourById: giveTourById,
activeGalaxyTourRunner: activeGalaxyTourRunner
};
+10 -11
View File
@@ -39,17 +39,16 @@ export var View = Backbone.View.extend({
if (!this.visible) {
this.visible = true;
this.$el.fadeIn("fast");
if (this.options.closing_events) {
var self = this;
$(document).on("keyup.ui-modal", e => {
if (e.keyCode == 27) {
self.hide(true);
}
});
this.$backdrop.on("click", () => {
self.hide(true);
});
}
}
if (this.options.closing_events) {
$(document).on("keyup.ui-modal", e => {
if (e.keyCode == 27) {
this.hide(true);
}
});
this.$backdrop.on("click", () => {
this.hide(true);
});
}
},
@@ -5,7 +5,6 @@ import Utils from "utils/utils";
import Buttons from "mvc/ui/ui-buttons";
var View = Backbone.View.extend({
initialize: function(options) {
var self = this;
this.data = [];
this.data2 = [];
this.model =
@@ -28,7 +27,9 @@ var View = Backbone.View.extend({
pagesize: 20
}).set(options);
this.on("change", () => {
self.model.get("onchange") && self.model.get("onchange")(self.value());
if (this.model.get("onchange")) {
this.model.get("onchange")(this.value());
}
});
this.listenTo(this.model, "change:data", this._changeData, this);
this.listenTo(this.model, "change:disabled", this._changeDisabled, this);
@@ -40,8 +41,11 @@ var View = Backbone.View.extend({
},
render: function() {
var self = this;
this.model.get("searchable") ? this._renderSearchable() : this._renderClassic();
if (this.model.get("searchable")) {
this._renderSearchable();
} else {
this._renderClassic();
}
this.$el.addClass(this.model.get("cls")).attr("id", this.model.get("id"));
this.$select
.empty()
@@ -49,8 +53,8 @@ var View = Backbone.View.extend({
.attr("id", `${this.model.get("id")}_select`)
.prop("multiple", this.model.get("multiple"))
.on("change", () => {
self.value(self._getValue());
self.trigger("change");
this.value(this._getValue());
this.trigger("change");
});
this._changeData();
this._changeWait();
@@ -60,7 +64,6 @@ var View = Backbone.View.extend({
/** Renders the classic selection field */
_renderClassic: function() {
var self = this;
this.$el
.addClass(this.model.get("multiple") ? "ui-select-multiple" : "ui-select")
.append((this.$select = $("<select/>")))
@@ -76,12 +79,12 @@ var View = Backbone.View.extend({
.off("mousedown")
.on("mousedown", event => {
var currentY = event.pageY;
var currentHeight = self.$select.height();
self.minHeight = self.minHeight || currentHeight;
var currentHeight = this.$select.height();
this.minHeight = this.minHeight || currentHeight;
$("#dd-helper")
.show()
.on("mousemove", event => {
self.$select.height(Math.max(currentHeight + (event.pageY - currentY), self.minHeight));
this.$select.height(Math.max(currentHeight + (event.pageY - currentY), this.minHeight));
})
.on("mouseup mouseleave", () => {
$("#dd-helper")
@@ -98,25 +101,27 @@ var View = Backbone.View.extend({
/** Renders the default select2 field */
_renderSearchable: function() {
var self = this;
this.$el.append((this.$select = $("<div/>"))).append((this.$dropdown = $("<div/>")));
this.$dropdown.hide();
if (!this.model.get("multiple")) {
this.$dropdown.show().on("click", () => {
self.$select.select2 && self.$select.select2("open");
if (this.$select.select2) {
this.$select.select2("open");
}
});
}
this.all_button = null;
if (this.model.get("multiple") && !this.model.get("individual") && !this.model.get("readonly")) {
this.all_button = new Buttons.ButtonCheck({
onclick: function() {
onclick: () => {
var new_value = [];
self.all_button.value() !== 0 &&
_.each(self.model.get("data"), option => {
if (this.all_button.value() !== 0) {
_.each(this.model.get("data"), option => {
new_value.push(option.value);
});
self.value(new_value);
self.trigger("change");
}
this.value(new_value);
this.trigger("change");
}
});
this.$el.prepend(this.all_button.$el);
@@ -127,7 +132,7 @@ var View = Backbone.View.extend({
_match: function(term, text) {
return (
!term ||
term == "" ||
term === "" ||
String(text)
.toUpperCase()
.indexOf(term.toUpperCase()) >= 0
@@ -136,18 +141,17 @@ var View = Backbone.View.extend({
/** Updates the selection options */
_changeData: function() {
var self = this;
this.data = [];
if (!this.model.get("multiple") && this.model.get("optional")) {
this.data.push({
value: "__null__",
label: self.model.get("empty_text")
label: this.model.get("empty_text")
});
}
_.each(this.model.get("data"), option => {
self.data.push(option);
this.data.push(option);
});
if (this.length() == 0) {
if (this.length() === 0) {
this.data.push({
value: "__null__",
label: this.model.get("error_text")
@@ -156,61 +160,71 @@ var View = Backbone.View.extend({
if (this.model.get("searchable")) {
this.data2 = [];
_.each(this.data, (option, index) => {
self.data2.push({
this.data2.push({
order: index,
id: option.value,
text: option.label,
tags: option.tags
});
});
this.$select.data("select2") && this.$select.select2("destroy");
if (this.$select.data("select2")) {
this.$select.select2("destroy");
}
this.matched_tags = {};
this.$select.select2({
data: self.data2,
data: this.data2,
closeOnSelect: !this.model.get("multiple"),
multiple: this.model.get("multiple"),
query: function(q) {
self.matched_tags = {};
var pagesize = self.model.get("pagesize");
var results = _.filter(self.data2, e => {
query: q => {
this.matched_tags = {};
var pagesize = this.model.get("pagesize");
var results = _.filter(this.data2, e => {
var found = false;
_.each(e.tags, tag => {
if (self._match(q.term, tag)) {
found = self.matched_tags[tag] = true;
if (this._match(q.term, tag)) {
found = this.matched_tags[tag] = true;
}
});
return found || self._match(q.term, e.text);
return found || this._match(q.term, e.text);
});
q.callback({
results: results.slice((q.page - 1) * pagesize, q.page * pagesize),
more: results.length >= q.page * pagesize
});
},
formatResult: function(result) {
return `${_.escape(result.text)}<div class="ui-tags">${_.reduce(
result.tags,
(memo, tag) => {
if (self.matched_tags[tag]) {
formatResult: result => {
let extraTagWarning = "";
let filteredTags = _.filter(result.tags, t => this.matched_tags.hasOwnProperty(t));
if (filteredTags.length > 5) {
extraTagWarning = `&nbsp;<div class="label label-warning">${filteredTags.length -
5} more tags</div>`;
}
return `
${_.escape(result.text)}
<div class="ui-tags">
${_.reduce(
filteredTags.slice(0, 5),
(memo, tag) => {
return `${memo}&nbsp;<div class="label label-info">${_.escape(tag)}</div>`;
}
return memo;
},
""
)}</div>`;
},
""
)}
${extraTagWarning}
</div>`;
}
});
this.$(".select2-container .select2-search input").off("blur");
} else {
this.$select.find("option").remove();
_.each(this.data, option => {
self.$select.append(
this.$select.append(
$("<option/>")
.attr("value", option.value)
.html(_.escape(option.label))
);
});
}
this.model.set("disabled", this.model.get("readonly") || this.length() == 0);
this.model.set("disabled", this.model.get("readonly") || this.length() === 0);
this._changeValue();
},
@@ -252,7 +266,9 @@ var View = Backbone.View.extend({
/** Return/Set current selection */
value: function(new_value) {
new_value !== undefined && this.model.set("value", new_value);
if (new_value !== undefined) {
this.model.set("value", new_value);
}
return this._getValue();
},
@@ -311,9 +327,13 @@ var View = Backbone.View.extend({
/** Update all available options at once */
add: function(options, sorter) {
_.each(this.model.get("data"), v => {
v.keep && !_.findWhere(options, { value: v.value }) && options.push(v);
if (v.keep && !_.findWhere(options, { value: v.value })) {
options.push(v);
}
});
sorter && options && options.sort(sorter);
if (sorter && options) {
options.sort(sorter);
}
this.model.set("data", options);
},
@@ -334,7 +354,6 @@ var View = Backbone.View.extend({
/** Set value to dom */
_setValue: function(new_value) {
var self = this;
if (new_value === null || new_value === undefined) {
new_value = "__null__";
}
@@ -351,8 +370,10 @@ var View = Backbone.View.extend({
if ($.isArray(new_value)) {
var val = [];
_.each(new_value, v => {
var d = _.findWhere(self.data2, { id: v });
d && val.push(d);
var d = _.findWhere(this.data2, { id: v });
if (d) {
val.push(d);
}
});
new_value = val;
} else {
@@ -1,8 +1,7 @@
// dependencies
import Utils from "utils/utils";
import Ui from "mvc/ui/ui-misc";
import Table from "mvc/ui/ui-table";
import List from "mvc/ui/ui-list";
// collection of libraries
var Libraries = Backbone.Collection.extend({
url: `${Galaxy.root}api/libraries?deleted=false`
@@ -85,7 +84,9 @@ var View = Backbone.View.extend({
// add change event. fires on trigger
this.on("change", () => {
options.onchange && options.onchange(self.value());
if (options.onchange) {
options.onchange(self.value());
}
});
// create elements
@@ -62,7 +62,7 @@ var Model = Backbone.Model.extend({
description: _l("Associate OpenIDs with your account."),
icon: "fa-openid",
onclick: function() {
window.location.href = `${Galaxy.root}user/openid_manage?cntrller=user&use_panels=True`;
window.location.href = `${Galaxy.root}openids/list`;
}
},
custom_builds: {
@@ -106,7 +106,7 @@ $.extend(CanvasManager.prototype, {
// If it appears that the user is trying to copy/paste text, we
// pass that through.
if (window.getSelection().toString() === "") {
if (this.app.workflow.active_node) {
if (this.app.workflow.active_node && this.app.workflow.active_node.type !== "subworkflow") {
e.clipboardData.setData(
"application/json",
JSON.stringify({
@@ -35,9 +35,7 @@ $.extend(Workflow.prototype, {
}
if (!this.canLabelOutputWith(toLabel)) {
Toastr.warning(
`Workflow contains duplicate workflow output labels ${
toLabel
}. This must be fixed before it can be saved.`
`Workflow contains duplicate workflow output labels ${toLabel}. This must be fixed before it can be saved.`
);
}
if (toLabel) {
@@ -8,10 +8,10 @@ function CollectionTypeDescription(collectionType) {
var NULL_COLLECTION_TYPE_DESCRIPTION = {
isCollection: false,
canMatch: function(other) {
canMatch: function() {
return false;
},
canMapOver: function(other) {
canMapOver: function() {
return false;
},
toString: function() {
@@ -30,13 +30,13 @@ var ANY_COLLECTION_TYPE_DESCRIPTION = {
canMatch: function(other) {
return NULL_COLLECTION_TYPE_DESCRIPTION !== other;
},
canMapOver: function(other) {
canMapOver: function() {
return false;
},
toString: function() {
return "AnyCollectionType[]";
},
append: function(otherCollectionType) {
append: function() {
throw "Cannot append to ANY_COLLECTION_TYPE_DESCRIPTION";
},
equal: function(other) {
@@ -50,7 +50,7 @@ $.extend(CollectionTypeDescription.prototype, {
return this;
}
if (otherCollectionTypeDescription === ANY_COLLECTION_TYPE_DESCRIPTION) {
return otherCollectionType;
return otherCollectionTypeDescription;
}
return new CollectionTypeDescription(`${this.collectionType}:${otherCollectionTypeDescription.collectionType}`);
},
@@ -143,7 +143,9 @@ var Terminal = Backbone.Model.extend({
},
destroyInvalidConnections: function() {
_.each(this.connectors, connector => {
connector && connector.destroyIfInvalid();
if (connector) {
connector.destroyIfInvalid();
}
});
},
setMapOver: function(val) {
@@ -258,7 +260,7 @@ var BaseInputTerminal = Terminal.extend({
if (this._collectionAttached()) {
// Can only attach one collection to multiple input
// data parameter.
inputsFilled = true;
inputFilled = true;
} else {
inputFilled = false;
}
@@ -317,17 +319,17 @@ var BaseInputTerminal = Terminal.extend({
if (thisDatatype == "input") {
return true;
}
var cat_outputs = new Array();
var cat_outputs = [];
cat_outputs = cat_outputs.concat(other.datatypes);
if (other.node.post_job_actions) {
for (var pja_i in other.node.post_job_actions) {
var pja = other.node.post_job_actions[pja_i];
if (
pja.action_type == "ChangeDatatypeAction" &&
(pja.output_name == "" || pja.output_name == other.name) &&
(pja.output_name === "" || pja.output_name == other.name) &&
pja.action_arguments
) {
cat_outputs.push(pja.action_arguments["newtype"]);
cat_outputs.push(pja.action_arguments.newtype);
}
}
}
@@ -277,9 +277,7 @@ export default Backbone.View.extend({
if (upgrade_message) {
window.show_modal(
"Issues loading this workflow",
`Please review the following issues, possibly resulting from tool upgrades or changes.<p><ul>${
upgrade_message
}</ul></p>`,
`Please review the following issues, possibly resulting from tool upgrades or changes.<p><ul>${upgrade_message}</ul></p>`,
{ Continue: hide_modal }
);
} else {
@@ -560,9 +558,7 @@ export default Backbone.View.extend({
if (upgrade_message) {
window.show_modal(
"Subworkflow embedded with changes",
`Problems were encountered loading this workflow (possibly a result of tool upgrades). Please review the following parameters and then save.<ul>${
upgrade_message
}</ul>`,
`Problems were encountered loading this workflow (possibly a result of tool upgrades). Please review the following parameters and then save.<ul>${upgrade_message}</ul>`,
{ Continue: hide_modal }
);
} else {
@@ -764,13 +760,15 @@ export default Backbone.View.extend({
// Fix width to computed width
// Now add floats
var buttons = $("<div class='buttons' style='float: right;'></div>");
buttons.append(
$("<div/>")
.addClass("fa-icon-button fa fa-files-o")
.click(e => {
node.clone();
})
);
if (type !== "subworkflow") {
buttons.append(
$("<div/>")
.addClass("fa-icon-button fa fa-files-o")
.click(e => {
node.clone();
})
);
}
buttons.append(
$("<div/>")
.addClass("fa-icon-button fa fa-times")
+45 -49
View File
@@ -5,7 +5,7 @@ import WORKFLOWS from "mvc/workflow/workflow-model";
import QueryStringParsing from "utils/query-string-parsing";
import _l from "utils/localization";
/** View of the individual workflows */
var WorkflowItemView = Backbone.View.extend({
const WorkflowItemView = Backbone.View.extend({
tagName: "tr", // name of (orphan) root tag in this.el
initialize: function() {
_.bindAll(
@@ -33,15 +33,20 @@ var WorkflowItemView = Backbone.View.extend({
},
showInToolPanel: function() {
this.model.set("show_in_tool_panel", !this.model.get("show_in_tool_panel"));
this.model.save();
// This reloads the whole page, so that the workflow appears in the tool panel.
// Ideally we would notify only the tool panel of a change
window.location = `${Galaxy.root}workflows/list`;
this.model.save(
{ show_in_tool_panel: !this.model.get("show_in_tool_panel") },
{
success: function() {
window.location = `${Galaxy.root}workflows/list`;
}
}
);
},
removeWorkflow: function() {
var wfName = this.model.get("name");
const wfName = this.model.get("name");
if (window.confirm(`Are you sure you want to delete workflow '${wfName}'?`)) {
this.model.destroy({
success: function() {
@@ -53,8 +58,8 @@ var WorkflowItemView = Backbone.View.extend({
},
renameWorkflow: function() {
var oldName = this.model.get("name");
var newName = window.prompt(`Enter a new Name for workflow '${oldName}'`, oldName);
const oldName = this.model.get("name");
const newName = window.prompt(`Enter a new Name for workflow '${oldName}'`, oldName);
if (newName) {
this.model.save(
{ name: newName },
@@ -69,16 +74,15 @@ var WorkflowItemView = Backbone.View.extend({
},
copyWorkflow: function() {
var self = this;
var oldName = this.model.get("name");
const oldName = this.model.get("name");
$.getJSON(`${this.model.urlRoot}/${this.model.id}/download`, wfJson => {
var newName = `Copy of ${oldName}`;
var currentOwner = self.model.get("owner");
let newName = `Copy of ${oldName}`;
const currentOwner = this.model.get("owner");
if (currentOwner != Galaxy.user.attributes.username) {
newName += ` shared by user ${currentOwner}`;
}
wfJson.name = newName;
self.collection.create(wfJson, {
this.collection.create(wfJson, {
at: 0,
wait: true,
success: function() {
@@ -96,16 +100,14 @@ var WorkflowItemView = Backbone.View.extend({
},
_rowTemplate: function() {
var show = this.model.get("show_in_tool_panel");
var wfId = this.model.id;
var checkboxHtml = `<input id="show-in-tool-panel" type="checkbox" class="show-in-tool-panel" ${
let show = this.model.get("show_in_tool_panel");
let wfId = this.model.id;
const checkboxHtml = `<input id="show-in-tool-panel" type="checkbox" class="show-in-tool-panel" ${
show ? `checked="${show}"` : ""
} value="${wfId}">`;
var trHtml = `<td><div class="dropdown"><button class="menubutton" type="button" data-toggle="dropdown">${_.escape(
const trHtml = `<td><div class="dropdown"><button class="menubutton" type="button" data-toggle="dropdown">${_.escape(
this.model.get("name")
)}<span class="caret"></span></button>${this._templateActions()}</div></td><td><span><div class="${
wfId
} tags-display"></div></td><td>${
)}<span class="caret"></span></button>${this._templateActions()}</div></td><td><span><div class="${wfId} tags-display"></div></td><td>${
this.model.get("owner") === Galaxy.user.attributes.username ? "You" : this.model.get("owner")
}</span></td><td>${this.model.get("number_of_steps")}</td><td>${
this.model.get("published") ? "Yes" : "No"
@@ -114,7 +116,7 @@ var WorkflowItemView = Backbone.View.extend({
},
renderTagEditor: function() {
var TagEditor = new TAGS.TagsEditor({
const TagEditor = new TAGS.TagsEditor({
model: this.model,
el: $.find(`.${this.model.id}.tags-display`),
workflow_mode: true
@@ -152,7 +154,7 @@ var WorkflowItemView = Backbone.View.extend({
});
/** View of the main workflow list page */
var WorkflowListView = Backbone.View.extend({
const WorkflowListView = Backbone.View.extend({
title: _l("Workflows"),
initialize: function() {
this.setElement("<div/>");
@@ -182,18 +184,16 @@ var WorkflowListView = Backbone.View.extend({
// TODO: check that file is valid galaxy workflow
this.unhighlightDropZone();
e.preventDefault();
var files = e.dataTransfer.files;
var self = this;
for (var i = 0, f; (f = files[i]); i++) {
self.readWorkflowFiles(f);
const files = e.dataTransfer.files;
for (let i = 0, f; (f = files[i]); i++) {
this.readWorkflowFiles(f);
}
},
readWorkflowFiles: function(f) {
var self = this;
var reader = new FileReader();
const reader = new FileReader();
reader.onload = theFile => {
var wf_json;
let wf_json;
try {
wf_json = JSON.parse(reader.result);
} catch (e) {
@@ -201,7 +201,7 @@ var WorkflowListView = Backbone.View.extend({
wf_json = null;
}
if (wf_json) {
self.collection.create(wf_json, {
this.collection.create(wf_json, {
at: 0,
wait: true,
success: function() {
@@ -218,8 +218,8 @@ var WorkflowListView = Backbone.View.extend({
_showArgErrors: _.once(() => {
// Parse args out of params, display if there's a message.
var msg_text = QueryStringParsing.get("message");
var msg_status = QueryStringParsing.get("status");
const msg_text = QueryStringParsing.get("message");
const msg_status = QueryStringParsing.get("status");
if (msg_status === "error") {
mod_toastr.error(_.escape(msg_text || "Unknown Error, please report this to an administrator."));
} else if (msg_text) {
@@ -229,18 +229,17 @@ var WorkflowListView = Backbone.View.extend({
render: function() {
// Add workflow header
var header = this._templateHeader();
const header = this._templateHeader();
// Add the actions buttons
var templateActions = this._templateActionButtons();
var tableTemplate = this._templateWorkflowTable();
const templateActions = this._templateActionButtons();
const tableTemplate = this._templateWorkflowTable();
this.$el.html(header + templateActions + tableTemplate);
var self = this;
_(this.collection.models).each(item => {
// in case collection is not empty
self.appendItem(item);
self.confirmDelete(item);
this.appendItem(item);
this.confirmDelete(item);
}, this);
var minQueryLength = 3;
const minQueryLength = 3;
this.searchWorkflow(this.$(".search-wf"), this.$(".workflow-search tr"), minQueryLength);
this.adjustActiondropdown();
this._showArgErrors();
@@ -248,7 +247,7 @@ var WorkflowListView = Backbone.View.extend({
},
appendItem: function(item) {
var workflowItemView = new WorkflowItemView({
const workflowItemView = new WorkflowItemView({
model: item,
collection: this.collection
});
@@ -258,7 +257,7 @@ var WorkflowListView = Backbone.View.extend({
/** Add confirm box before removing/unsharing workflow */
confirmDelete: function(workflow) {
var $el_shared_wf_link = this.$(`.link-confirm-shared-${workflow.id}`);
const $el_shared_wf_link = this.$(`.link-confirm-shared-${workflow.id}`);
$el_shared_wf_link.click(() =>
window.confirm(`Are you sure you want to remove the shared workflow '${workflow.attributes.name}'?`)
);
@@ -267,12 +266,12 @@ var WorkflowListView = Backbone.View.extend({
/** Implement client side workflow search/filtering */
searchWorkflow: function($el_searchinput, $el_tabletr, min_querylen) {
$el_searchinput.on("keyup", function() {
var query = $(this).val();
const query = $(this).val();
// Filter when query is at least 3 characters
// otherwise show all rows
if (query.length >= min_querylen) {
// Ignore the query's case using 'i'
var regular_expression = new RegExp(query, "i");
const regular_expression = new RegExp(query, "i");
$el_tabletr.hide();
$el_tabletr
.filter(function() {
@@ -313,7 +312,7 @@ var WorkflowListView = Backbone.View.extend({
/** Template for workflow table */
_templateWorkflowTable: function() {
var tableHtml =
const tableHtml =
'<table class="table colored"><thead>' +
'<tr class="header">' +
"<th>Name</th>" +
@@ -323,9 +322,7 @@ var WorkflowListView = Backbone.View.extend({
"<th>Published</th>" +
"<th>Show in tools panel</th>" +
"</tr></thead>";
return `${
tableHtml
}<tbody class="workflow-search "><div class="hidden_description_layer"><p>Drop workflow files here to import</p></tbody></table></div>`;
return `${tableHtml}<tbody class="workflow-search "><div class="hidden_description_layer"><p>Drop workflow files here to import</p></tbody></table></div>`;
},
/** Main template */
@@ -343,7 +340,7 @@ var WorkflowListView = Backbone.View.extend({
}
});
var ImportWorkflowView = Backbone.View.extend({
const ImportWorkflowView = Backbone.View.extend({
initialize: function() {
this.setElement("<div/>");
this.render();
@@ -351,9 +348,8 @@ var ImportWorkflowView = Backbone.View.extend({
/** Open page to import workflow */
render: function() {
var self = this;
$.getJSON(`${Galaxy.root}workflow/upload_import_workflow`, options => {
self.$el.empty().append(self._mainTemplate(options));
this.$el.empty().append(this._mainTemplate(options));
});
},
+171 -96
View File
@@ -1,169 +1,244 @@
/** zh localization */
/* by steven shen */
define({
// ----------------------------------------------------------------------------- masthead
"Analyze Data": "数据分析",
Workflow: "流程",
"Shared Data": "共享的数据",
"Data Libraries": "数据库",
Histories: "已发布的历史",
Workflows: "已发布的流程",
Visualizations: "已发布的可视化",
Pages: "已发布的页面",
Visualization: "可视化",
"New Track Browser": "创建 Track Browser",
"Saved Visualizations": "已保存的保存",
"Interactive Environments": "交互式环境",
Admin: "管理员",
Help: "帮助",
Support: "支持",
Search: "搜索",
"Mailing Lists": "邮件列表",
Videos: "视频",
Wiki: false,
"How to Cite Galaxy": "引用",
"Interactive Tours": "使用引导",
User: "用户",
Login: "登陆",
Register: "注册",
"Login or Register": "登陆或注册",
"Logged in as": "您已登陆为",
Preferences: "用户偏好性",
"Custom Builds": "自定义构建集",
Logout: "退出",
"Saved Histories": "保存历史",
"Saved Datasets": "保存数据集",
"Saved Pages": "保存页面",
//Tooltip
"Account and saved data": "账号及数据保存",
"Account registration or login": "注册与登录",
"Support, contact, and community": "支持,联系,社区",
"Administer this Galaxy": "管理您的Galaxy",
"Visualize datasets": "数据集可视化",
"Access published resources": "访问发布的资源",
"Chain tools into workflows": "将工具链接到工作流程中",
"Analysis home view": "数据分析主页",
// ---------------------------------------------------------------------------- histories
// ---- history/options-menu
"History Lists": "历史列表",
// Saved histories is defined above.
"Saved Histories": "保存历史",
"Histories Shared with Me": "分享给您历史",
"Current History": "当前历史",
"Create New": "创建新的历史",
"Copy History": "复制历史",
"Share or Publish": "分享或发布历史",
"Show Structure": "展示结构",
"Extract Workflow": "提取为工作流",
// Delete is defined elsewhere, but is also in this menu.
Delete: "删除",
"Delete Permanently": "永久删除",
"Dataset Actions": "数据操作",
"Copy Datasets": "复制数据集",
"Dataset Security": "数据安全",
"Resume Paused Jobs": "恢复已暂停的任务",
"Collapse Expanded Datasets": false,
"Unhide Hidden Datasets": "取消隐藏的数据集",
"Delete Hidden Datasets": "删除隐藏的数据集",
"Purge Deleted Datasets": "清空已删除的数据集",
Downloads: "下载",
"Export Tool Citations": "导出工具文献引用",
"Export History to File": "导出历史到文件",
"Other Actions": "其他操作",
"Import from File": "从文件导入",
Webhooks: false,
// ---- history-model
// ---- history-view
"This history is empty": "历史已空",
"No matching datasets found": "未找到匹配的数据集",
//"An error occurred while getting updates from the server" :
//false,
//"Please contact a Galaxy administrator if the problem persists" :
//false,
"An error occurred while getting updates from the server": "从服务器更新出现错误",
"Please contact a Galaxy administrator if the problem persists": "如果问题仍然存在,请联系Galaxy管理员",
//TODO:
//"An error was encountered while <% where %>" :
//false,
"Search datasets": "搜索数据集",
"You are currently viewing a deleted history!": "正在查看已删除的历史",
"You are over your disk quota": "您已超过磁盘配额",
//"Tool execution is on hold until your disk usage drops below your allocated quota" :
//false,
All: "皆",
"An error was encountered while <% where %>": "出现错误,当执行以下操作时: <% where %>",
"search datasets": "搜索数据集",
"You are currently viewing a deleted history!": "您正在查看已删除的历史记录",
"You are over your disk quota": "您超出了您的磁盘配额",
"Tool execution is on hold until your disk usage drops below your allocated quota":
"工具执行处于暂停状态,直到您的磁盘使用量低于您分配的配额",
All: "所有的",
None: "一个也没有",
"For all selected": "为每个选定",
// ---- history-view-edit
//"Edit history tags" :
//false,
//"Edit history Annotation" :
//false,
"Edit history tags": "编辑历史标签",
"Edit history Annotation": "编辑历史备注",
"Click to rename history": "单击要重命名的历史",
// multi operations
"Operations on multiple datasets": "编辑多个数据集",
//"Hide datasets" :
//false,
//"Unhide datasets" :
//false,
//"Delete datasets" :
//false,
//"Undelete datasets" :
//false,
"Hide datasets": "隐藏数据集",
"Unhide datasets": "显示数据集",
"Delete datasets": "删除数据集",
"Undelete datasets": "取消删除数据集",
"Permanently delete datasets": "永久删除数据集",
"This will permanently remove the data in your datasets. Are you sure?": "这将永久在你的数据集删除数据。你确定?",
"This will permanently remove the data in your datasets. Are you sure?": "这将永久在您的数据集删除数据。你确定?",
// ---- history-view-annotated
Dataset: "数据集",
//"Annotation" :
//false,
Dataset: "数据",
Annotation: "备注",
// ---- history-view-edit-current
"This history is empty. Click 'Get Data' on the left tool menu to start": "历史已空,请单击左边窗格中‘获取数据’",
"You must be logged in to create histories": "你必须登录后才能创建历史",
//TODO:
//"You can <% loadYourOwn %> or <% externalSource %>" :
"You can ": "您可以 ",
" or ": " 或者 ",
"load your own data": "上传您的个人数据",
"get data from an external source": "从外部来源获取数据",
// these aren't in zh/ginga.po and the template doesn't localize
//"Include Deleted Datasets" :
//false,
//"load your own data" :
//false,
//"get data from an external source" :
//"Include Hidden Datasets" :
//false,
// ---------------------------------------------------------------------------- datasets
// ---- hda-model
//"Unable to purge dataset" :
//false,
"Unable to purge dataset": "无法清除数据集",
// ---- hda-base
// display button
//"Cannot display datasets removed from disk" :
//false,
//"This dataset must finish uploading before it can be viewed" :
//false,
//"This dataset is not yet viewable" :
//false,
"View data": "数据",
"Cannot display datasets removed from disk": "无法显示已从磁盘中删除的数据集",
"This dataset must finish uploading before it can be viewed": "此数据集必须先完成上传, 然后才能查看",
"This dataset is not yet viewable": "此数据集是不可见的",
"View data": "查看数据",
// download button
Download: "下载",
"Download dataset": "下载数据集",
//"Additional files" :
//false,
"Additional files": "其他文件",
// info/show_params
"View details": "查看详情",
// dataset states
// state: new
//"This is a new dataset and not all of its data are available yet" :
//false,
"This is a new dataset and not all of its data are available yet": "这是一个新的数据集,并不是所有的数据都可用",
// state: noPermission
//"You do not have permission to view this dataset" :
//false,
"You do not have permission to view this dataset": "您无权查看此数据集",
// state: discarded
//"The job creating this dataset was cancelled before completion" :
//false,
"The job creating this dataset was cancelled before completion": "创建此数据集的任务在完成之前已被取消",
// state: queued
"This job is waiting to run": "等待运行的进程",
"This job is waiting to run": "任务正在等待运行",
// state: upload
//"This dataset is currently uploading" :
//false,
"This dataset is currently uploading": "此数据集正在上传中",
// state: setting_metadata
//"Metadata is being auto-detected" :
//false,
"Metadata is being auto-detected": "元数据正在被自动检测中",
// state: running
"This job is currently running": "正在运行的进程",
"This job is currently running": "任务正在运行中",
// state: paused
//"This job is paused. Use the \"Resume Paused Jobs\" in the history menu to resume" :
//false,
'This job is paused. Use the "Resume Paused Jobs" in the history menu to resume':
'此任务已暂停。使用历史菜单中的 "恢复已暂停的工作" 来恢复',
// state: error
"An error occurred with this dataset": "进程运行时出错",
"An error occurred with this dataset": "此数据集发生错误",
// state: empty
"No data": "没有数据",
// state: failed_metadata
//"An error occurred setting the metadata for this dataset" :
//false,
"An error occurred setting the metadata for this dataset": "设置此数据集的元数据时发生错误",
// ajax error prefix
//"There was an error getting the data for this dataset" :
//false,
"There was an error getting the data for this dataset": "获取此数据集的数据时出错",
// purged'd/del'd msg
//"This dataset has been deleted and removed from disk" :
//false,
"This dataset has been deleted and removed from disk": "此数据集已被删除并从磁盘中删除",
"This dataset has been deleted": "此数据集已被删除",
"This dataset has been hidden": "此数据集已隐藏",
"This dataset has been hidden": "此数据集已被隐藏",
format: "格式",
database: "数据库",
// ---- hda-edit
"Edit attributes": "编辑属性",
//"Cannot edit attributes of datasets removed from disk" :
//false,
//"Undelete dataset to edit attributes" :
//false,
//"This dataset must finish uploading before it can be edited" :
//false,
//"This dataset is not yet editable" :
//false,
"Cannot edit attributes of datasets removed from disk": "无法编辑已从磁盘中删除的数据集的属性",
"Undelete dataset to edit attributes": "取消删除数据集以编辑属性",
"This dataset must finish uploading before it can be edited": "该数据集必须先上传完成, 然后才能编辑",
"This dataset is not yet editable": "该数据集不可编辑",
Delete: "删除",
//"Dataset is already deleted" :
//false,
"Dataset is already deleted": "数据集已被删除",
"View or report this error": "报告错误",
"View or report this error": "查看或报告此错误",
"Run this job again": "重新运行",
"Run this job again": "重新运行此任务",
Visualize: "图形",
//"Visualize in" :
//false,
Visualize: "可视化",
"Visualize in": "的可视化",
"Undelete it": "反删除",
"Undelete it": "取消删除",
"Permanently remove it from disk": "从磁盘中永久删除",
"Unhide it": "取消隐藏"
"Unhide it": "取消隐藏",
//"You may be able to" :
//false,
//"set it manually or retry auto-detection" :
//false,
"You may be able to": "您可能可以",
"set it manually or retry auto-detection": "手动设置或重试自动检测",
//"Edit dataset tags" :
//false,
//"Edit dataset annotation" :
//false,
"Edit dataset tags": "编辑数据集标签",
"Edit dataset annotation": "编辑数据集备注",
"Tool Help": "工具帮助",
// ---------------------------------------------------------------------------- admin
"Search Tool Shed": "搜索 Tool Shed",
"Monitor installing repositories": "",
"Manage installed tools": "管理已安装的工具",
"Reset metadata": "重置元数据",
"Download local tool": "下载本地工具",
"Tool lineage": false,
"Reload a tool's configuration": "重新加载工具的配置",
"Review tool migration stages": "查看工具迁移状态",
"View Tool Error Logs": "查看工具错误日志",
"Manage Display Whitelist": "管理显示白名单",
"Manage Tool Dependencies": "管理工具依赖",
Users: "用户",
Groups: "组别",
"API keys": false,
"Impersonate a user": "模拟用户",
Data: "数据",
Quotas: "配额",
Roles: "角色",
"Local data": "本地数据",
"Form Definitions": "定义表单",
// ---------------------------------------------------------------------------- Scratchbook
"Enable/Disable Scratchbook": "启用/禁用 Scratchbook",
"Show/Hide Scratchbook": "显示/隐藏 Scratchbook",
// ---------------------------------------------------------------------------- misc. MVC
//"Tags" :
//false,
//"Annotation" :
//false,
//"Edit annotation" :
//false,
Tags: "标签",
"Edit annotation": "编辑备注",
// ----------------------------------------------------------------------------
ja: true,
fr: true,
zh: true
});
+23 -48
View File
@@ -19,13 +19,12 @@ import layout_modal from "layout/modal";
_.extend(window, layout_modal);
import async_save_text from "utils/async-save-text";
window.async_save_text = async_save_text;
import POPUPMENU from "ui/popupmenu";
window.make_popupmenu = POPUPMENU.make_popupmenu;
window.make_popup_menus = POPUPMENU.make_popup_menus;
import Popupmenu from "ui/popupmenu";
window.make_popupmenu = Popupmenu.make_popupmenu;
window.make_popup_menus = Popupmenu.make_popup_menus;
import init_tag_click_function from "ui/autocom_tagging";
window.init_tag_click_function = init_tag_click_function;
import TOURS from "mvc/tours";
import QUERY_STRING from "utils/query-string-parsing";
import Tours from "mvc/tours";
// console.debug( 'galaxy globals loaded' );
// ============================================================================
@@ -89,10 +88,7 @@ function init_refresh_on_change() {
.change(function() {
var select_field = $(this);
var select_val = select_field.val();
var refresh = false;
var ref_on_change_vals = select_field.attr("refresh_on_change_values");
if (ref_on_change_vals) {
ref_on_change_vals = ref_on_change_vals.split(",");
var last_selected_value = select_field.attr("last_selected_value");
@@ -114,10 +110,7 @@ function init_refresh_on_change() {
.click(function() {
var select_field = $(this);
var select_val = select_field.val();
var refresh = false;
var ref_on_change_vals = select_field.attr("refresh_on_change_values");
if (ref_on_change_vals) {
ref_on_change_vals = ref_on_change_vals.split(",");
var last_selected_value = select_field.attr("last_selected_value");
@@ -156,7 +149,7 @@ $(document).ready(() => {
$("[title]").tooltip();
}
// Make popup menus.
make_popup_menus();
Popupmenu.make_popup_menus();
// Replace big selects.
replace_big_select_inputs(20, 1500);
@@ -165,7 +158,7 @@ $(document).ready(() => {
// add use_panels=True and set target to self.
$("a").click(function() {
var anchor = $(this);
var galaxy_main_exists = parent.frames && parent.frames.galaxy_main;
var galaxy_main_exists = window.parent.frames && window.parent.frames.galaxy_main;
if (anchor.attr("target") == "galaxy_main" && !galaxy_main_exists) {
var href = anchor.attr("href");
if (href.indexOf("?") == -1) {
@@ -180,44 +173,26 @@ $(document).ready(() => {
return anchor;
});
var et = JSON.parse(sessionStorage.getItem("activeGalaxyTour"));
if (et) {
et = TOURS.hooked_tour_from_data(et);
if (et && et.steps) {
if (window && window.self === window.top) {
// Only kick off a new tour if this is the toplevel window (non-iframe). This
// functionality actually *could* be useful, but we'd need to handle it better and
// come up with some design guidelines for tours jumping between windows.
// Disabling for now.
var tour = new Tour(
_.extend(
{
steps: et.steps
},
TOURS.tour_opts
)
);
tour.init();
tour.restart();
}
}
}
Tours.activeGalaxyTourRunner();
function onloadWebhooks() {
if (Galaxy.root !== undefined) {
// Load all webhooks with the type 'onload'
$.getJSON(`${Galaxy.root}api/webhooks/onload/all`, webhooks => {
_.each(webhooks, webhook => {
if (webhook.activate && webhook.script) {
$("<script/>", { type: "text/javascript" })
.text(webhook.script)
.appendTo("head");
$("<style/>", { type: "text/css" })
.text(webhook.styles)
.appendTo("head");
}
// Wait until Galaxy.config is loaded.
if (Galaxy.config) {
if (Galaxy.config.enable_webhooks) {
// Load all webhooks with the type 'onload'
$.getJSON(`${Galaxy.root}api/webhooks/onload/all`, webhooks => {
_.each(webhooks, webhook => {
if (webhook.activate && webhook.script) {
$("<script/>", { type: "text/javascript" })
.text(webhook.script)
.appendTo("head");
$("<style/>", { type: "text/css" })
.text(webhook.styles)
.appendTo("head");
}
});
});
});
}
} else {
setTimeout(onloadWebhooks, 100);
}
@@ -0,0 +1,138 @@
import * as mod_toastr from "libs/toastr";
import mod_group_model from "toolshed/groups/group-model";
// toolshed group detail view
const GroupDetailView = Backbone.View.extend({
el: "#groups_element",
options: {},
app: null,
initialize: function(options) {
this.options = _.extend(this.options, options);
this.app = window.globalTS.groups;
if (this.app.collection !== null) {
this.model = this.app.collection.get(this.options.group_id);
this.render();
} else {
this.fetchGroup();
}
},
fetchGroup: function(options) {
const that = this;
this.options = _.extend(this.options, options);
this.model = new mod_group_model.Group({ id: this.options.group_id });
this.model.fetch({
success: function(model) {
console.log("received data: ");
console.log(model);
that.render();
},
error: function(model, response) {
if (typeof response.responseJSON !== "undefined") {
mod_toastr.error(response.responseJSON.err_msg);
} else {
mod_toastr.error("An error occurred.");
}
}
});
},
render: function() {
const template = this.templateRow();
this.$el.html(template({ group: this.model }));
$("#center [data-toggle]").tooltip();
$("#center").css("overflow", "auto");
},
templateRow: function() {
return _.template(
[
"<div>",
'<h3><%= _.escape(group.get("name")) %></h3>',
'<p class="" style="color:gray;">',
'A group of <%= group.get("members").length %> members with <%= group.get("repositories").length %> repositories and a total of <%= group.get("total_downloads") %> combined repository clones.</p>',
"<h3>Members</h3>",
'<table class="grid table table-condensed">',
"<thead>",
"<th>Name</th>",
"<th>Repositories</th>",
"<th>Registered</th>",
"</thead>",
"<tbody>",
'<% _.each(group.get("members"), function(member) { %>',
"<tr>",
"<td>",
"<%= _.escape(member.username) %>",
"</td>",
"<td>",
'<a data-toggle="tooltip" data-placement="top" title="Repositories of <%= _.escape(member.username) %>" href="/repository/browse_repositories_by_user?user_id=<%= member.id %>&use_panels=true" id="<%= member.id %>"><%= member.user_repos_count %></a>',
"</td>",
"<td>",
"<%= member.time_created %>",
"</td>",
"</tr>",
"<% }); %>",
"</tbody>",
"</table>",
"<h3>Repositories</h3>",
'<table class="grid table table-condensed">',
"<thead>",
"<th>Name</th>",
"<th>Description</th>",
"<th>Clones</th>",
"<th>Owner</th>",
"<th>Categories</th>",
"<th>Created</th>",
"<th>Updated</th>",
"<th>Rating</th>",
"<th>Verified</th>",
"</thead>",
"<tbody>",
'<% _.each(group.get("repositories"), function(repo) { %>',
"<tr>",
"<td>",
'<a data-toggle="tooltip" data-placement="top" title="Details of <%= _.escape(repo.name) %>" href="/view/<%= _.escape(repo.owner) %>/<%= _escape(repo.name) %>" id="<%= repo.id %>"><%= _.escape(repo.name) %></a>',
"</td>",
"<td>",
"<%= _.escape(repo.description) %>",
"</td>",
"<td>",
"<%= repo.times_downloaded %>",
"</td>",
"<td>",
"<%= _.escape(repo.owner) %>",
"</td>",
"<td>",
"<% _.each((repo.categories), function(cat) { %>",
'<a data-toggle="tooltip" data-placement="top" title="Repositories in <%= cat.name %>" href="/repository/browse_repositories_in_category?id=<%= cat.id %>&use_panels=true"><%= cat.name %></a><br/>',
// '<%= repo.categories %>',
"<% }); %>",
"</td>",
'<td data-toggle="tooltip" data-placement="top" title="<%= repo.time_created_full %>">',
"<%= repo.time_created %>",
"</td>",
'<td data-toggle="tooltip" data-placement="top" title="<%= repo.time_updated_full %>">',
"<%= repo.time_updated %>",
"</td>",
"<td>",
"<%= repo.ratings_mean %>",
"</td>",
"<td>",
"<%= repo.approved %>",
"</td>",
"</tr>",
"<% }); %>",
"</tbody>",
"</table>",
"</div>"
].join("")
);
}
});
export default {
GroupDetailView: GroupDetailView
};
@@ -0,0 +1,128 @@
import * as mod_toastr from "libs/toastr";
import mod_group_model from "toolshed/groups/group-model";
import mod_group_row from "toolshed/groups/group-listrow-view";
const GroupListView = Backbone.View.extend({
el: "#groups_element",
defaults: {},
/**
* Initialize and fetch the groups from server.
* Async render afterwards.
* @param {object} options an object with options
*/
initialize: function(options) {
this.options = _.defaults(this.options || {}, this.defaults, options);
const that = this;
window.globalTS.groups.collection = new mod_group_model.Groups();
window.globalTS.groups.collection.fetch({
success: function(model) {
console.log("received data: ");
console.log(model);
that.render();
},
error: function(model, response) {
if (typeof response.responseJSON !== "undefined") {
mod_toastr.error(response.responseJSON.err_msg);
} else {
mod_toastr.error("An error occurred.");
}
}
});
},
fetch: function() {},
/**
* Render the groups table from the object's own collection.
*/
render: function(options) {
this.options = _.extend(this.options, options);
$(".tooltip").hide();
const template = this.templateGroupsList();
this.$el.html(template({ length: window.globalTS.groups.collection.models.length }));
this.renderRows(window.globalTS.groups.collection.models);
$("#center [data-toggle]").tooltip();
$("#center").css("overflow", "auto");
},
/**
* Render all given models as rows in the groups list
* @param {array} groups_to_render array of group models to render
*/
renderRows: function(groups_to_render) {
for (let i = 0; i < groups_to_render.length; i++) {
const group = groups_to_render[i];
this.renderOne({ group: group });
}
},
/**
* Create a view for the given model and add it to the groups view.
* @param {Group} model of the view that will be rendered
*/
renderOne: function(options) {
const rowView = new mod_group_row.GroupListRowView(options);
this.$el.find("#group_list_body").append(rowView.el);
},
/**
* Table heading was clicked, update sorting preferences and re-render.
* @return {[type]} [description]
*/
// sort_clicked : function(){
// if (Galaxy.libraries.preferences.get('sort_order') === 'asc'){
// Galaxy.libraries.preferences.set({'sort_order': 'desc'});
// } else {
// Galaxy.libraries.preferences.set({'sort_order': 'asc'});
// }
// this.render();
// },
/**
* Sort the underlying collection according to the parameters received.
* Currently supports only sorting by name.
*/
// sortLibraries: function(){
// if (Galaxy.libraries.preferences.get('sort_by') === 'name'){
// if (Galaxy.libraries.preferences.get('sort_order') === 'asc'){
// this.collection.sortByNameAsc();
// } else if (Galaxy.libraries.preferences.get('sort_order') === 'desc'){
// this.collection.sortByNameDesc();
// }
// }
// },
// MMMMMMMMMMMMMMMMMM
// === TEMPLATES ====
// MMMMMMMMMMMMMMMMMM
templateGroupsList: function() {
const tmpl_array = [];
tmpl_array.push('<div id="groups">');
tmpl_array.push("</div>");
tmpl_array.push('<div class="groups_container table-responsive">');
tmpl_array.push("<% if(length === 0) { %>");
tmpl_array.push("<div>There are no groups yet.</div>");
tmpl_array.push("<% } else{ %>");
tmpl_array.push('<table class="grid table table-condensed">');
tmpl_array.push(" <thead>");
tmpl_array.push(" <th>Name</th>");
// tmpl_array.push(' <th style="width:22%;">description</th>');
tmpl_array.push(" <th>Members</th> ");
tmpl_array.push(" <th>Repositories</th>");
tmpl_array.push(" </thead>");
tmpl_array.push(' <tbody id="group_list_body">');
// group item views will attach here
tmpl_array.push(" </tbody>");
tmpl_array.push("</table>");
tmpl_array.push("<% }%>");
tmpl_array.push("</div>");
return _.template(tmpl_array.join(""));
}
});
export default {
GroupListView: GroupListView
};
@@ -1,36 +1,32 @@
define([],
function() {
// toolshed group row view
var GroupListRowView = Backbone.View.extend({
const GroupListRowView = Backbone.View.extend({
events: {},
initialize : function( options ){
this.render( options.group );
initialize: function(options) {
this.render(options.group);
},
render: function( group ){
var tmpl = this.templateRow();
this.setElement(tmpl( { group:group } ));
render: function(group) {
const tmpl = this.templateRow();
this.setElement(tmpl({ group: group }));
this.$el.show();
return this;
},
templateRow: function() {
return _.template([
return _.template(
[
'<tr class="" data-id="<%- group.get("id") %>">',
'<td><a href="groups#/<%= group.get("id") %>"><%= _.escape(group.get("name")) %></a></td>',
// '<td>description</td>',
'<td><%= group.get("total_members") %></td>',
'<td><%= group.get("total_repos") %></td>',
'</tr>'
].join(''));
}
"</tr>"
].join("")
);
}
});
return {
export default {
GroupListRowView: GroupListRowView
};
});
@@ -0,0 +1,17 @@
// ============================================================================
// TS GROUP RELATED MODELS
const Group = Backbone.Model.extend({
urlRoot: `${Galaxy.root}api/groups`
});
const Groups = Backbone.Collection.extend({
url: `${Galaxy.root}api/groups`,
model: Group
});
export default {
Group: Group,
Groups: Groups
};
@@ -0,0 +1,42 @@
// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
// === MAIN TOOLSHED GROUP MODULE ====
// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
import mod_group_list from "toolshed/groups/group-list-view";
import mod_group_detail from "toolshed/groups/group-detail-view";
// ============================================================================
// ROUTER
const ToolshedRouter = Backbone.Router.extend({
routes: {
"": "groups",
":group_id": "group_page"
}
});
const ToolshedGroups = Backbone.View.extend({
groupListView: null,
groupDetailView: null,
collection: null,
initialize: function() {
window.globalTS.groups = this;
this.ts_router = new ToolshedRouter();
this.ts_router.on("route:groups", () => {
window.globalTS.groups.groupListView = new mod_group_list.GroupListView();
});
this.ts_router.on("route:group_page", group_id => {
window.globalTS.groups.groupDetailView = new mod_group_detail.GroupDetailView({
group_id: group_id
});
});
Backbone.history.start({ pushState: false });
}
});
export default {
ToolshedGroups: ToolshedGroups
};
+7 -9
View File
@@ -1,6 +1,7 @@
import _l from "utils/localization";
import * as _ from "libs/underscore";
import * as d3 from "libs/d3";
import * as d3 from "d3";
import { event as currentEvent } from "d3";
import visualization from "viz/visualization";
import mod_utils from "utils/utils";
import config from "utils/config";
@@ -193,7 +194,6 @@ var CircsterView = Backbone.View.extend({
tracks_start_radii = d3.range(radius_start, min_dimension / 2, dataset_arc_height + track_gap);
// Map from track start to bounds.
var self = this;
return _.map(tracks_start_radii, radius => [radius, radius + dataset_arc_height]);
},
@@ -221,8 +221,8 @@ var CircsterView = Backbone.View.extend({
.call(
d3.behavior.zoom().on("zoom", () => {
// Do zoom, drag.
var scale = d3.event.scale;
svg.attr("transform", `translate(${d3.event.translate}) scale(${scale})`);
var scale = currentEvent.scale;
svg.attr("transform", `translate(${currentEvent.translate}) scale(${scale})`);
// Propagate scale changes to views.
if (self.scale !== scale) {
@@ -628,7 +628,6 @@ var CircsterTrackView = Backbone.View.extend({
* default data bounds.
*/
_update_data_bounds: function(new_bounds) {
var old_bounds = this.data_bounds;
this.data_bounds =
new_bounds || this.get_data_bounds(this.track.get("data_manager").get_genome_wide_data(this.genome));
this._transition_chrom_data();
@@ -962,7 +961,6 @@ var CircsterQuantitativeTrackView = CircsterTrackView.extend({
_data_bounds_ticks_fn: function() {
// Closure vars.
var self = this;
visibleChroms = 0;
// Return function for locating ticks based on chrom arc data.
return (
@@ -1101,9 +1099,9 @@ var Circster = Backbone.View.extend({
// load css
mod_utils.cssLoadFile("static/style/circster.css");
// -- Configure visualization --
var genome = new visualization.Genome(galaxy_config.app.genome);
var genome = new visualization.Genome(window.galaxy_config.app.genome);
var vis = new visualization.GenomeVisualization(galaxy_config.app.viz_config);
var vis = new visualization.GenomeVisualization(window.galaxy_config.app.viz_config);
// Add Circster-specific config options.
vis.get("config").add([
@@ -1143,7 +1141,7 @@ var Circster = Backbone.View.extend({
// setup title
$("#center .unified-panel-header-inner").append(
`${galaxy_config.app.viz_config.title} ${galaxy_config.app.viz_config.dbkey}`
`${window.galaxy_config.app.viz_config.title} ${window.galaxy_config.app.viz_config.dbkey}`
);
// setup menu
+20 -18
View File
@@ -2,13 +2,7 @@ import _l from "utils/localization";
/**
* Top-level trackster code, used for creating/loading visualizations and user interface elements.
*/
// global variables
var ui = null;
var view = null;
var browser_router = null;
// trackster viewer
//import * as $ from 'jquery';
import * as _ from "libs/underscore";
import tracks from "viz/trackster/tracks";
import visualization from "viz/visualization";
@@ -25,6 +19,12 @@ import "libs/farbtastic";
import "libs/jquery/jquery.form";
import "libs/jquery/jquery.rating";
import "ui/editable-text";
// trackster global variables
var ui = null;
var view = null;
var browser_router = null;
/**
* Base Object/Model for inhertiance.
*/
@@ -152,7 +152,7 @@ var TracksterUI = Base.extend({
title: _l("Bookmarks"),
on_click: function() {
// HACK -- use style to determine if panel is hidden and hide/show accordingly.
force_right_panel($("div#right").css("right") == "0px" ? "hide" : "show");
window.force_right_panel($("div#right").css("right") == "0px" ? "hide" : "show");
}
},
{
@@ -250,7 +250,7 @@ var TracksterUI = Base.extend({
// Create view.
var self = this;
var view = new tracks.TracksterView(_.extend(view_config, { header: false }));
view = new tracks.TracksterView(_.extend(view_config, { header: false }));
view.editor = true;
$.when(view.load_chroms_deferred).then(chrom_info => {
@@ -408,10 +408,10 @@ var TracksterView = Backbone.View.extend({
});
// hide right panel
force_right_panel("hide");
window.force_right_panel("hide");
// check if id is available
if (galaxy_config.app.id) {
if (window.galaxy_config.app.id) {
this.view_existing();
} else if (query_string.get("dataset_id")) {
this.choose_existing_or_new();
@@ -459,7 +459,6 @@ var TracksterView = Backbone.View.extend({
view_in_saved: function(dataset_params) {
var tracks_grid = new GridView({
url_base: `${Galaxy.root}visualization/list_tracks`,
dict_format: true,
embedded: true
});
Galaxy.modal.show({
@@ -484,7 +483,7 @@ var TracksterView = Backbone.View.extend({
// view
view_existing: function() {
// get config
var viz_config = galaxy_config.app.viz_config;
var viz_config = window.galaxy_config.app.viz_config;
// view
view = ui.create_visualization(
@@ -534,8 +533,11 @@ var TracksterView = Backbone.View.extend({
// select default
var dbkeys_in_genomes = response.map(r => r[1]);
if (galaxy_config.app.default_dbkey && _.contains(dbkeys_in_genomes, galaxy_config.app.default_dbkey)) {
$("#new-dbkey").val(galaxy_config.app.default_dbkey);
if (
window.galaxy_config.app.default_dbkey &&
_.contains(dbkeys_in_genomes, window.galaxy_config.app.default_dbkey)
) {
$("#new-dbkey").val(window.galaxy_config.app.default_dbkey);
}
// change focus
@@ -589,7 +591,7 @@ var TracksterView = Backbone.View.extend({
name: name,
dbkey: dbkey
},
galaxy_config.app.gene_region
window.galaxy_config.app.gene_region
);
// initialize editor
@@ -605,9 +607,9 @@ var TracksterView = Backbone.View.extend({
$("#center .unified-panel-title").text(`${view.config.get_value("name")} (${view.dbkey})`);
// add dataset
if (galaxy_config.app.add_dataset)
if (window.galaxy_config.app.add_dataset)
$.ajax({
url: `${Galaxy.root}api/datasets/${galaxy_config.app.add_dataset}`,
url: `${Galaxy.root}api/datasets/${window.galaxy_config.app.add_dataset}`,
data: { hda_ldda: "hda", data_type: "track_config" },
dataType: "json",
success: function(track_data) {
@@ -46,14 +46,12 @@ var select_datasets = (filters, success_fn) => {
var history_grid = new GridView({
url_base: `${Galaxy.root}visualization/list_history_datasets`,
filters: filters,
dict_format: true,
embedded: true
});
// library dataset selection tab
var library_grid = new GridView({
url_base: `${Galaxy.root}visualization/list_library_datasets`,
dict_format: true,
embedded: true
});
+4 -2
View File
@@ -221,7 +221,9 @@
}
}
}
span.nametags {
div.nametags {
max-height:26px;
overflow-y: auto;
span.label {
display: inline-block;
margin-right: 2px;
@@ -698,4 +700,4 @@
margin-right: 8px;
}
}
}
}
+19 -58
View File
@@ -104,7 +104,7 @@ th.button_heading{
margin-right: 0.2em;
}
.toolbar-item{
margin-left: 1em;
margin-left: 0.5em;
}
}
#libraries_element, #folder_items_element{
@@ -112,7 +112,7 @@ th.button_heading{
margin-left: 0.5em;
}
}
.help-button{
.right-float{
float: right;
}
.pagination-sm{
@@ -128,11 +128,6 @@ th.button_heading{
.libimport-select-all{
margin-left: 0.5em;
}
.library-modal-item{
width:90%;
margin-left: 1em;
margin-right: 1em;
}
.paginator-bottom{
width: 27em;
margin-left: auto;
@@ -143,60 +138,26 @@ th.button_heading{
}
// Extra style for Galaxy modal
.modal-content #selected_history_content{
ul{
list-style-type: none;
.modal-content{
.library_selected_history_content{
ul{
list-style-type: none;
}
}
.library-collection-type-select, .library-collection-history-select{
margin-left:auto;
margin-right:auto;
width:200px;
min-width:200px;
}
.library-modal-item{
width:90%;
margin-left: auto;
margin-right: auto;
margin-top: 1.5em;
}
}
// Follows the style for the deprecated admin libraries interface
#library-grid{
ul{
list-style: none;
}
span.expandLink{
padding-left: 12px;
display: inline-block;
vertical-align: middle;
background: url(../images/silk/resultset_next.png) no-repeat;
}
.folderRow.expanded span.expandLink{
background: url(../images/silk/resultset_bottom.png) no-repeat;
}
.folderRow span.rowIcon{
float: left;
margin-right: 5px;
width: 16px;
height: 16px;
display: inline-block;
vertical-align: middle;
background: url(../images/silk/folder.png);
}
.libraryItem-error{
margin-right: 2px;
padding: 0 2px 0 2px;
border: 1px solid @state-danger-border;
background: @state-danger-bg;
}
.libraryItem-queued{
margin-right: 2px;
padding: 0 2px 0 2px;
border: 1px solid @state-default-border;
background: @state-default-bg;
}
.libraryItem-running{
margin-right: 2px;
padding: 0 2px 0 2px;
border: 1px solid @state-running-border;
background: @state-running-bg;
}
.libraryItem-upload{
margin-right: 2px;
padding: 0 2px 0 2px;
border: 1px solid @state-upload-border;
background: @state-upload-bg;
}
}
.libraryTitle{
th{
text-align: left;
-80
View File
@@ -1,80 +0,0 @@
/**
* Grunt task to fetch third-party libraries using bower and copy them to the proper location in client/.
* @param {Object} grunt main grunt file
* @return {Function} callback to build this task
*/
module.exports = function( grunt ){
"use strict";
var dev_path = './galaxy/scripts',
// where to move fetched bower components into the build structure (libName: [ bower-location, libs-location ])
libraryLocations = {
'farbtastic': [ 'src/farbtastic.js', 'farbtastic.js' ],
'bootstrap-tour': [ 'build/js/bootstrap-tour.js', 'bootstrap-tour.js' ],
// these need to be updated and tested
//'jquery-form': [ 'jquery.form.js', 'jquery/jquery.form.js' ],
//'jquery-autocomplete': [ 'src/jquery.autocomplete.js', 'jquery/jquery.autocomplete.js' ],
//'select2': [ 'select2.js', 'jquery/select2.js' ],
//'jStorage': [ 'jstorage.js', 'jquery/jstorage.js' ],
//'dynatree': [ 'dist/jquery.dynatree.js', 'jquery/jquery.dynatree.js' ],
//'jquery.event.drag-drop': [
// [ 'event.drag/jquery.event.drag.js', 'jquery/jquery.event.drag.js' ],
// [ 'event.drag/jquery.event.drop.js', 'jquery/jquery.event.drop.js' ]
//],
// these are complicated by additional css/less
//'toastr': [ 'toastr.js', 'toastr.js' ],
//'wymeditor': [ 'dist/wymeditor/jquery.wymeditor.js', 'jquery/jquery.wymeditor.js' ],
//'jstree': [ 'jstree.js', 'jquery/jstree.js' ],
// these have been customized by Galaxy
//'bootstrap': [ 'dist/js/bootstrap.js', 'bootstrap.js' ],
//'jquery-ui': [
// // multiple components now
// [ '', 'jquery/jquery-ui.js' ]
//],
};
// call bower to install libraries and other external resources
grunt.config( 'bower-install-simple', {
options: {
color: true
},
"prod": {
options: {
production: true
}
},
"dev": {
options: {
production: false
}
}
});
/** copy external libraries from bower components to scripts/libs */
function copyLibs(){
var lib_path = dev_path + '/libs/';
for( var libName in libraryLocations ){
if( libraryLocations.hasOwnProperty( libName ) ){
var bower_dir = 'bower_components',
location = libraryLocations[ libName ],
source = [ bower_dir, libName, location[0] ].join( '/' ),
destination = lib_path + location[1];
grunt.log.writeln( source + ' -> ' + destination );
grunt.file.copy( source, destination );
}
}
}
grunt.loadNpmTasks( 'grunt-bower-install-simple' );
grunt.registerTask( 'copy-libs', 'copy external libraries to src', copyLibs );
grunt.registerTask( 'install-libs', 'fetch external libraries and copy to src',
[ 'bower-install-simple:prod', 'copy-libs' ] );
};
-123
View File
@@ -1,123 +0,0 @@
/**
* Grunt task to compress/bundle static/scripts.
* @param {Object} grunt main grunt file
* @return {Function} callback to build this task
*/
module.exports = function( grunt ){
"use strict";
var app = grunt.config.get( 'app' ),
paths = grunt.config.get( 'paths' ),
// uglify settings used when scripts are decompressed/not-minified
decompressedSettings = {
mangle : false,
beautify : true,
compress : {
drop_debugger : false
}
},
// uglify settings used when scripts are compressed/minified
compressedSettings = {
mangle : {
screw_ie8 : true
},
compress : {
// high compression options
screw_ie8 : true,
sequences: true,
dead_code : true,
drop_debugger : true,
comparisons : true,
conditionals : true,
evaluate : true,
booleans : true,
loops : true,
unused : true,
hoist_funs : true,
if_return : true,
join_vars : true,
cascade : true,
}
};
grunt.config( 'uglify', {
target : {
files: [{
expand : true,
cwd : paths.srcSymlink,
// NOTE: do not use uglify in the apps dir (webpack will do that section)
src : [ '**/*.js', '!apps/**/*.js' ],
// src : '**/*.js',
dest : paths.dist
}],
}
});
if (grunt.option( 'develop' )){
grunt.config( 'uglify.options', decompressedSettings );
} else {
grunt.config( 'uglify.target.options', {
sourceMap : true,
sourceMapName : function( path ){
// rewrite name to have all source maps in 'static/maps'
return path.replace( paths.dist, paths.maps ) + '.map';
}
});
grunt.config( 'uglify.options', compressedSettings );
}
// -------------------------------------------------------------------------- decompress for easier debugging
grunt.registerTask( 'decompress', function(){
grunt.log.writeln( "decompressing... (don't forget to call 'grunt' again before committing)" );
grunt.config( 'uglify.options', decompressedSettings );
grunt.config( 'uglify.target.options', {});
grunt.task.run( 'uglify' );
});
// alias for symmetry
grunt.registerTask( 'compress', [ 'uglify' ] );
// -------------------------------------------------------------------------- watch & copy,pack only those changed
// use 'grunt watch' (from a new tab in your terminal) to have grunt re-copy changed files automatically
grunt.config( 'watch', {
watch: {
// watch for changes in the src dir
// NOTE: but not in the apps dir (which is only used by webpack)
files: [ paths.srcSymlink + '/**', '!' + paths.srcSymlink + '/apps/**' ],
tasks: [ 'uglify' ],
options: {
spawn: false
}
}
});
// outer scope variable for the event handler and onChange fn - begin with empty hash
var changedFiles = Object.create(null);
// when files are changed, set the copy src and packScripts target to the filenames of the updated files
var onChange = grunt.util._.debounce(function() {
grunt.log.writeln( 'onChange, changedFiles:', Object.keys( changedFiles ) );
grunt.config( 'uglify.target.files', [{
expand : true,
cwd : paths.srcSymlink,
src : Object.keys( changedFiles ),
dest : paths.dist
}]);
changedFiles = Object.create(null);
}, 200);
var addChangedFile = function( action, filepath ) {
// store each filepath in a Files obj, the debounced fn above will use it as an aggregate list for copying
// we need to take galaxy/scripts out of the filepath or it will be copied to the wrong loc
filepath = filepath.replace( paths.srcSymlink + '/', '' );
// grunt.log.writeln( 'on.watch, filepath:', filepath );
changedFiles[ filepath ] = action;
onChange();
};
grunt.event.on( 'watch', addChangedFile );
// --------------------------------------------------------------------------
grunt.loadNpmTasks( 'grunt-contrib-watch' );
grunt.loadNpmTasks( 'grunt-contrib-uglify' );
};
+1 -2
View File
@@ -34,8 +34,7 @@ var paths = {
'jquery-mousewheel': [ 'jquery.mousewheel.js', 'jquery/jquery.mousewheel.js' ],
'raven-js': ['dist/raven.js', 'raven.js'],
'requirejs': [ 'require.js', 'require.js' ],
'underscore': [ 'underscore.js', 'underscore.js' ],
'vue': [ 'dist/vue.js', 'vue.js' ]
'underscore': [ 'underscore.js', 'underscore.js' ]
},
libs: ['galaxy/scripts/libs/**/*.js']
};
+7 -7
View File
@@ -28,7 +28,7 @@
"raven-js": "^3.17.0",
"requirejs": "2",
"underscore": "^1.8.3",
"vue": "^2.5.3"
"vue": "^2.5.9"
},
"scripts": {
"watch": "gulp stage-libs && concurrently \"yarn run webpack-watch\" \"yarn run gulp watch\" \"yarn run style-watch\"",
@@ -55,11 +55,11 @@
"devDependencies": {
"babel-core": "^6.24.0",
"babel-loader": "^7.1.1",
"babel-preset-env": "^1.3.2",
"concurrently": "^3.4.0",
"babel-preset-env": "^1.6.1",
"concurrently": "^3.5.1",
"css-loader": "^0.28.7",
"del": "^3.0.0",
"expose-loader": "^0.7.3",
"expose-loader": "^0.7.4",
"grunt": "^0.4.5",
"grunt-bower-install-simple": "^1.1.0",
"grunt-cli": "^0.1.13",
@@ -87,11 +87,11 @@
"karma-webpack": "^2.0.6",
"phantomjs-polyfill": "0.0.2",
"phantomjs-prebuilt": "^2.1.7",
"prettier": "^1.7.4",
"prettier": "^1.9.1",
"qunitjs": "^2.4.1",
"sinon": "^4.1.2",
"vue-loader": "^13.5.0",
"vue-template-compiler": "^2.5.3",
"webpack": "^3.6.0"
"vue-template-compiler": "^2.5.9",
"webpack": "^3.10.0"
}
}
@@ -1,146 +0,0 @@
define([
"libs/toastr",
"toolshed/scripts/mvc/groups/group-model"
],
function(
mod_toastr,
mod_group_model
) {
// toolshed group detail view
var GroupDetailView = Backbone.View.extend({
el: "#groups_element",
options: {},
app: null,
initialize : function( options ){
this.options = _.extend( this.options, options );
this.app = window.globalTS.groups;
if ( this.app.collection !== null ){
this.model = this.app.collection.get( this.options.group_id );
this.render();
} else {
this.fetchGroup();
}
},
fetchGroup: function( options ){
var that = this;
this.options = _.extend( this.options, options );
this.model = new mod_group_model.Group( { id: this.options.group_id } );
this.model.fetch({
success: function( model ){
console.log( 'received data: ' );
console.log( model );
that.render();
},
error: function( model, response ){
if ( typeof response.responseJSON !== "undefined" ){
mod_toastr.error( response.responseJSON.err_msg );
} else {
mod_toastr.error( 'An error occurred.' );
}
}
});
},
render: function(){
var template = this.templateRow();
this.$el.html( template( { group: this.model } ) );
$( "#center [data-toggle]" ).tooltip();
$( "#center" ).css( 'overflow','auto' );
},
templateRow: function() {
return _.template([
'<div>',
'<h3><%= _.escape(group.get("name")) %></h3>',
'<p class="" style="color:gray;">',
'A group of <%= group.get("members").length %> members with <%= group.get("repositories").length %> repositories and a total of <%= group.get("total_downloads") %> combined repository clones.</p>',
'<h3>Members</h3>',
'<table class="grid table table-condensed">',
'<thead>',
'<th>Name</th>',
'<th>Repositories</th>',
'<th>Registered</th>',
'</thead>',
'<tbody>',
'<% _.each(group.get("members"), function(member) { %>',
'<tr>',
'<td>',
'<%= _.escape(member.username) %>',
'</td>',
'<td>',
'<a data-toggle="tooltip" data-placement="top" title="Repositories of <%= _.escape(member.username) %>" href="/repository/browse_repositories_by_user?user_id=<%= member.id %>&use_panels=true" id="<%= member.id %>"><%= member.user_repos_count %></a>',
'</td>',
'<td>',
'<%= member.time_created %>',
'</td>',
'</tr>',
'<% }); %>',
'</tbody>',
'</table>',
'<h3>Repositories</h3>',
'<table class="grid table table-condensed">',
'<thead>',
'<th>Name</th>',
'<th>Description</th>',
'<th>Clones</th>',
'<th>Owner</th>',
'<th>Categories</th>',
'<th>Created</th>',
'<th>Updated</th>',
'<th>Rating</th>',
'<th>Verified</th>',
'</thead>',
'<tbody>',
'<% _.each(group.get("repositories"), function(repo) { %>',
'<tr>',
'<td>',
'<a data-toggle="tooltip" data-placement="top" title="Details of <%= _.escape(repo.name) %>" href="/view/<%= _.escape(repo.owner) %>/<%= _escape(repo.name) %>" id="<%= repo.id %>"><%= _.escape(repo.name) %></a>',
'</td>',
'<td>',
'<%= _.escape(repo.description) %>',
'</td>',
'<td>',
'<%= repo.times_downloaded %>',
'</td>',
'<td>',
'<%= _.escape(repo.owner) %>',
'</td>',
'<td>',
'<% _.each((repo.categories), function(cat) { %>',
'<a data-toggle="tooltip" data-placement="top" title="Repositories in <%= cat.name %>" href="/repository/browse_repositories_in_category?id=<%= cat.id %>&use_panels=true"><%= cat.name %></a><br/>',
// '<%= repo.categories %>',
'<% }); %>',
'</td>',
'<td data-toggle="tooltip" data-placement="top" title="<%= repo.time_created_full %>">',
'<%= repo.time_created %>',
'</td>',
'<td data-toggle="tooltip" data-placement="top" title="<%= repo.time_updated_full %>">',
'<%= repo.time_updated %>',
'</td>',
'<td>',
'<%= repo.ratings_mean %>',
'</td>',
'<td>',
'<%= repo.approved %>',
'</td>',
'</tr>',
'<% }); %>',
'</tbody>',
'</table>',
'</div>'
].join(''));
}
});
return {
GroupDetailView: GroupDetailView
};
});
@@ -1,141 +0,0 @@
define([
"libs/toastr",
"toolshed/scripts/mvc/groups/group-model",
"toolshed/scripts/mvc/groups/group-listrow-view"
], function(
mod_toastr,
mod_group_model,
mod_group_row
){
var GroupListView = Backbone.View.extend({
el: '#groups_element',
defaults: {},
/**
* Initialize and fetch the groups from server.
* Async render afterwards.
* @param {object} options an object with options
*/
initialize : function( options ){
this.options = _.defaults( this.options || {}, this.defaults, options );
var that = this;
window.globalTS.groups.collection = new mod_group_model.Groups();
window.globalTS.groups.collection.fetch({
success: function( model ){
console.log( 'received data: ' );
console.log( model );
that.render();
},
error: function( model, response ){
if ( typeof response.responseJSON !== "undefined" ){
mod_toastr.error( response.responseJSON.err_msg );
} else {
mod_toastr.error( 'An error occurred.' );
}
}
});
},
fetch: function(){
},
/**
* Render the groups table from the object's own collection.
*/
render: function ( options ) {
this.options = _.extend( this.options, options );
$( ".tooltip" ).hide();
var template = this.templateGroupsList();
this.$el.html( template( { length: window.globalTS.groups.collection.models.length } ) );
this.renderRows( window.globalTS.groups.collection.models );
$( "#center [data-toggle]" ).tooltip();
$( "#center" ).css( 'overflow','auto' );
},
/**
* Render all given models as rows in the groups list
* @param {array} groups_to_render array of group models to render
*/
renderRows: function( groups_to_render ){
for ( var i = 0; i < groups_to_render.length; i++ ) {
var group = groups_to_render[i];
this.renderOne( { group: group } )
}
},
/**
* Create a view for the given model and add it to the groups view.
* @param {Group} model of the view that will be rendered
*/
renderOne: function( options ){
var rowView = new mod_group_row.GroupListRowView( options );
this.$el.find( '#group_list_body' ).append( rowView.el );
},
/**
* Table heading was clicked, update sorting preferences and re-render.
* @return {[type]} [description]
*/
// sort_clicked : function(){
// if (Galaxy.libraries.preferences.get('sort_order') === 'asc'){
// Galaxy.libraries.preferences.set({'sort_order': 'desc'});
// } else {
// Galaxy.libraries.preferences.set({'sort_order': 'asc'});
// }
// this.render();
// },
/**
* Sort the underlying collection according to the parameters received.
* Currently supports only sorting by name.
*/
// sortLibraries: function(){
// if (Galaxy.libraries.preferences.get('sort_by') === 'name'){
// if (Galaxy.libraries.preferences.get('sort_order') === 'asc'){
// this.collection.sortByNameAsc();
// } else if (Galaxy.libraries.preferences.get('sort_order') === 'desc'){
// this.collection.sortByNameDesc();
// }
// }
// },
// MMMMMMMMMMMMMMMMMM
// === TEMPLATES ====
// MMMMMMMMMMMMMMMMMM
templateGroupsList: function(){
var tmpl_array = [];
tmpl_array.push('<div id="groups">');
tmpl_array.push('</div>');
tmpl_array.push('<div class="groups_container table-responsive">');
tmpl_array.push('<% if(length === 0) { %>');
tmpl_array.push('<div>There are no groups yet.</div>');
tmpl_array.push('<% } else{ %>');
tmpl_array.push('<table class="grid table table-condensed">');
tmpl_array.push(' <thead>');
tmpl_array.push(' <th>Name</th>');
// tmpl_array.push(' <th style="width:22%;">description</th>');
tmpl_array.push(' <th>Members</th> ');
tmpl_array.push(' <th>Repositories</th>');
tmpl_array.push(' </thead>');
tmpl_array.push(' <tbody id="group_list_body">');
// group item views will attach here
tmpl_array.push(' </tbody>');
tmpl_array.push('</table>');
tmpl_array.push('<% }%>');
tmpl_array.push('</div>');
return _.template(tmpl_array.join(''));
},
});
return {
GroupListView: GroupListView
};
});
@@ -1,22 +0,0 @@
define([], function() {
// ============================================================================
// TS GROUP RELATED MODELS
var Group = Backbone.Model.extend({
urlRoot: Galaxy.root + 'api/groups',
});
var Groups = Backbone.Collection.extend({
url: Galaxy.root + 'api/groups',
model: Group,
});
return {
Group: Group,
Groups: Groups
};
});
@@ -1,51 +0,0 @@
// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
// === MAIN TOOLSHED GROUP MODULE ====
// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
define([
'toolshed/scripts/mvc/groups/group-list-view',
'toolshed/scripts/mvc/groups/group-detail-view',
'toolshed/scripts/mvc/groups/group-model'
],
function(
mod_group_list,
mod_group_detail,
mod_group_model
) {
// ============================================================================
// ROUTER
var ToolshedRouter = Backbone.Router.extend({
routes: {
"" : "groups",
":group_id" : "group_page"
}
});
var ToolshedGroups = Backbone.View.extend({
groupListView: null,
groupDetailView: null,
collection: null,
initialize : function(){
window.globalTS.groups = this;
this.ts_router = new ToolshedRouter();
this.ts_router.on( 'route:groups', function() {
window.globalTS.groups.groupListView = new mod_group_list.GroupListView();
});
this.ts_router.on( 'route:group_page', function( group_id ) {
window.globalTS.groups.groupDetailView = new mod_group_detail.GroupDetailView( { group_id: group_id } );
});
Backbone.history.start({pushState: false});
}
});
return {
ToolshedGroups: ToolshedGroups
};
});
+1 -1
View File
@@ -25,7 +25,7 @@ var commonLibs = [
"libs/farbtastic",
"libs/bootstrap",
"libs/bootstrap-tour",
"libs/vue",
"vue",
// mvc
"libs/underscore",
"libs/backbone",
+23 -19
View File
@@ -887,9 +887,9 @@ babel-plugin-transform-strict-mode@^6.24.1:
babel-runtime "^6.22.0"
babel-types "^6.24.1"
babel-preset-env@^1.3.2:
version "1.6.0"
resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.0.tgz#2de1c782a780a0a5d605d199c957596da43c44e4"
babel-preset-env@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.1.tgz#a18b564cc9b9afdf4aae57ae3c1b0d99188e6f48"
dependencies:
babel-plugin-check-es2015-constants "^6.22.0"
babel-plugin-syntax-trailing-function-commas "^6.22.0"
@@ -1659,9 +1659,9 @@ concat-stream@~1.4.6:
readable-stream "~1.1.9"
typedarray "~0.0.5"
concurrently@^3.4.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-3.5.0.tgz#8cf1b7707a6916a78a4ff5b77bb04dec54b379b2"
concurrently@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-3.5.1.tgz#ee8b60018bbe86b02df13e5249453c6ececd2521"
dependencies:
chalk "0.5.1"
commander "2.6.0"
@@ -2616,9 +2616,9 @@ expand-tilde@^2.0.2:
dependencies:
homedir-polyfill "^1.0.1"
expose-loader@^0.7.3:
version "0.7.3"
resolved "https://registry.yarnpkg.com/expose-loader/-/expose-loader-0.7.3.tgz#35fbd3659789e4faa81f59de8b7e9fc39e466d51"
expose-loader@^0.7.4:
version "0.7.4"
resolved "https://registry.yarnpkg.com/expose-loader/-/expose-loader-0.7.4.tgz#9bcdd3878b5da9107930b55a03f65afe90b3314a"
extend@^3.0.0, extend@~3.0.0, extend@~3.0.1:
version "3.0.1"
@@ -5894,10 +5894,14 @@ preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@^1.7.0, prettier@^1.7.4:
prettier@^1.7.0:
version "1.8.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.8.2.tgz#bff83e7fd573933c607875e5ba3abbdffb96aeb8"
prettier@^1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.9.1.tgz#41638a0d47c1efbd1b7d5a742aaa5548eab86d70"
pretty-bytes@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84"
@@ -7495,9 +7499,9 @@ vue-style-loader@^3.0.0:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
vue-template-compiler@^2.5.3:
version "2.5.3"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.3.tgz#ab631b0694e211a6aaf0d800102b37836aae36a4"
vue-template-compiler@^2.5.9:
version "2.5.9"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.9.tgz#7fabc73c8d3d12d32340cd86c5fc33e00e86d686"
dependencies:
de-indent "^1.0.2"
he "^1.1.0"
@@ -7506,9 +7510,9 @@ vue-template-es2015-compiler@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz#dc42697133302ce3017524356a6c61b7b69b4a18"
vue@^2.5.3:
version "2.5.3"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.3.tgz#e1a3b1f49b6e93e574ce040b95cbc873912fecc1"
vue@^2.5.9:
version "2.5.9"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.9.tgz#b2380cd040915dca69881dafd121d760952e65f7"
walk-sync@0.3.1:
version "0.3.1"
@@ -7577,9 +7581,9 @@ webpack@^1.14.0:
watchpack "^0.2.1"
webpack-core "~0.6.9"
webpack@^3.6.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.7.0.tgz#9081d0b1ba4950e34ddc54195721eba4bf205e83"
webpack@^3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.10.0.tgz#5291b875078cf2abf42bdd23afe3f8f96c17d725"
dependencies:
acorn "^5.0.0"
acorn-dynamic-import "^2.0.0"
+5
View File
@@ -920,6 +920,11 @@ use_interactive = True
# create symlinks to them).
#user_library_import_symlink_whitelist = None
# In conjunction or alternatively, Galaxy can restrict user library imports to
# those files that the user can read (by checking basic unix permissions).
# For this to work, the username has to match the username on the filesystem.
#user_library_import_check_permissions = False
# Allow admins to paste filesystem paths during upload. For libraries this
# adds an option to the admin library upload tool allowing admins to paste
# filesystem paths to files and directories in a box, and these paths will be
+25
View File
@@ -248,6 +248,31 @@
a requirement of the other.
-->
<!-- <param id="requests_cpu">500m</param> -->
<!-- <param id="requests_memory">500Mi</param> -->
<!-- <param id="limits_cpu">2</param> -->
<!-- <param id="limits_memory">2Gi</param> -->
<!-- Kubernetes resource Requests and Limits
Parameters above (requests_* and limits_*) set default minimal (requests) and
maximal (limits) CPU and memory resources to be allocated to all containers in
Kubernetes jobs by default.
Limits and requests for CPU resources are measured in cpu units. Fractional requests are allowed. A
Container with requests_cpu of 0.5 is guaranteed half as much CPU as one that asks for 1 CPU. The
expression 0.1 is equivalent to the expression 100m, which can be read as “one hundred millicpu”. Some
people say “one hundred millicores”, and this is understood to mean the same thing. A request with a
decimal point, like 0.1, is converted to 100m by the API, and precision finer than 1m is not allowed.
For this reason, the form 100m might be preferred.
You can express memory as a plain integer or as a fixed-point integer using one of these suffixes: E,
P, T, G, M, K. You can also use the power-of-two equivalents: Ei, Pi, Ti, Gi, Mi, Ki.
For instance, "1Gi" stands for 1 Gigabyte, and "500Mi" stands for 500 megabytes. Using other formats
will make the jobs fail as Kubernetes won't recognize the assignment.
For more details on the Kubernetes resources management, see:
https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/
-->
</plugin>
<plugin id="godocker" type="runner" load="galaxy.jobs.runners.godocker:GodockerJobRunner">
<!-- Go-Docker is a batch computing/cluster management tool using Docker
+10
View File
@@ -52,6 +52,16 @@
</object_store>
-->
<!-- Sample Cloud Object Store
<object_store type="cloud">
<auth access_key="..." secret_key="..." />
<bucket name="..." use_reduced_redundancy="False" />
<cache path="database/object_store_cache" size="1000" />
<extra_dir type="job_work" path="database/job_working_directory_s3"/>
<extra_dir type="temp" path="database/tmp_s3"/>
</object_store>
-->
</backends>
</object_store>
-1
View File
@@ -1,7 +1,6 @@
<?xml version="1.0"?>
<openid>
<provider file="genomespace.xml" />
<provider file="yahoo.xml" />
<provider file="aol.xml" />
<provider file="launchpad.xml" />
</openid>
@@ -6,6 +6,6 @@
# appropriate files.
---
-
image: bgruening/docker-hicbrowser
image: quay.io/bgruening/hicbrowser
description: |
A simple web browser to visualize Hi-C and other genomic tracks
@@ -38,6 +38,8 @@
error : function(){},
assert : function(){}
};
window.jQuery = window.jquery = window.$;
define( 'jquery', [], function(){ return window.$; })
require.config({
baseUrl: Galaxy.root + "static/scripts/",
paths: {
@@ -64,4 +66,4 @@
});
</script>
</body>
</html>
</html>
@@ -174,7 +174,7 @@ class TourGenerator(object):
elif input.type == 'select':
params = []
if name in test_inputs:
for option in self._tool.inputs[name].static_options:
for option in input.static_options:
for test_option in self._test.inputs[name]:
if test_option == option[1]:
params.append(option[0])
@@ -58,17 +58,7 @@ $(document).ready(function() {
},
_generateTour: function(data) {
var tourData = Tours.default.hooked_tour_from_data(data);
sessionStorage.setItem('activeGalaxyTour', JSON.stringify(data));
// Generate and run the tour
var tour = new Tour(_.extend({
steps: tourData.steps
}));
tour.init();
tour.goTo(0);
tour.restart();
var tour = Tours.giveTourWithData(data);
// Force ending the tour when pressing the Execute button
$('#execute').on('mousedown', function() {
if (tour) {
+1 -1
View File
@@ -59,7 +59,7 @@ galaxy_status() {
if [[ $(grep '\[server:' $RUN_IN/config/galaxy.ini|awk -F'(:)|(])' '{ print $2 }') == 'main' ]]
then
echo -n "$SERVICE_NAME status: "
status -p $RUN_IN/paster.pid galaxy
status -p $RUN_IN/galaxy.pid galaxy
else
for proc in $(grep '\[server:' $RUN_IN/config/galaxy.ini|awk -F'(:)|(])' '{ print $2 }')
do
+9 -4
View File
@@ -13,10 +13,15 @@ import os
import socket
import sys
import time
import urllib2
import warnings
from user import home
from six.moves.urllib.request import (
build_opener,
HTTPCookieProcessor,
Request
)
with warnings.catch_warnings():
warnings.simplefilter('ignore')
import twill.commands as tc
@@ -85,7 +90,7 @@ class Browser:
dprint("no cookie jar at above path, creating")
tc.save_cookies(self.cookie_jar)
tc.load_cookies(self.cookie_jar)
self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(tc.get_browser().cj))
self.opener = build_opener(HTTPCookieProcessor(tc.get_browser().cj))
def get(self, path):
tc.go("%s%s" % (self.server, path))
@@ -94,9 +99,9 @@ class Browser:
def req(self, path, data=None, method=None):
url = self.server + path
if data:
req = urllib2.Request(url, headers={'Content-Type': 'application/json'}, data=json.dumps(data))
req = Request(url, headers={'Content-Type': 'application/json'}, data=json.dumps(data))
else:
req = urllib2.Request(url, headers={'Content-Type': 'application/json'})
req = Request(url, headers={'Content-Type': 'application/json'})
if method:
req.get_method = lambda: method
res = self.opener.open(req)
+1 -1
View File
@@ -21,7 +21,7 @@ Many of the following instructions are best practices for any production applica
* Create a **NON-ROOT** user called galaxy. Running as an existing user will cause problems down the line when you want to grant or restrict access to data.
* Start with a fresh checkout of Galaxy, don't try to convert one previously used for development. Download and install it in the galaxy user home directory.
* Galaxy should be a managed system service (like Apache, mail servers, database servers, *etc.*) run by the galaxy user. Init scripts, OS X launchd definitions and Solaris SMF manifests are provided in the `contrib/` directory of the distribution. You can also use the `--daemon` and `--stop-daemon` arguments to `run.sh` to start and stop by hand, but still run detached. When running as a daemon the server's output log will be written to `paster.log` instead of the terminal, unless instructed otherwise with the `--log-file` argument.
* Galaxy should be a managed system service (like Apache, mail servers, database servers, *etc.*) run by the galaxy user. Init scripts, OS X launchd definitions and Solaris SMF manifests are provided in the `contrib/` directory of the distribution. You can also use the `--daemon` and `--stop-daemon` arguments to `run.sh` to start and stop by hand, but still run detached. When running as a daemon the server's output log will be written to `galaxy.log` instead of the terminal, unless instructed otherwise with the `--log-file` argument.
* Give Galaxy its own database user and database to prevent Galaxy's schema from conflicting with other tables in your database. Also, restrict Galaxy's database user so it only has access to its own database.
* Make sure Galaxy is using a clean Python interpreter. Conflicts in $PYTHONPATH or the interpreter's `site-packages/` directory could cause problems. Galaxy manages its own dependencies for the framework, so you do not need to worry about these. The easiest way to do this is with a [virtualenv](http://pypi.python.org/pypi/virtualenv):
+1 -1
View File
@@ -180,7 +180,7 @@ For this to work, the user under which your nginx server runs will need read acc
### External user authentication
- [Apache for External Authentication](https://galaxyproject.org/admin/config/apache-external-user-auth/)
- [Built-in Galaxy External Authentication](../authentication.md)
- [Built-in Galaxy External Authentication](../authentication.html)
### Display Sites
@@ -1,4 +1,6 @@
AGalaxy contains a plugin infrastructure for collecting and displaying metrics for job execution (see the original [pull request](https://bitbucket.org/galaxy/galaxy-central/pull-request/352) for implementation details).
# Job Metrics
Galaxy contains a plugin infrastructure for collecting and displaying metrics for job execution (see the original [pull request](https://bitbucket.org/galaxy/galaxy-central/pull-request/352) for implementation details).
## Configuration
+1 -1
View File
@@ -304,4 +304,4 @@ server { # This you should already have.
### External User Authentication
- [Nginx for External Authentication](https://galaxyproject.org/admin/config/nginx-external-user-auth/)
- [Built-in Galaxy External Authentication](../authentication.md)
- [Built-in Galaxy External Authentication](../authentication.html)
+87 -30
View File
@@ -8,6 +8,11 @@ import os.path
from markupsafe import escape
from galaxy import util
from galaxy.exceptions import (
AdminRequiredException,
ConfigDoesNotAllowException,
RequestParameterInvalidException,
)
from galaxy.tools.actions import upload_common
from galaxy.tools.parameters import populate_state
from galaxy.util.path import (
@@ -19,6 +24,47 @@ from galaxy.util.path import (
log = logging.getLogger(__name__)
def validate_server_directory_upload(trans, server_dir):
if server_dir in [None, 'None', '']:
raise RequestParameterInvalidException("Invalid or unspecified server_dir parameter")
if trans.user_is_admin():
import_dir = trans.app.config.library_import_dir
import_dir_desc = 'library_import_dir'
if not import_dir:
raise ConfigDoesNotAllowException('"library_import_dir" is not set in the Galaxy configuration')
else:
import_dir = trans.app.config.user_library_import_dir
if not import_dir:
raise ConfigDoesNotAllowException('"user_library_import_dir" is not set in the Galaxy configuration')
if server_dir != trans.user.email:
import_dir = os.path.join(import_dir, trans.user.email)
import_dir_desc = 'user_library_import_dir'
full_dir = os.path.join(import_dir, server_dir)
unsafe = None
if safe_relpath(server_dir):
username = trans.user.username if trans.app.config.user_library_import_check_permissions else None
if import_dir_desc == 'user_library_import_dir' and safe_contains(import_dir, full_dir, whitelist=trans.app.config.user_library_import_symlink_whitelist, username=username):
for unsafe in unsafe_walk(full_dir, whitelist=[import_dir] + trans.app.config.user_library_import_symlink_whitelist):
log.error('User attempted to import a path that resolves to a path outside of their import dir: %s -> %s', unsafe, os.path.realpath(unsafe))
else:
log.error('User attempted to import a directory path that resolves to a path outside of their import dir: %s -> %s', server_dir, os.path.realpath(full_dir))
unsafe = True
if unsafe:
raise RequestParameterInvalidException("Invalid server_dir specified")
return full_dir, import_dir_desc
def validate_path_upload(trans):
if not trans.app.config.allow_library_path_paste:
raise ConfigDoesNotAllowException('"allow_path_paste" is not set to True in the Galaxy configuration file')
if not trans.user_is_admin():
raise AdminRequiredException('Uploading files via filesystem paths can only be performed by administrators')
class LibraryActions(object):
"""
Mixin for controllers that provide library functionality.
@@ -42,37 +88,11 @@ class LibraryActions(object):
upload_option = kwd.get('upload_option', 'upload_file')
response_code = 200
if upload_option == 'upload_directory':
if server_dir in [None, 'None', '']:
response_code = 400
if trans.user_is_admin():
import_dir = trans.app.config.library_import_dir
import_dir_desc = 'library_import_dir'
else:
import_dir = trans.app.config.user_library_import_dir
if server_dir != trans.user.email:
import_dir = os.path.join(import_dir, trans.user.email)
import_dir_desc = 'user_library_import_dir'
full_dir = os.path.join(import_dir, server_dir)
unsafe = None
if safe_relpath(server_dir):
if import_dir_desc == 'user_library_import_dir' and safe_contains(import_dir, full_dir, whitelist=trans.app.config.user_library_import_symlink_whitelist):
for unsafe in unsafe_walk(full_dir, whitelist=[import_dir] + trans.app.config.user_library_import_symlink_whitelist):
log.error('User attempted to import a path that resolves to a path outside of their import dir: %s -> %s', unsafe, os.path.realpath(unsafe))
else:
log.error('User attempted to import a directory path that resolves to a path outside of their import dir: %s -> %s', server_dir, os.path.realpath(full_dir))
unsafe = True
if unsafe:
response_code = 403
message = 'Invalid server_dir'
if import_dir:
message = 'Select a directory'
else:
response_code = 403
message = '"%s" is not defined in the Galaxy configuration file' % import_dir_desc
full_dir, import_dir_desc = validate_server_directory_upload(trans, server_dir)
message = 'Select a directory'
elif upload_option == 'upload_paths':
if not trans.app.config.allow_library_path_paste:
response_code = 403
message = '"allow_library_path_paste" is not defined in the Galaxy configuration file'
# Library API already checked this - following check isn't actually needed.
validate_path_upload(trans)
# Some error handling should be added to this method.
try:
# FIXME: instead of passing params here ( which have been processed by util.Params(), the original kwd
@@ -118,6 +138,43 @@ class LibraryActions(object):
uploaded_datasets.append(self._make_library_uploaded_dataset(trans, params, name, file, 'server_dir', library_bunch))
return uploaded_datasets, 200, None
def _get_server_dir_files(self, params, full_dir, import_dir_desc):
files = []
try:
for entry in os.listdir(full_dir):
# Only import regular files
path = os.path.join(full_dir, entry)
link_data_only = params.get('link_data_only', 'copy_files')
if os.path.islink(full_dir) and link_data_only == 'link_to_files':
# If we're linking instead of copying and the
# sub-"directory" in the import dir is actually a symlink,
# dereference the symlink, but not any of its contents.
link_path = os.readlink(full_dir)
if os.path.isabs(link_path):
path = os.path.join(link_path, entry)
else:
path = os.path.abspath(os.path.join(link_path, entry))
elif os.path.islink(path) and os.path.isfile(path) and link_data_only == 'link_to_files':
# If we're linking instead of copying and the "file" in the
# sub-directory of the import dir is actually a symlink,
# dereference the symlink (one dereference only, Vasili).
link_path = os.readlink(path)
if os.path.isabs(link_path):
path = link_path
else:
path = os.path.abspath(os.path.join(os.path.dirname(path), link_path))
if os.path.isfile(path):
files.append(path)
except Exception as e:
message = "Unable to get file list for configured %s, error: %s" % (import_dir_desc, str(e))
response_code = 500
return None, response_code, message
if not files:
message = "The directory '%s' contains no valid files" % full_dir
response_code = 400
return None, response_code, message
return files, None, None
def _get_path_paste_uploaded_datasets(self, trans, params, library_bunch, response_code, message):
preserve_dirs = util.string_as_bool(params.get('preserve_dirs', False))
uploaded_datasets = []
+1 -3
View File
@@ -371,9 +371,6 @@ class Configuration(object):
# Beta containers interface used by GIEs
self.enable_beta_containers_interface = string_as_bool(kwargs.get('enable_beta_containers_interface', 'False'))
# Deprecated API for sample tracking
self.enable_legacy_sample_tracking_api = string_as_bool(kwargs.get('enable_legacy_sample_tracking_api', 'False'))
# Certain modules such as the pause module will automatically cause
# workflows to be scheduled in job handlers the way all workflows will
# be someday - the following two properties can also be used to force this
@@ -429,6 +426,7 @@ class Configuration(object):
self.library_import_dir = kwargs.get('library_import_dir', None)
self.user_library_import_dir = kwargs.get('user_library_import_dir', None)
self.user_library_import_symlink_whitelist = listify(kwargs.get('user_library_import_symlink_whitelist', []), do_strip=True)
self.user_library_import_check_permissions = string_as_bool(kwargs.get('user_library_import_check_permissions', False))
# Searching data libraries
self.ftp_upload_dir = kwargs.get('ftp_upload_dir', None)
self.ftp_upload_dir_identifier = kwargs.get('ftp_upload_dir_identifier', 'email') # attribute on user - email, username, id, etc...
@@ -46,7 +46,7 @@ SWARM_MANAGER_CONF_DEFAULTS = {
'service_wait_count_limit': 0,
'service_wait_time_limit': 5,
'slots_min_limit': 0,
'slots_max_limit': sys.maxint,
'slots_max_limit': sys.maxsize,
'slots_min_spare': 0,
'node_idle_limit': 120,
'limits': [],
@@ -137,7 +137,7 @@ class SwarmManager(object):
state = node_state['state']
if not nodes:
nodes = self._docker_interface.nodes()
node = (filter(lambda x: x.name == name, nodes) + [None])[0]
node = ([x for x in nodes if x.name == name] + [None])[0]
if not node:
if elapsed > self._conf.spawn_wait_time:
log.warning("spawning node '%s' not found in `docker node ls` and spawn_wait_time exceeded! %d seconds have elapsed", name, elapsed)
+49 -181
View File
@@ -16,12 +16,13 @@ from json import dumps
import h5py
import pysam
import pysam.bcftools
from bx.seq.twobit import TWOBIT_MAGIC_NUMBER, TWOBIT_MAGIC_NUMBER_SWAP, TWOBIT_MAGIC_SIZE
from galaxy import util
from galaxy.datatypes import metadata
from galaxy.datatypes.metadata import DictParameter, ListParameter, MetadataElement, MetadataParameter
from galaxy.util import FILENAME_VALID_CHARS, nice_size, sqlite, which
from galaxy.util import FILENAME_VALID_CHARS, nice_size, sqlite
from galaxy.util.checkers import is_bz2, is_gzip
from . import data, dataproviders
@@ -206,186 +207,85 @@ class Bam(Binary):
MetadataElement(name="column_types", default=['str', 'int', 'str', 'int', 'int', 'str', 'str', 'int', 'int', 'str', 'str', 'str'], desc="Column types", param=metadata.ColumnTypesParameter, readonly=True, visible=False, no_value=[])
MetadataElement(name="column_names", default=['QNAME', 'FLAG', 'RNAME', 'POS', 'MAPQ', 'CIGAR', 'MRNM', 'MPOS', 'ISIZE', 'SEQ', 'QUAL', 'OPT'], desc="Column names", readonly=True, visible=False, optional=True, no_value=[])
def _get_samtools_version(self):
version = '0.0.0'
samtools_exec = which('samtools')
if not samtools_exec:
message = 'Attempting to use functionality requiring samtools, but it cannot be located on Galaxy\'s PATH.'
raise Exception(message)
p = subprocess.Popen(['samtools', '--version-only'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, error = p.communicate()
# --version-only is available
# Format is <version x.y.z>+htslib-<a.b.c>
if p.returncode == 0:
version = output.split('+')[0]
return version
output = subprocess.Popen(['samtools'], stderr=subprocess.PIPE, stdout=subprocess.PIPE).communicate()[1]
lines = output.split('\n')
for line in lines:
if line.lower().startswith('version'):
# Assuming line looks something like: version: 0.1.12a (r862)
version = line.split()[1]
break
return version
@staticmethod
def merge(split_files, output_file):
"""
Merges BAM files
tmp_dir = tempfile.mkdtemp()
stderr_name = tempfile.NamedTemporaryFile(dir=tmp_dir, prefix="bam_merge_stderr").name
command = ["samtools", "merge", "-f", output_file] + split_files
proc = subprocess.Popen(args=command, stderr=open(stderr_name, 'wb'))
exit_code = proc.wait()
# Did merge succeed?
stderr = open(stderr_name).read().strip()
if stderr:
if exit_code != 0:
shutil.rmtree(tmp_dir) # clean up
raise Exception("Error merging BAM files: %s" % stderr)
else:
print(stderr)
os.unlink(stderr_name)
os.rmdir(tmp_dir)
def _is_coordinate_sorted(self, file_name):
"""See if the input BAM file is sorted from the header information."""
output = subprocess.check_output(["samtools", "view", "-H", file_name])
return 'SO:coordinate' in output or 'SO:sorted' in output
:param split_files: List of bam file paths to merge
:param output_file: Write merged bam file to this location
"""
pysam.merge('-O', 'BAM', output_file, *split_files)
def dataset_content_needs_grooming(self, file_name):
"""See if file_name is a sorted BAM file"""
version = self._get_samtools_version()
if version < '0.1.13':
return not self._is_coordinate_sorted(file_name)
else:
# Samtools version 0.1.13 or newer produces an error condition when attempting to index an
# unsorted bam file - see http://biostar.stackexchange.com/questions/5273/is-my-bam-file-sorted.
# So when using a newer version of samtools, we'll first check if the input BAM file is sorted
# from the header information. If the header is present and sorted, we do nothing by returning False.
# If it's present and unsorted or if it's missing, we'll index the bam file to see if it produces the
# error. If it does, sorting is needed so we return True (otherwise False).
#
# TODO: we're creating an index file here and throwing it away. We then create it again when
# the set_meta() method below is called later in the job process. We need to enhance this overall
# process so we don't create an index twice. In order to make it worth the time to implement the
# upload tool / framework to allow setting metadata from directly within the tool itself, it should be
# done generically so that all tools will have the ability. In testing, a 6.6 gb BAM file took 128
# seconds to index with samtools, and 45 minutes to sort, so indexing is relatively inexpensive.
if self._is_coordinate_sorted(file_name):
return False
index_name = tempfile.NamedTemporaryFile(prefix="bam_index").name
stderr_name = tempfile.NamedTemporaryFile(prefix="bam_index_stderr").name
proc = subprocess.Popen(['samtools', 'index', file_name, index_name], stderr=open(stderr_name, 'wb'))
proc.wait()
stderr = open(stderr_name).read().strip()
if stderr:
try:
os.unlink(index_name)
except OSError:
pass
try:
os.unlink(stderr_name)
except OSError:
pass
# Return True if unsorted error condition is found (find returns -1 if string is not found).
return stderr.find("[bam_index_core] the alignment is not sorted") != -1
try:
os.unlink(index_name)
except OSError:
pass
try:
os.unlink(stderr_name)
except OSError:
pass
return False
"""
Check if file_name is a coordinate-sorted BAM file
"""
# The best way to ensure that BAM files are coordinate-sorted and indexable
# is to actually index them.
index_name = tempfile.NamedTemporaryFile(prefix="bam_index").name
try:
# If pysam fails to index a file it will write to stderr,
# and this causes the set_meta script to fail. So instead
# we start another process and discard stderr.
cmd = ['python', '-c', "import pysam; pysam.index('%s', '%s')" % (file_name, index_name)]
with open(os.devnull, 'w') as devnull:
subprocess.check_call(cmd, stderr=devnull, shell=False)
needs_sorting = False
except subprocess.CalledProcessError:
needs_sorting = True
try:
os.unlink(index_name)
except Exception:
pass
return needs_sorting
def groom_dataset_content(self, file_name):
"""
Ensures that the Bam file contents are sorted. This function is called
Ensures that the BAM file contents are sorted. This function is called
on an output dataset after the content is initially generated.
"""
# Use samtools to sort the Bam file
# $ samtools sort
# Usage: samtools sort [-on] [-m <maxMem>] <in.bam> <out.prefix>
# Sort alignments by leftmost coordinates. File <out.prefix>.bam will be created.
# This command may also create temporary files <out.prefix>.%d.bam when the
# whole alignment cannot be fitted into memory ( controlled by option -m ).
# Use pysam to sort the BAM file
# This command may also creates temporary files <out.prefix>.%d.bam when the
# whole alignment cannot fit into memory.
# do this in a unique temp directory, because of possible <out.prefix>.%d.bam temp files
if not self.dataset_content_needs_grooming(file_name):
# Don't re-sort if already sorted
return
tmp_dir = tempfile.mkdtemp()
tmp_sorted_dataset_file_name_prefix = os.path.join(tmp_dir, 'sorted')
stderr_name = tempfile.NamedTemporaryFile(dir=tmp_dir, prefix="bam_sort_stderr").name
samtools_created_sorted_file_name = "%s.bam" % tmp_sorted_dataset_file_name_prefix # samtools accepts a prefix, not a filename, it always adds .bam to the prefix
proc = subprocess.Popen(['samtools', 'sort', file_name, tmp_sorted_dataset_file_name_prefix],
cwd=tmp_dir, stderr=open(stderr_name, 'wb'))
exit_code = proc.wait()
# Did sort succeed?
stderr = open(stderr_name).read().strip()
if stderr:
if exit_code != 0:
shutil.rmtree(tmp_dir) # clean up
raise Exception("Error Grooming BAM file contents: %s" % stderr)
else:
print(stderr)
sorted_file_name = "%s.bam" % tmp_sorted_dataset_file_name_prefix
slots = os.environ.get('GALAXY_SLOTS', 1)
try:
pysam.sort("-@%s" % slots, file_name, '-T', tmp_sorted_dataset_file_name_prefix, '-O', 'BAM', '-o', sorted_file_name)
except Exception:
shutil.rmtree(tmp_dir, ignore_errors=True)
raise
# Move samtools_created_sorted_file_name to our output dataset location
shutil.move(samtools_created_sorted_file_name, file_name)
shutil.move(sorted_file_name, file_name)
# Remove temp file and empty temporary directory
os.unlink(stderr_name)
os.rmdir(tmp_dir)
def init_meta(self, dataset, copy_from=None):
Binary.init_meta(self, dataset, copy_from=copy_from)
def set_meta(self, dataset, overwrite=True, **kwd):
""" Creates the index for the BAM file. """
# These metadata values are not accessible by users, always overwrite
index_file = dataset.metadata.bam_index
if not index_file:
index_file = dataset.metadata.spec['bam_index'].param.new_file(dataset=dataset)
# Create the Bam index
# $ samtools index
# Usage: samtools index <in.bam> [<out.index>]
stderr_name = tempfile.NamedTemporaryFile(prefix="bam_index_stderr").name
command = ['samtools', 'index', dataset.file_name, index_file.file_name]
exit_code = subprocess.call(args=command, stderr=open(stderr_name, 'wb'))
# Did index succeed?
if exit_code == -6:
# SIGABRT, most likely samtools 1.0+ which does not accept the index name parameter.
dataset_symlink = os.path.join(os.path.dirname(index_file.file_name),
'__dataset_%d_%s' % (dataset.id, os.path.basename(index_file.file_name)))
os.symlink(dataset.file_name, dataset_symlink)
try:
command = ['samtools', 'index', dataset_symlink]
exit_code = subprocess.call(args=command, stderr=open(stderr_name, 'wb'))
shutil.move(dataset_symlink + '.bai', index_file.file_name)
except Exception as e:
open(stderr_name, 'ab+').write('Galaxy attempted to build the BAM index with samtools 1.0+ but failed: %s\n' % e)
exit_code = 1 # Make sure an exception raised by shutil.move() is re-raised below
finally:
os.unlink(dataset_symlink)
stderr = open(stderr_name).read().strip()
if stderr:
if exit_code != 0:
os.unlink(stderr_name) # clean up
raise Exception("Error Setting BAM Metadata: %s" % stderr)
else:
print(stderr)
pysam.index(dataset.file_name, index_file.file_name)
dataset.metadata.bam_index = index_file
# Remove temp file
os.unlink(stderr_name)
# Now use pysam with BAI index to determine additional metadata
try:
bam_file = pysam.AlignmentFile(dataset.file_name, mode='rb', index_filename=index_file.file_name)
# TODO: Reference names, lengths, read_groups and headers can become very large, truncate when necessary
dataset.metadata.reference_names = list(bam_file.references)
dataset.metadata.reference_lengths = list(bam_file.lengths)
dataset.metadata.bam_header = bam_file.header
dataset.metadata.read_groups = [read_group['ID'] for read_group in dataset.metadata.bam_header.get('RG', []) if 'ID' in read_group]
dataset.metadata.sort_order = dataset.metadata.bam_header.get('HD', {}).get('SO', None)
dataset.metadata.bam_version = dataset.metadata.bam_header.get('HD', {}).get('VN', None)
dataset.metadata.sort_order = bam_file.header.get('HD', {}).get('SO', None)
dataset.metadata.bam_version = bam_file.header.get('HD', {}).get('VN', None)
except Exception:
# Per Dan, don't log here because doing so will cause datasets that
# fail metadata to end in the error state
@@ -586,24 +486,7 @@ class CRAM(Binary):
def set_index_file(self, dataset, index_file):
try:
# @todo when pysam 1.2.1 or pysam 1.3.0 gets released and becomes
# a dependency of galaxy, use pysam.index(alignment, target_idx)
# This currently gives coredump in the current release but is
# fixed in the dev branch:
# xref: https://github.com/samtools/samtools/issues/199
dataset_symlink = os.path.join(os.path.dirname(index_file.file_name), '__dataset_%d_%s' % (dataset.id, os.path.basename(index_file.file_name)))
os.symlink(dataset.file_name, dataset_symlink)
pysam.index(dataset_symlink)
tmp_index = dataset_symlink + ".crai"
if os.path.isfile(tmp_index):
shutil.move(tmp_index, index_file.file_name)
return index_file.file_name
else:
os.unlink(dataset_symlink)
log.warning('%s, expected crai index not created for: %s', self, dataset.file_name)
return False
pysam.index(dataset.file_name, index_file.file_name)
except Exception as exc:
log.warning('%s, set_index_file Exception: %s', self, exc)
return False
@@ -635,13 +518,6 @@ class Bcf(BaseBcf):
"""
Class describing a (BGZF-compressed) BCF file
>>> from galaxy.datatypes.sniff import get_test_fname
>>> fname = get_test_fname('1.bcf')
>>> Bcf().sniff(fname)
True
>>> fname = get_test_fname('1.bcf_uncompressed')
>>> Bcf().sniff(fname)
False
"""
file_ext = "bcf"
@@ -665,24 +541,16 @@ class Bcf(BaseBcf):
if not index_file:
index_file = dataset.metadata.spec['bcf_index'].param.new_file(dataset=dataset)
# Create the bcf index
# $ bcftools index
# Usage: bcftools index <in.bcf>
dataset_symlink = os.path.join(os.path.dirname(index_file.file_name),
'__dataset_%d_%s' % (dataset.id, os.path.basename(index_file.file_name)))
os.symlink(dataset.file_name, dataset_symlink)
stderr_name = tempfile.NamedTemporaryFile(prefix="bcf_index_stderr").name
command = ['bcftools', 'index', dataset_symlink]
try:
subprocess.check_call(args=command, stderr=open(stderr_name, 'wb'))
shutil.move(dataset_symlink + '.csi', index_file.file_name) # this will fail if bcftools < 1.0 is used, because it creates a .bci index file instead of .csi
pysam.bcftools.index(dataset_symlink)
shutil.move(dataset_symlink + '.csi', index_file.file_name)
except Exception as e:
stderr = open(stderr_name).read().strip()
raise Exception('Error setting BCF metadata: %s' % (stderr or str(e)))
raise Exception('Error setting BCF metadata: %s' % (str(e)))
finally:
# Remove temp file and symlink
os.remove(stderr_name)
os.remove(dataset_symlink)
dataset.metadata.bcf_index = index_file
@@ -5,6 +5,7 @@ Uses pysam to convert a CRAM file to a sorted bam file.
usage: %prog in_file out_file
"""
import optparse
import os
import pysam
@@ -14,7 +15,8 @@ def main():
parser = optparse.OptionParser()
(options, args) = parser.parse_args()
input_fname, output_fname = args
pysam.sort('-o', output_fname, '-O', 'bam', '-T', '.', input_fname)
slots = os.getenv('GALAXY_SLOTS', 1)
pysam.sort("-@%s" % slots, '-o', output_fname, '-O', 'bam', '-T', '.', input_fname)
if __name__ == "__main__":
@@ -21,20 +21,29 @@ def main():
parser.add_option('-e', '--end-col', type='int', dest='end_col')
parser.add_option('-P', '--preset', dest='preset')
(options, args) = parser.parse_args()
input_fname, index_fname, out_fname = args
_, bgzip_fname, out_fname = args
to_tabix(bgzip_fname=bgzip_fname,
out_fname=out_fname,
preset=options.preset,
chrom_col=options.chrom_col,
start_col=options.start_col,
end_col=options.end_col)
def to_tabix(bgzip_fname, out_fname, preset=None, chrom_col=None, start_col=None, end_col=None):
# Create index.
if options.preset:
if preset:
# Preset type.
pysam.tabix_index(filename=index_fname, preset=options.preset, keep_original=True,
index_filename=out_fname)
bgzip_fname = pysam.tabix_index(filename=bgzip_fname, preset=preset, keep_original=True,
index=out_fname, force=True)
else:
# For interval files; column indices are 0-based.
pysam.tabix_index(filename=index_fname, seq_col=(options.chrom_col - 1),
start_col=(options.start_col - 1), end_col=(options.end_col - 1),
keep_original=True, index_filename=out_fname)
if os.path.getsize(index_fname) == 0:
bgzip_fname = pysam.tabix_index(filename=bgzip_fname, seq_col=(chrom_col - 1),
start_col=(start_col - 1), end_col=(end_col - 1),
keep_original=True, index=out_fname, force=True)
if os.path.getsize(out_fname) == 0:
sys.stderr.write("The converted tabix index file is empty, meaning the input data is invalid.")
return bgzip_fname
if __name__ == "__main__":
+2 -2
View File
@@ -449,7 +449,7 @@ class ImzML(Binary):
def generate_primary_file(self, dataset=None):
rval = ['<html><head><title>imzML Composite Dataset </title></head><p/>']
rval.append('<div>This composite dataset is composed of the following files:<p/><ul>')
for composite_name, composite_file in self.get_composite_files(dataset=dataset).iteritems():
for composite_name, composite_file in self.get_composite_files(dataset=dataset).items():
fn = composite_name
opt_text = ''
if composite_file.get('description'):
@@ -493,7 +493,7 @@ class Analyze75(Binary):
def generate_primary_file(self, dataset=None):
rval = ['<html><head><title>Analyze75 Composite Dataset.</title></head><p/>']
rval.append('<div>This composite dataset is composed of the following files:<p/><ul>')
for composite_name, composite_file in self.get_composite_files(dataset=dataset).iteritems():
for composite_name, composite_file in self.get_composite_files(dataset=dataset).items():
fn = composite_name
opt_text = ''
if composite_file.optional:
@@ -1,7 +1,6 @@
<tool id="__SET_METADATA__" name="Set External Metadata" version="1.0.1" tool_type="set_metadata">
<type class="SetMetadataTool" module="galaxy.tools"/>
<requirements>
<requirement type="package">samtools</requirement>
<requirement type="package" version="1.5">bcftools</requirement>
</requirements>
<action module="galaxy.tools.actions.metadata" class="SetMetadataToolAction"/>
+2 -2
View File
@@ -104,7 +104,7 @@ def check_newlines(fname, bytes_to_read=52428800):
return False
def convert_newlines(fname, in_place=True, tmp_dir=None, tmp_prefix=None):
def convert_newlines(fname, in_place=True, tmp_dir=None, tmp_prefix="gxupload"):
"""
Converts in place a file from universal line endings
to Posix line endings.
@@ -166,7 +166,7 @@ def sep2tabs(fname, in_place=True, patt="\\s+"):
return (i, temp_name)
def convert_newlines_sep2tabs(fname, in_place=True, patt="\\s+", tmp_dir=None, tmp_prefix=None):
def convert_newlines_sep2tabs(fname, in_place=True, patt="\\s+", tmp_dir=None, tmp_prefix="gxupload"):
"""
Combines above methods: convert_newlines() and sep2tabs()
so that files do not need to be read twice
+16 -18
View File
@@ -15,6 +15,8 @@ import tempfile
from cgi import escape
from json import dumps
import pysam
from galaxy import util
from galaxy.datatypes import binary, data, metadata
from galaxy.datatypes.metadata import MetadataElement
@@ -23,6 +25,7 @@ from galaxy.datatypes.sniff import (
iter_headers
)
from galaxy.util import compression_utils
from galaxy.util.checkers import is_gzip
from . import dataproviders
if sys.version_info > (3,):
@@ -733,6 +736,11 @@ class BaseVcf(Tabular):
class Vcf(BaseVcf):
file_ext = 'vcf'
def sniff(self, filename):
if is_gzip(filename):
return False
return super(Vcf, self).sniff(filename)
class VcfGz(BaseVcf, binary.Binary):
file_ext = 'vcf_bgzip'
@@ -740,33 +748,23 @@ class VcfGz(BaseVcf, binary.Binary):
MetadataElement(name="tabix_index", desc="Vcf Index File", param=metadata.FileParameter, file_ext="tbi", readonly=True, no_value=None, visible=False, optional=True)
def sniff(self, filename):
if not is_gzip(filename):
return False
return super(VcfGz, self).sniff(filename)
def set_meta(self, dataset, **kwd):
super(BaseVcf, self).set_meta(dataset, **kwd)
""" Creates the index for the VCF file. """
# These metadata values are not accessible by users, always overwrite
index_file = dataset.metadata.bcf_index
index_file = dataset.metadata.tabix_index
if not index_file:
index_file = dataset.metadata.spec['tabix_index'].param.new_file(dataset=dataset)
# Create the bcf index
# $ bcftools index
# Usage: bcftools index <in.bcf>
dataset_symlink = os.path.join(os.path.dirname(index_file.file_name),
'__dataset_%d_%s' % (dataset.id, os.path.basename(index_file.file_name))) + ".vcf.gz"
os.symlink(dataset.file_name, dataset_symlink)
stderr_name = tempfile.NamedTemporaryFile(prefix="bcf_index_stderr").name
command = ['bcftools', 'index', '-t', dataset_symlink]
try:
subprocess.check_call(args=command, stderr=open(stderr_name, 'wb'))
shutil.move(dataset_symlink + '.tbi', index_file.file_name) # this will fail if bcftools < 1.0 is used, because it creates a .bci index file instead of .csi
pysam.tabix_index(dataset.file_name, index=index_file.file_name, preset='vcf', force=True)
except Exception as e:
stderr = open(stderr_name).read().strip()
raise Exception('Error setting BCF metadata: %s' % (stderr or str(e)))
finally:
# Remove temp file and symlink
os.remove(stderr_name)
os.remove(dataset_symlink)
raise Exception('Error setting VCF.gz metadata: %s' % (str(e)))
dataset.metadata.tabix_index = index_file

Some files were not shown because too many files have changed in this diff Show More