Merge branch 'dev' of https://github.com/galaxyproject/galaxy into realtimetools

This commit is contained in:
Daniel Blankenberg
2019-08-13 14:47:53 -04:00
62 changed files with 656 additions and 539 deletions
@@ -16,19 +16,24 @@
<table v-if="match(plugin)">
<tr class="ui-thumbnails-item" @click="select(plugin)">
<td>
<img v-if="plugin.logo" alt="ui thumbnails" class="ui-thumbnails-image" :src="plugin.logo" />
<img
v-if="plugin.logo"
alt="ui thumbnails"
class="ui-thumbnails-image"
:src="plugin.logo"
/>
<div v-else class="ui-thumbnails-icon fa fa-eye" />
</td>
<td>
<div class="ui-thumbnails-title font-weight-bold text-dark">{{ plugin.html }}</div>
<div class="ui-thumbnails-text text-dark">{{ plugin.description }}</div>
<div class="ui-thumbnails-title font-weight-bold">{{ plugin.html }}</div>
<div class="ui-thumbnails-text">{{ plugin.description }}</div>
</td>
</tr>
<tr v-if="!fixed">
<td />
<td v-if="plugin.name == name">
<div v-if="hdas && hdas.length > 0">
<div class="font-weight-bold text-dark">Select a dataset to visualize:</div>
<div class="font-weight-bold">Select a dataset to visualize:</div>
<div class="ui-select">
<select class="select" v-model="selected">
<option v-for="file in hdas" :key="file.id" :value="file.id">{{
@@ -51,7 +51,6 @@ function TagModel(props = {}) {
get: function() {
if (!this.text.length) return false;
return this.text != "name:";
}
});
}
@@ -84,7 +84,7 @@ export default {
},
methods: {
findSection: function(name) {
const result = ['', ''];
const result = ["", ""];
if (name) {
const found = this.toolSections.find(s => {
return s.name.toLowerCase().trim() == name.toLowerCase().trim();
@@ -157,7 +157,9 @@ export default {
},
setupRepository: function(details) {
this.selectedChangeset = details.changeset_revision;
this.selectedRequiresPanel = details.includes_tools_for_display_in_tool_panel || details.repository.type == "repository_suite_definition";
this.selectedRequiresPanel =
details.includes_tools_for_display_in_tool_panel ||
details.repository.type == "repository_suite_definition";
this.showSettings = true;
},
uninstallRepository: function(details) {
@@ -4,8 +4,7 @@
<h2 id="data-managers-title">Local Data</h2>
<p>
Data Managers are used to manage Galaxy's local data. They can be installed manually, or via the
<b-link :href="toolShedLink" target="galaxy_main">ToolShed</b-link>. For more
comprehensive information
<b-link :href="toolShedLink" target="galaxy_main">ToolShed</b-link>. For more comprehensive information
<b-link href="https://galaxyproject.org/admin/tools/data-managers/" target="_blank">See the Wiki</b-link
>.
</p>
@@ -17,10 +16,10 @@
import { getAppRoot } from "onload/loadConfig";
export default {
computed:{
computed: {
toolShedLink() {
return `${getAppRoot()}admin_toolshed/browse_tool_sheds`
return `${getAppRoot()}admin_toolshed/browse_tool_sheds`;
}
}
}
};
</script>
@@ -27,7 +27,10 @@
('.', '_', '-').</b-form-text
>
</b-form-group>
<b-form-group v-if="mailing_join_addr && server_mail_configured" label="Subscribe to mailing list">
<b-form-group
v-if="mailing_join_addr && server_mail_configured"
label="Subscribe to mailing list"
>
<input name="subscribe" type="checkbox" v-model="subscribe" />
</b-form-group>
<b-button name="create" type="submit" :disabled="disableCreate">Create</b-button>
+2 -2
View File
@@ -5,10 +5,10 @@
</h4>
<table>
<tr v-for="(xref, index) in xrefs" v-bind:key="index">
<th> - {{xref.reftype}}: </th>
<th>- {{ xref.reftype }}:</th>
<td>
<template v-if="xref.reftype == 'bio.tools'">
<a :href="`https://bio.tools/${xref.value}`" target="_blank">{{xref.value}}</a>
<a :href="`https://bio.tools/${xref.value}`" target="_blank">{{ xref.value }}</a>
</template>
<template v-else>
{{ xref.value }}
@@ -52,9 +52,17 @@ const ToolPanel = Backbone.View.extend({
title: _l("Show favorites"),
icon: "fa fa-star-o",
onclick: e => {
$("#tool-search-query")
.val("#favorites")
.trigger("change");
const $search_query = $("#tool-search-query");
const $header_btn = $(".panel-header-button");
$header_btn.find(".fa").toggleClass("fa-star-o fa-star");
$header_btn.tooltip("hide");
if ($search_query.val().indexOf("#favorites") != -1) {
$search_query.val("");
$search_query.keyup();
$header_btn.attr("title", "");
} else {
$search_query.val("#favorites").trigger("change");
}
}
});
panel_buttons.push(this.favorite_button);
+2 -2
View File
@@ -172,8 +172,8 @@ const View = Backbone.View.extend({
`<div id="everything">
<div id="background"/>
<div id="masthead"/>
<div id="messagebox"/>
<div id="inactivebox" class="panel-warning-message" />
<div id="messagebox" class="full-message"/>
<div id="inactivebox" class="full-message panel-warning-message" />
<div id="columns">
<div id="left" class="unified-panel"/>
<div id="center" />
@@ -95,7 +95,6 @@ var HistoryViewColumn = Backbone.View.extend(baseMVC.LoggableMixin).extend({
return false;
}
return columnLeft <= viewRight;
},
/** shortcut to the panel */
@@ -355,8 +355,8 @@ var FolderListView = Backbone.View.extend({
renderSortIcon: function() {
$('[class*="sort-icon"]')
.removeClass("fa-sort-alpha-desc")
.removeClass("fa-sort-alpha-asc");
.removeClass("fa-sort-alpha-desc")
.removeClass("fa-sort-alpha-asc");
if (this.current_sort_order === "asc") {
$(`.sort-icon-${this.current_sort_key}`).addClass("fa-sort-alpha-asc");
@@ -74,7 +74,7 @@ var FolderAsModel = LibraryItem.extend({
});
var Folder = Backbone.Collection.extend({
model: LibraryItem,
model: LibraryItem
});
var FolderContainer = Backbone.Model.extend({
@@ -18,14 +18,12 @@ var generateComparator = (sort_key, sort_order) => (itemA, itemB) => {
if (typeof itemA.get(sort_key) === "number") {
comparable_itemA_key = itemA.get(sort_key);
comparable_itemB_key = itemB.get(sort_key);
}
else
{
} else {
comparable_itemA_key = itemA.get(sort_key).toLowerCase();
comparable_itemB_key = itemB.get(sort_key).toLowerCase();
}
if ( comparable_itemA_key > comparable_itemB_key) {
if (comparable_itemA_key > comparable_itemB_key) {
return sort_order === "asc" ? 1 : -1;
}
if (comparable_itemB_key > comparable_itemA_key) {
+3 -1
View File
@@ -117,7 +117,9 @@ var TagsEditor = Backbone.View.extend(baseMVC.LoggableMixin)
});
if (renderedArray.length === 0) {
// If there are no tags to render we just show the add-tag-button
renderedArray.push(`<img alt="add tag button" src=${addButton} class="add-tag-button" title="Add tags"/>`);
renderedArray.push(
`<img alt="add tag button" src=${addButton} class="add-tag-button" title="Add tags"/>`
);
}
return renderedArray.join(" ");
},
@@ -280,13 +280,13 @@ export default FormBase.extend({
}
}).$mount(vm);
}
if (options.xrefs && options.xrefs.length) {
if (options.xrefs && options.xrefs.length) {
var xrefInstance = Vue.extend(xrefs);
vm = document.createElement("div");
$el.append(vm);
new xrefInstance({
propsData: {
id: options.id,
id: options.id,
source: "tools"
}
}).$mount(vm);
@@ -335,7 +335,7 @@ const View = Backbone.View.extend({
this.button_type = new Ui.RadioButton.View({
value: this.model.get("current"),
data: this.button_data,
cls: "mr-2",
cls: "pr-lg-2",
onchange: function(value) {
self.model.set("current", value);
self.trigger("change");
@@ -346,7 +346,6 @@ const View = Backbone.View.extend({
this.button_dialog = new Ui.Button({
icon: "fa-folder-open-o",
tooltip: "Browse Datasets",
cls: "ml-2",
onclick: () => {
const current = this.model.get("current");
const cnf = this.config[current];
@@ -364,10 +363,10 @@ const View = Backbone.View.extend({
});
// append views
const $fields = $("<div/>").addClass("w-100");
const $fields = $("<div/>").addClass("overflow-auto w-100 py-2 py-lg-0 pr-lg-2 ");
this.$el
.empty()
.addClass("d-flex flex-row")
.addClass("d-flex flex-row flex-wrap flex-lg-nowrap")
.append($("<div/>").append(this.button_type.$el))
.append($fields)
.append($("<div/>").append(this.button_dialog.$el));
@@ -97,7 +97,7 @@ class CanvasManager {
this.init_copy_paste();
}
setZoom(zoomLevel) {
this.zoomLevel = Math.min(Math.max(0, zoomLevel), zoomLevels.length-1);
this.zoomLevel = Math.min(Math.max(0, zoomLevel), zoomLevels.length - 1);
this.canvasZoom = zoomLevels[this.zoomLevel];
// Set CSS transform to appropriate zoom level
this.cv.css("transform-origin", "top left");
@@ -115,26 +115,28 @@ class CanvasManager {
bottom: "1rem",
cursor: "pointer"
});
const zoomButton = $(`<a class="btn btn-light" title="Reset Zoom Level">${zoomLevels[defaultZoomLevel] * 100}%</a>`).css({
const zoomButton = $(
`<a class="btn btn-light" title="Reset Zoom Level">${zoomLevels[defaultZoomLevel] * 100}%</a>`
).css({
width: "4rem"
});
zoomControl.append(
$('<a class="btn btn-secondary fa fa-minus"/>').click(() => {
this.setZoom(this.zoomLevel - 1);
zoomButton.text(Math.floor(zoomLevels[this.zoomLevel] * 100) + '%');
zoomButton.text(Math.floor(zoomLevels[this.zoomLevel] * 100) + "%");
})
);
zoomControl.append(
zoomButton.click(() => {
this.setZoom(defaultZoomLevel);
zoomButton.text(Math.floor(zoomLevels[this.zoomLevel] * 100) + '%');
zoomButton.text(Math.floor(zoomLevels[this.zoomLevel] * 100) + "%");
})
);
zoomControl.append(
$('<a class="btn btn-secondary fa fa-plus"/>').click(() => {
this.setZoom(this.zoomLevel + 1);
zoomButton.text(Math.floor(zoomLevels[this.zoomLevel] * 100) + '%');
zoomButton.text(Math.floor(zoomLevels[this.zoomLevel] * 100) + "%");
})
);
this.cv.closest("#workflow-canvas-body").append(zoomControl);
@@ -158,12 +158,12 @@ const WorkflowItemView = Backbone.View.extend({
<a class="dropdown-item" href="${getAppRoot()}api/workflows/${
this.model.id
}/download?format=json-download">Download</a>
<a class="dropdown-item" id="copy-workflow">Copy</a>
<a class="dropdown-item" id="rename-workflow">Rename</a>
<a class="dropdown-item" href="javascript:void(0)" id="copy-workflow">Copy</a>
<a class="dropdown-item" href="javascript:void(0)" id="rename-workflow">Rename</a>
<a class="dropdown-item" href="${getAppRoot()}workflow/display_by_id?id=${
this.model.id
}">View</a>
<a class="dropdown-item" id="delete-workflow">Delete</a>
<a class="dropdown-item" href="javascript:void(0)" id="delete-workflow">Delete</a>
</div>`;
} else {
return `<div class="dropdown-menu">
@@ -171,7 +171,7 @@ const WorkflowItemView = Backbone.View.extend({
"owner"
)}&slug=${this.model.get("slug")}">View</a>
<a class="dropdown-item" href="${getAppRoot()}workflows/run?id=${this.model.id}">Run</a>
<a class="dropdown-item" id="copy-workflow">Copy</a>
<a class="dropdown-item" href="javascript:void(0)" id="copy-workflow">Copy</a>
<a class="dropdown-item link-confirm-shared-${
this.model.id
}" href="${getAppRoot()}workflow/sharing?unshare_me=True&id=${this.model.id}">Remove</a></li>
@@ -323,7 +323,6 @@ _.extend(NumberFilter.prototype, {
*/
applies_to: function(element) {
return element.length > this.index;
},
/**
* Helper function: returns true if value in in filter's [low, high] range.
+179 -255
View File
@@ -10,19 +10,9 @@
$fa-font-path: "../../../node_modules/font-awesome/fonts/";
@import "../../../node_modules/font-awesome/scss/font-awesome.scss";
.fa-icon {
// TODO: remove fa-icon and replace with fa
@extend .fa;
}
@import "select2.scss";
@import "jquery.rating.scss";
/* fix for zero width select2 - remove when fixed there */
.select2-minwidth {
min-width: 256px;
}
// galaxy sub-components
@import "reports";
@import "frame.scss";
@@ -38,7 +28,24 @@ $fa-font-path: "../../../node_modules/font-awesome/fonts/";
@import "message.scss";
@import "toolshed.scss";
// Mixins
// ==== Select2 ====
/* fix for zero width select2 - remove when fixed there */
.select2-minwidth {
min-width: 256px;
}
.select2-drop-active,
.select2-drop-above.select2-drop-active {
border: $border-default;
.select2-search {
@extend .pt-1;
}
}
.select2-drop {
background: $body-bg;
color: $text-color;
}
// ==== Mixins ====
@mixin user-select($select) {
-webkit-touch-callout: #{$select};
@each $pre in -webkit-, -moz-, -ms-, -o-, -khtml- {
@@ -47,17 +54,6 @@ $fa-font-path: "../../../node_modules/font-awesome/fonts/";
#{user-select}: #{$select};
}
.unselectable {
@include user-select(none);
}
.parent-width {
//TODO: check out box-sizing w/ bs4
//@include box-sizing(border-box);
width: 100%;
*width: 90%;
}
@mixin border-radius($radius) {
// from font-awesome 3.0
-webkit-border-radius: $radius;
@@ -65,121 +61,117 @@ $fa-font-path: "../../../node_modules/font-awesome/fonts/";
border-radius: $radius;
}
// ==== Real (basic) styles ====
// ==== Basic styles ====
body {
@extend .m-2;
}
.unselectable {
@include user-select(none);
}
.parent-width {
@extend .w-100;
}
.clear {
@include clearfix();
}
body {
// ==== Page layout styles ====
.full-content {
@extend .m-0;
@extend .p-0;
@extend .w-100;
@extend .h-100;
@extend .overflow-hidden;
@extend .position-absolute;
background: $body-bg;
color: $text-color;
margin: 10px;
}
// For panel layout pages
&.full-content {
overflow: hidden;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.full-message {
@extend .w-100;
@extend .overflow-hidden;
height: $panel-message-height !important;
border-bottom: solid $border-color 1px;
font-size: 90%;
display: none;
}
#everything {
@extend .d-flex;
@extend .flex-column;
@extend .full-content;
}
#columns {
@extend .position-relative;
flex: 1;
}
#background {
@extend .full-content;
z-index: -1;
}
// Used for dragging panels, popup menus, to deal with capturing clicks in iframes
#dd-helper {
@extend .full-content;
opacity: 0;
z-index: 900; // Bootstrap elements start at 1000
display: none;
}
// ==== Panel layout styles ====
// Layout the paneled UI using flexbox.
#everything {
display: flex;
flex-direction: column;
position: absolute;
top: 0;
left: 0;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
#columns {
// Fill extra space in parent
flex: 1;
// Allow columns to be absolutely positioned inside
position: relative;
}
#background {
position: absolute;
background: #fff;
z-index: -1;
top: 0;
left: 0;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
#messagebox {
width: 100%;
height: $panel-message-height !important;
overflow: hidden;
border-bottom: solid $border-default-color 1px;
font-size: 90%;
display: none;
}
#inactivebox {
width: 100%;
height: $panel-message-height !important;
overflow: hidden;
border-bottom: solid $border-default-color 1px;
font-size: 90%;
display: none;
}
// Panels
#left,
#center,
#right {
position: absolute;
top: 0;
height: 100%;
overflow: hidden;
background: white;
@extend .position-absolute;
@extend .h-100;
@extend .overflow-hidden;
}
#left {
@extend .unified-panel;
left: 0px;
width: $panel-width;
border-right: $layout-border;
}
#right {
@extend .unified-panel;
right: 0px;
width: $panel-width;
border-left: $layout-border;
}
#right > .unified-panel-footer {
.drag {
left: 0;
}
}
#center {
@extend .overflow-hidden;
left: $panel-width;
right: $panel-width;
overflow: hidden;
.center-container {
position: absolute;
width: 100%;
height: 100%;
@extend .position-absolute;
@extend .w-100;
@extend .h-100;
.center-panel {
@extend .position-absolute;
@extend .w-100;
@extend .h-100;
@extend .overflow-auto;
display: none;
position: absolute;
width: 100%;
height: 100%;
padding: $margin-h $margin-v;
overflow: auto;
background: $brand-white;
background: $white;
}
.center-frame {
position: absolute;
width: 100%;
height: 100%;
@extend .position-absolute;
@extend .w-100;
@extend .h-100;
}
}
// Replaces unnecessary if/then logic in base_panels.mako
@@ -191,34 +183,34 @@ body {
}
}
#left,
#right,
.unified-panel {
background: $panel-bg-color;
display: flex;
flex-direction: column;
}
// ==== Unified panel styles ====
.unified-panel {
display: flex;
@extend .flex-column;
background: $panel-bg-color;
}
.unified-panel-header {
@extend .unselectable;
@extend .mx-3;
@extend .px-3;
@extend .d-flex;
height: $panel_header_height;
font-size: 1rem;
font-weight: bold;
display: flex;
align-items: center;
color: $panel-header-text-color;
background: $panel-bg-header-color;
a {
color: $panel-header-text-color;
}
.unified-panel-header-inner {
width: 100%;
@extend .w-100;
align-items: center;
justify-content: space-between;
}
.panel-header-buttons {
@extend .float-right;
order: 9999;
float: right;
.panel-header-button {
text-align: center;
&:not(:last-child) {
@@ -230,11 +222,19 @@ body {
}
}
}
.unified-panel-controls {
@extend .px-3;
}
.unified-panel-body {
@extend .p-0;
@extend .w-100;
@extend .h-100;
@extend .overflow-auto;
flex: 1;
}
.unified-panel-body-background {
background: none repeat scroll 0 0 $panel-bg-color;
}
.unified-panel-footer {
@extend .unselectable;
position: relative;
@@ -262,15 +262,9 @@ body {
}
}
#right > .unified-panel-footer {
.drag {
left: 0;
}
}
// Needs to be outside panel to show when hidden
.panel-collapse {
@extend .fa-icon;
@extend .fa;
$border-color: darken($panel-footer-bg-color, 20%);
font-size: 4/3em;
cursor: pointer;
@@ -315,35 +309,6 @@ body {
}
}
.menu-bg {
background: whiteSmoke top repeat-x;
}
div.unified-panel-body {
width: 100%;
height: 100%;
padding: 0px;
flex: 1;
overflow: auto;
}
div.unified-panel-body-background {
background: none repeat scroll 0 0 $panel-bg-color;
}
// Used for dragging panels, popup menus, to deal with capturing clicks in iframes
#dd-helper {
background: white;
opacity: 0;
z-index: 900; // Bootstrap elements start at 1000
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: none;
}
// Messages for message box, slightly different style
.panel-error-message,
@@ -379,10 +344,11 @@ div.unified-panel-body-background {
}
}
// Masthead
// ==== Masthead ====
#masthead {
padding: 0;
margin-bottom: 0px;
@extend .p-0;
@extend .mb-0;
background-color: $brand-dark;
height: $masthead-height;
.navbar-nav {
@@ -395,48 +361,47 @@ div.unified-panel-body-background {
align-items: center;
&.active {
background: darken($brand-dark, 10%);
.nav-link {
color: $white;
}
}
.nav-link {
cursor: pointer;
text-decoration: none;
color: $gray-300;
&:hover {
color: $dropdown-link-active-color;
color: $brand-toggle;
}
&.nav-icon {
font-size: 1.7em;
}
&.toggle {
color: $dropdown-link-active-color;
color: $brand-toggle;
}
}
.nav-note {
font-weight: bold;
font-size: 10px;
position: relative;
right: 5px;
top: 10px;
color: $dropdown-link-active-color;
width: 0px;
@extend .font-weight-bold;
@extend .position-relative;
right: 0.4rem;
top: 0.7rem;
font-size: 0.7rem;
color: $brand-toggle;
width: 0;
}
}
}
.navbar-brand {
position: absolute;
left: 0;
// top: 0;
@extend .position-absolute;
@extend .font-weight-bold;
left: 0.3rem;
top: 0;
font-family: verdana;
font-weight: bold;
font-size: 20px;
// line-height: 1;
color: $white;
// Override margin and padding due to shorter navbar height
padding: 5px 20px 12px;
margin-left: -15px;
font-size: 1.25rem;
color: $gray-300;
text-decoration: none;
.navbar-brand-image {
display: inline;
width: 26px;
// vertical-align: top;
margin-left: 0.35em;
border: none;
}
@@ -446,9 +411,10 @@ div.unified-panel-body-background {
}
}
// ==== Quota meter styles ====
.quota-meter-container {
position: absolute;
// top: 0;
right: 0;
height: 32px;
}
@@ -528,11 +494,11 @@ div.metadataFormBody div.metadataFormTitle {
div.metadataFormDisabled div.metadataFormTitle {
background: #eee;
border-color: $border-default-color;
border-color: $border-color;
}
div.metadataFormDisabled {
border-color: $border-default-color;
border-color: $border-color;
}
div.metadataHelpBody {
@@ -552,11 +518,11 @@ div.toolFormBody div.toolFormTitle {
div.toolFormDisabled div.toolFormTitle {
background: #f5f5f5;
border-color: $border-default-color;
border-color: $border-color;
}
div.toolFormDisabled {
border-color: $border-default-color;
border-color: $border-color;
}
div.toolHelp {
@@ -693,19 +659,6 @@ div.form-row-input label {
margin-bottom: 10px;
}
// Selects
select {
padding: 2px;
font-size: $font-size-base;
line-height: $line-height-base;
}
select,
input,
textarea {
font: inherit;
}
.form-row {
select,
textarea,
@@ -718,23 +671,6 @@ textarea {
display: block;
}
textarea,
input[type="text"],
input[type="password"] {
font-size: $font-size-base;
line-height: $line-height-base;
}
// Always style buttons and submits as bootstrap buttons
input[type="submit"],
button {
@extend .btn;
@extend .btn-secondary;
&.btn-primary {
@extend .btn-primary;
}
}
// Messages
.errormessagelarge,
@@ -792,8 +728,6 @@ button {
padding: 5px;
padding-left: 25px;
min-height: 15px;
//border: 1px solid $error_message_border;
//background-color: $error_message_bg;
background-image: url(../../images/error_small.png);
background-repeat: no-repeat;
background-position: 5px 6px;
@@ -923,55 +857,49 @@ div.debug {
}
// Data grid style
.grid-table {
width: 100%;
@extend .w-100;
}
.grid-header {
padding-bottom: 1em;
}
.grid-header h2 {
margin: 0;
margin-bottom: 0.5em;
@extend .mb-2;
}
.grid-header .title {
font-weight: bold;
@extend .font-weight-bold;
}
.grid {
padding-top: 1em;
@extend .pt-2;
@extend .w-100;
border-collapse: collapse;
width: 100%;
}
.grid tbody td {
line-height: $line-height-base;
border-top: solid #dddddd 1px;
border-bottom: solid #dddddd 1px;
padding: 5px;
@extend .border-top;
@extend .border-bottom;
@extend .p-1;
}
.grid tbody td:empty {
padding: 0;
@extend .p-0;
}
.grid thead tr {
height: 2em;
}
.grid thead th {
@extend .p-1;
@extend .border-top;
@extend .border-bottom;
line-height: $line-height-base;
background: $table-heading-bg;
color: contrast($table-heading-bg);
border-top: solid $table-border-color 1px;
border-bottom: solid $table-border-color 1px;
padding: 5px;
background: $brand-light;
color: $text-color;
text-align: left;
white-space: nowrap;
}
.grid tfoot td {
background-color: #f8f8f8;
border-top: solid #dddddd 1px;
border-bottom: solid #dddddd 1px;
padding: 5px;
@extend .border-top;
@extend .border-bottom;
@extend .p-1;
background-color: $brand-light;
}
.grid .current {
background-color: #eeeeff;
background-color: lighten($brand-success, 20%);
}
// Pulled out of grid base
@@ -1077,32 +1005,25 @@ ul.manage-table-actions li {
}
.state-progress {
border: 1px solid gray;
position: relative;
margin-top: 2px;
margin-bottom: 1px;
@extend .position-relative;
@extend .m-1;
border: $border-default;
.info {
color: $black;
background: $white;
color: $text-color;
background: $body-bg;
}
.new {
background: $state-default-bg;
}
.running {
background: $state-running-bg;
}
.ok {
background: $state-success-bg;
}
.note {
margin-left: 1em;
position: absolute;
@extend .position-absolute;
@extend .ml-2;
}
}
@@ -1170,6 +1091,7 @@ a.btn {
}
a.action-button {
color: $btn-default-color;
background: $btn-default-bg;
text-decoration: none;
}
.action-button > img {
@@ -1370,7 +1292,7 @@ div.permissionContainer {
.editable-text:hover {
cursor: text;
border: dotted $border-default-color 1px;
border: dotted $border-color 1px;
}
.icon-button.multiinput {
@@ -1445,11 +1367,13 @@ div.toolPanelLabel {
@extend .px-3;
font-weight: bold;
font-size: $h4-font-size;
color: $gray-600;
color: $text-muted;
text-transform: uppercase;
}
div.toolSectionTitle, div.toolTitle, div.toolTitleNoSection {
div.toolSectionTitle,
div.toolTitle,
div.toolTitleNoSection {
display: block;
.labels {
float: right;
@@ -1753,7 +1677,7 @@ div.toolSectionWrapper {
@extend .fa;
@extend .fa-circle;
@extend .mt-1;
color: $white;
color: $brand-light;
position: absolute;
z-index: 1500;
> icon {
@@ -1791,7 +1715,7 @@ div.toolSectionWrapper {
}
.mark-terminal {
cursor: pointer;
color: darken($border-default-color, 20%);
color: $text-muted;
&.mark-terminal-active {
color: $brand-success;
}
+4 -3
View File
@@ -1,4 +1,5 @@
.charts-client {
background: $body-bg;
.charts-buttons {
@extend .float-right;
position: absolute;
@@ -7,7 +8,7 @@
width: 170px;
top: 4px;
.btn {
background: white;
background: $body-bg;
color: $panel-header-text-color;
border: none;
.icon {
@@ -60,8 +61,8 @@
float: left;
width: 30%;
height: 100vh;
padding-bottom: 32px;
padding-top: 32px;
padding-bottom: 40px;
padding-top: 40px;
overflow: hidden;
display: flex;
flex-direction: column;
+5 -7
View File
@@ -4,7 +4,7 @@
// ---------------------------------------------------------------------------- dataset states
//.dataset .state-icon {
.state-icon {
@extend .fa-icon;
@extend .fa;
display: inline-block;
margin-right: 4px;
vertical-align: middle;
@@ -309,17 +309,15 @@
@extend %vertical-spacing;
.annotation {
@extend %info-section;
//white-space: pre-line;
white-space: pre-wrap;
overflow: auto;
}
// fake placeholder for editable text annotation
.annotation:empty:after {
position: relative;
top: -4px;
font-size: 10px;
font-style: italic;
color: grey;
@extend .position-relative;
@extend .font-italic;
@extend .h6;
color: $text-muted;
//TODO: move out for localization
content: "Click here to edit annotation";
}
+2 -2
View File
@@ -1,5 +1,5 @@
$frame-zindex: 1000;
$frame-default-border: $border-default-color;
$frame-default-border: $border-color;
.galaxy-frame {
.corner {
@@ -19,7 +19,7 @@ $frame-default-border: $border-default-color;
height: 100%;
width: 100%;
opacity: 0.5;
background: $brand-dark;
background: $brand-light;
overflow: auto;
}
+18 -28
View File
@@ -39,13 +39,13 @@
.annotation-display {
display: none;
margin-bottom: 8px;
color: $text-muted;
.prompt {
display: block;
margin: 0px;
padding: 0px;
font-size: 90%;
font-weight: normal;
color: #555;
}
.prompt:after {
content: ":";
@@ -66,7 +66,7 @@
top: -4px;
font-size: 10px;
font-style: italic;
color: grey;
color: $text-muted;
//TODO: move out for localization
content: "Click here to edit annotation";
}
@@ -77,13 +77,11 @@
width: 100%;
}
}
.list-pagination {
width: 100%;
text-align: center;
margin: 0;
border-top: 1px solid $btn-default-border;
button {
margin: 0;
padding: 0;
@@ -98,7 +96,6 @@
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
margin: 0;
padding: 0;
width: 48%;
@@ -107,7 +104,6 @@
box-shadow: none;
text-align: center;
text-align-last: center;
&:focus {
outline: none;
}
@@ -123,7 +119,7 @@
border-radius: 0px;
background: transparent;
&.pages {
color: grey;
color: $text-muted;
text-decoration: underline;
}
&:hover,
@@ -135,7 +131,6 @@
text-decoration: none;
}
}
&:hover > *:not([disabled]) {
color: $btn-default-color;
}
@@ -145,7 +140,7 @@
// the help text that appears above the drop target
.history-drop-target-help {
margin: 10px 10px 4px 10px;
color: grey;
color: $text-muted;
font-size: 80%;
font-style: italic;
text-align: center;
@@ -163,7 +158,7 @@
overflow-y: auto;
&:not(:empty) {
border-top: 1px solid $border-default-color;
border-top: 1px solid $border-color;
}
&:empty {
flex-grow: 0 !important;
@@ -183,7 +178,7 @@
width: 100%;
padding: 16px;
text-align: center;
color: grey;
color: $text-color;
}
.history-content.dataset,
@@ -363,7 +358,7 @@
$mph-column_width: 312px;
$mph-border_width: 1px;
$mph-column_gap: 8px;
$mph-header_height: 29px;
$mph-header_height: 40px;
$mph-footer_height: 0;
$mph-controls_height: 20px;
@@ -427,14 +422,15 @@
& > .header,
& > .footer {
width: 100%;
background-color: lightgrey;
.btn {
height: 21px;
}
background-color: $white;
}
& > .header {
min-height: $mph-header_height;
max-height: $mph-header_height;
.search-control {
width: 300px;
float: left;
}
// popover containing sorting and filters
.more-options {
.btn {
@@ -466,7 +462,6 @@
// the header/footers controls
.control-column {
margin-top: 4px;
.btn {
@extend .btn-sm;
}
@@ -477,9 +472,8 @@
.header-info {
display: inline-block;
padding: 2px 4px 2px 4px;
color: grey;
color: $text-muted;
}
&.control-column-right,
&.control-column-left {
margin-right: 8px;
@@ -512,7 +506,7 @@
.middle {
min-width: 100%;
margin: 0px 0px 0px 0px;
background-color: white;
background-color: $white;
padding: $mph-column_gap 0px;
}
@@ -524,7 +518,7 @@
&:first-child {
position: fixed;
z-index: 10;
background-color: white;
background-color: $white;
width: $mph-column_width + $mph-column_gap;
padding-left: $mph-column_gap;
@@ -544,7 +538,7 @@
.panel-controls {
width: 100%;
height: ($mph-controls_height + 4);
background-color: white;
background-color: $white;
.btn {
@extend .btn-sm;
@@ -557,20 +551,17 @@
}
.current-label {
display: inline-block;
color: grey;
color: $text-muted;
padding-left: 2px;
margin-top: 2px;
}
}
.history-panel {
width: 100%;
border: $mph-border_width solid grey;
border-radius: 3px 3px 0px 0px;
background-color: $panel-bg-color;
overflow: auto;
& > .controls .subtitle {
min-height: 15px;
margin: -4px 0px 8px;
@@ -578,7 +569,6 @@
content: ", ";
}
}
& > .controls .list-pagination {
// text a bit smaller for all
font-size: 90%;
@@ -597,7 +587,7 @@
transform-origin: left top 0;
margin-left: 16px;
white-space: nowrap;
color: grey;
color: $text-muted;
span {
// space btwn icon and text
margin-right: 8px;
+2 -2
View File
@@ -1,6 +1,6 @@
// ---------------------------------------------------------------------------- generic panel list items
.job.list-item {
border: 1px solid $border-default-color;
border: 1px solid $border-color;
.title-bar {
.subtitle {
@@ -45,7 +45,7 @@
.list-item {
border: 0px;
border-radius: 0px;
border-top: 1px solid $border-default-color;
border-top: 1px solid $border-color;
&:last-child {
}
}
+1 -1
View File
@@ -83,7 +83,7 @@
color: $brand-light;
}
tr.table-primary td a {
color: $brand-white;
color: $white;
}
a.dark {
color: white;
+7 -7
View File
@@ -13,11 +13,11 @@ $vertical-gap: 8px;
%help-text {
font-weight: normal;
color: #555;
color: $gray-700;
}
.list-item {
border: 1px solid $border-default-color;
border: 1px solid $border-color;
.vertical-spacing {
@extend %vertical-spacing;
@@ -51,7 +51,7 @@ $vertical-gap: 8px;
.help-text {
@extend %help-text;
font-weight: normal;
color: #555;
color: $gray-700;
}
.selector {
@@ -88,14 +88,14 @@ $vertical-gap: 8px;
text-decoration: underline;
}
.subtitle {
color: #777;
color: $text-muted;
font-size: 90%;
a {
color: inherit;
}
}
.state-description {
color: #777;
color: $text-muted;
font-size: 90%;
a {
color: inherit;
@@ -156,7 +156,7 @@ $vertical-gap: 8px;
}
}
.subtitle {
color: #777;
color: $text-muted;
font-size: 90%;
a {
color: inherit;
@@ -225,7 +225,7 @@ $vertical-gap: 8px;
background: white;
padding: 4px;
.list-items {
border: 1px solid $border-default-color;
border: 1px solid $border-color;
border-radius: 3px;
.list-item {
&:first-child {
+50 -3
View File
@@ -25,7 +25,7 @@ pre {
}
.btn-secondary {
border: 1px solid $border-default-color;
border: $border-default;
}
.popover .popover-body {
@@ -38,6 +38,13 @@ pre {
}
}
.dropdown-item,
.dropdown-header {
color: $text-color;
}
// Input fields
input[type="checkbox"],
input[type="radio"] {
margin-left: 0.5ex;
@@ -45,6 +52,35 @@ input[type="radio"] {
float: left;
}
select {
padding: 2px;
font-size: $font-size-base;
line-height: $line-height-base;
}
select,
input,
textarea {
font: inherit;
}
textarea,
input[type="text"],
input[type="password"] {
font-size: $font-size-base;
line-height: $line-height-base;
}
// Always style buttons and submits as bootstrap buttons
input[type="submit"],
button {
@extend .btn;
@extend .btn-secondary;
&.btn-primary {
@extend .btn-primary;
}
}
// Modal -- wider by default, scroll like Trello
.modal-dialog {
@@ -64,6 +100,14 @@ input[type="radio"] {
@extend .mb-2;
}
.tabbable {
.nav-tabs {
.nav-item {
cursor: pointer;
}
}
}
// Don't make blockquotes very large since they're often used in the toolform
// and this does not look nice to have a quoted portion of a tool's help
// documentation be rendered 150% larger.
@@ -86,7 +130,10 @@ pre.code {
}
// increase visibility of links within alert boxes
.alert-info, .alert-danger, .alert-warning, .alert-success {
.alert-info,
.alert-danger,
.alert-warning,
.alert-success {
a {
font-weight: bold;
}
@@ -96,6 +143,6 @@ pre.code {
.dropdown-header {
font-size: 1rem;
color: $brand-dark;
padding: .5rem 1.5rem .5rem 1rem;
padding: 0.5rem 1.5rem 0.5rem 1rem;
font-weight: bold;
}
+29 -21
View File
@@ -7,20 +7,26 @@ $brand-warning: #fe7f02;
$brand-danger: #e31a1e;
$brand-light: #f8f9fa;
$brand-dark: #2c3143;
$brand-white: #fff;
$brand-toggle: gold;
// Grays from Bootstrap 4 _variables.scss
$white: $brand-white;
// Text colors
$text-color: $brand-dark;
$text-muted: lighten($text-color, 10%);
$border-color: lighten($text-color, 60%);
// Light grays for backgrounds
$white: lighten($brand-light, 15%); // body background
$gray-100: $brand-light;
$gray-200: darken($gray-100, 5%); // welcome background
$gray-300: darken($gray-100, 10%);
$gray-400: darken($gray-100, 20%); // input borders
$gray-500: darken($gray-100, 40%);
$gray-600: darken($gray-100, 60%); // text-muted
$gray-700: darken($gray-100, 70%);
$gray-800: darken($gray-100, 80%);
$gray-900: darken($gray-100, 90%); // text-regular
$gray-200: darken($brand-light, 5%); // welcome background
$gray-300: darken($brand-light, 10%);
// Dark grays for foregrounds
$gray-900: $text-color;
$gray-800: $text-muted;
$gray-700: $text-muted;
$gray-600: $text-muted;
$gray-500: $text-muted;
$gray-400: $border-color;
$black: $brand-dark;
// These variables don't actually determine the colors in Bootstrap 4, instead
@@ -84,19 +90,22 @@ $h6-font-size: $font-size-base * 0.85; // default 1
// Increase contrast of dropdown items
$dropdown-link-hover-color: $brand-light;
$dropdown-link-hover-bg: $brand-primary;
$dropdown-link-active-color: $brand-toggle;
// Color of input elements
$input-color: $text-color;
$input-color-placeholder: $text-muted;
// Galaxy Variables (not used by bootstrap)
$body-bg: $white;
$text-color: $gray-900;
// Masthead
$masthead-height: 3rem;
// Side panels
$panel-bg-color: $gray-100;
$panel-bg-color: $brand-light;
$panel-text-color: $text-color;
$panel-header-text-color: $text-color;
$panel-bg-header-color: $panel-bg-color;
$panel-footer-bg-color: $panel-bg-color;
$panel-message-height: 2.5rem;
$panel-width: 18rem;
@@ -115,13 +124,12 @@ $layout-border: none;
$border-radius-base: 0.1875rem;
$border-radius-large: 0.3125rem;
$border-radius-extralarge: 1rem;
$border-default-color: $gray-400;
$border-default: 1px solid $border-default-color;
$border-default: 1px solid $border-color;
// Buttons
$btn-default-color: $text-color;
$btn-default-bg: $gray-200;
$btn-default-border: $border-default-color;
$btn-default-border: $border-color;
// Forms
$form-heading-bg: $panel-bg-color;
@@ -130,17 +138,17 @@ $form-border: darken($form-heading-bg, 20%);
// Table
$table-heading-bg: $panel-bg-color;
$table-bg-accent: darken($panel-bg-color, 5%);
$table-border-color: darken($table-heading-bg, 20%);
$table-border-color: $border-color;
// Additional state colors
$state-default-bg: $gray-200;
$state-default-border: $border-default-color;
$state-default-border: $border-color;
$state-paused-bg: lighten($brand-info, 50%);
$state-running-bg: lighten($brand-warning, 40%);
$state-running-border: $border-default-color;
$state-running-border: $border-color;
// Workflow editor
$workflow-editor-bg: $brand-white;
$workflow-editor-bg: $white;
$workflow-overview-bg: $gray-200;
// Margins
+4 -22
View File
@@ -46,6 +46,7 @@ $ui-margin-horizontal-large: $margin-v * 2;
.modal-backdrop {
z-index: -1;
opacity: 0.5;
background: $brand-light;
}
.modal-body {
position: static;
@@ -84,13 +85,6 @@ $ui-margin-horizontal-large: $margin-v * 2;
cursor: pointer;
}
// tabs
.tabbable {
.nav-item {
cursor: pointer;
}
}
// thumbnails
.ui-thumbnails {
.ui-thumbnails-item {
@@ -105,7 +99,7 @@ $ui-margin-horizontal-large: $margin-v * 2;
height: 86px;
font-size: 4em;
padding: 20px 26px;
color: lighten($brand-primary, 10%);
color: $text-color;
}
.ui-thumbnails-text {
word-wrap: break-word;
@@ -253,9 +247,9 @@ $ui-margin-horizontal-large: $margin-v * 2;
// popovers
.popover {
max-width: 700px;
max-width: 70%;
.popover-header {
color: $brand-white;
color: $brand-light;
background-color: $brand-primary;
i {
@extend .float-right;
@@ -422,9 +416,6 @@ $ui-margin-horizontal-large: $margin-v * 2;
-webkit-appearance: none;
-moz-border-radius: $border-radius-base;
line-height: 1.5rem;
.select2-chosen {
white-space: normal;
}
.select2-arrow {
display: none;
}
@@ -477,12 +468,3 @@ $ui-margin-horizontal-large: $margin-v * 2;
border: 2px solid $state-success-bg;
background: lighten($state-success-bg, 10%) !important;
}
// select2 adjustments
.select2-drop-active,
.select2-drop-above.select2-drop-active {
border: 1px solid $border-default-color;
.select2-search {
@extend .pt-1;
}
}
@@ -1,7 +1,6 @@
$icon-btn-size: 22px;
$icon-btn-margin: 2px;
$icon-btn-color: $btn-default-color;
$icon-btn-bg: $btn-default-bg;
$icon-btn-border: $btn-default-border;
@@ -153,8 +153,6 @@
margin-bottom: 17px;
overflow: hidden;
padding: 15px;
background: #f8f8f8;
&:not(.expanded) {
// chosen to match alert - dependent on line height and .alert padding
max-height: 49px;
@@ -1,5 +1,8 @@
.search-input {
@extend .mb-2;
::placeholder {
color: $text-muted;
}
.search-clear,
.search-loading {
@extend .mr-2;
@@ -8,7 +11,7 @@
float: right;
margin-top: -25px;
font-size: 1.4em;
color: $gray-500;
color: $text-muted;
}
.search-clear:hover {
color: $brand-info;
@@ -28,7 +31,7 @@
border: $border-default;
@include border-radius($border-radius-extralarge);
max-width: auto;
background: $brand-white;
background: $white;
}
.search-query:focus {
border-color: $brand-info;
+1
View File
@@ -39,6 +39,7 @@
.upload-input {
font-size: $font-size-base;
color: $text-color;
height: 20px;
line-height: 18px;
}
+11
View File
@@ -3505,6 +3505,17 @@
:Type: str
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``workflow_schedulers_config_file``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:Description:
Optional configuration file similar to `job_config_file` to
specify which Galaxy processes should schedule workflows.
:Default: ``config/workflow_schedulers_conf.xml``
:Type: str
~~~~~~~~~~~~~~~~~~~~~~~~
``cache_user_job_count``
~~~~~~~~~~~~~~~~~~~~~~~~
+2 -2
View File
@@ -242,9 +242,9 @@ previous section:
Galaxy sends files (e.g. dataset downloads) by opening the file and streaming it in chunks through the proxy server.
However, this ties up the Galaxy process, which can impact the performance of other operations (see [Production Server
Configuration](production.md) for a more in-depth explanation).
Configuration](production.md) for a more in-depth explanation).
Nginx can assume this task instead and, as an added benefit, speed up downloads. In addition, both the IGV genome browser and JBrowse tool (run within Galaxy) require support for the HTTP *Range* header, and this is only available if the proxy serves datasets.
Nginx can assume this task instead and, as an added benefit, speed up downloads. In addition, the Integrative Genomics Viewer (IGV), the Integrated Genome Browser (IGB), and the JBrowse tool (run within Galaxy) require support for the HTTP *Range* header, and this is only available if the proxy serves datasets.
This is accomplished through the use of the special `X-Accel-Redirect` header. Dataset security is maintained in this configuration because nginx will still check with Galaxy to ensure that the requesting user has permission to access the dataset before sending it.
To enable it, add the following to your Galaxy's `server {}` block:
+25 -11
View File
@@ -49,11 +49,11 @@ Two options are set in the sample `config/galaxy.yml` which should not be enable
During deployment, you may run into problems with failed jobs. By default, Galaxy removes files related to job execution. You can instruct Galaxy to keep files of failed jobs with: `cleanup_job: onsuccess`
### Switching to a database server
### Switching to PostgreSQL database server
The most important recommendation is to switch to an actual database server. By default, Galaxy will use [SQLite](http://www.sqlite.org/), which is a serverless simple file database engine. Since it's serverless, all of the database processing occurs in the Galaxy process itself. This has two downsides: it occupies the aforementioned GIL (meaning that the process is not free to do other tasks), and it is not nearly as efficient as a dedicated database server. There are other drawbacks, too. When load increases with multiple users, the risk of transactional locks also increases. Locks will cause (among other things) timeouts and job errors. If you start with SQLite and then later realize a need for a database server, you'll need to migrate your database or start over. Galaxy does not provide an internal method to migrate data from SQLite, and although free conversion tools are available on the web, this process is non-trivial.
For this reason, Galaxy also supports [PostgreSQL](https://www.postgresql.org/) and [MySQL](https://dev.mysql.com/). *PostgreSQL is much preferred since we've found it works better with our DB abstraction layer, [SQLAlchemy](https://www.sqlalchemy.org/).*
For this reason, Galaxy also supports PostgreSQL through our DB abstraction layer, [SQLAlchemy](https://www.sqlalchemy.org/).
To use an external database, you'll need to set one up. That process is outside the scope of this document, but is usually simple. For example, on Debian and Redhat-based Linux distributions, one may already be installed. If not, it should be an `apt-get install` or `yum install` away. On macOS, there are installers available from the [PostgreSQL](https://www.postgresql.org/) website.
@@ -61,29 +61,43 @@ Once installed, create a new database user and new database which the new user i
To configure Galaxy, set `database_connection` in Galaxy's config file, `config/galaxy.yml`. The syntax for a database URL is explained in the [SQLAlchemy documentation](https://docs.sqlalchemy.org/en/latest/core/engines.html).
Here follow two example database URLs with username and password:
An example database URL with username and password:
```
postgresql://username:password@localhost/mydatabase
mysql://username:password@localhost/mydatabase
```
It's worth noting that some platforms (for example, Debian/Ubuntu) store database sockets in a directory other than the database engine's default. If you're connecting to a database server on the same host as the Galaxy server and the socket is in a non-standard location, you'll need to use these custom arguments (these are the defaults for Debian/Ubuntu, change as necessary for your installation):
It's worth noting that some platforms (for example, Debian/Ubuntu) store database sockets in a directory other than the database engine's default. If you're connecting to a database server on the same host as the Galaxy server and the socket is in a non-standard location, you'll need to use these custom arguments (this is the default for Debian/Ubuntu, change as necessary for your installation):
```
postgresql:///mydatabase?host=/var/run/postgresql
mysql:///mydatabase?unix_socket=/var/run/mysqld/mysqld.sock
```
For more hints on available options for the database URL, see the [SQLAlchemy documentation](https://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls).
If you are using [MySQL](https://dev.mysql.com/) and encounter the "MySQL server has gone away" error, please note the `database_engine_option_pool_recycle` option in `config/galaxy.yml`. If this does not solve your problem, see [this post](http://gmod.827538.n3.nabble.com/template/NamlServlet.jtp?macro=print_post&node=2354941) on the Galaxy Development [mailing list](https://galaxyproject.org/mailing-lists/).
#### Deprecated MySQL support
If you are using [MySQL](https://dev.mysql.com/), please make sure the database output is in UTF-8, otherwise you may encounter Python TypeErrors.
Postgres database backend is more used and better tested, please use it if you can.
If you are using [MySQL](https://dev.mysql.com/) with [MyISAM](https://dev.mysql.com/doc/refman/8.0/en/myisam-storage-engine.html) table engine, when Galaxy is in multiprocess configuration, workflow steps will [get executed out of order](http://dev.list.galaxyproject.org/Job-execution-order-mixed-up-tt4662488.html) and fail. Use [InnoDB](https://dev.mysql.com/doc/refman/8.0/en/innodb-storage-engine.html) engine instead or switch to [PostgreSQL](https://www.postgresql.org/).
In the past Galaxy supported [MySQL](https://dev.mysql.com/) but currently there are nontrivial problems associated with it so expect amount of troubleshooting when using it.
Connection string example:
```
mysql://username:password@localhost/mydatabase
```
Connecting using socket on a Debian/Ubuntu system:
```
mysql:///mydatabase?unix_socket=/var/run/mysqld/mysqld.sock
```
Some tips when using MySQL:
* If you encounter the "MySQL server has gone away" error, please note the `database_engine_option_pool_recycle` option in `config/galaxy.yml`. If this does not solve your problem, see [this post](http://gmod.827538.n3.nabble.com/template/NamlServlet.jtp?macro=print_post&node=2354941) on the Galaxy Development [mailing list](https://galaxyproject.org/mailing-lists/).
* Please make sure the database output is in UTF-8, otherwise you may encounter Python TypeErrors.
* If you are using [MySQL](https://dev.mysql.com/) with [MyISAM](https://dev.mysql.com/doc/refman/8.0/en/myisam-storage-engine.html) table engine, when Galaxy is in multiprocess configuration, workflow steps will [get executed out of order](http://dev.list.galaxyproject.org/Job-execution-order-mixed-up-tt4662488.html) and fail. Use [InnoDB](https://dev.mysql.com/doc/refman/8.0/en/innodb-storage-engine.html) engine instead or switch to [PostgreSQL](https://www.postgresql.org/).
### Using a proxy server
+10 -10
View File
@@ -156,34 +156,34 @@ class BaseAppConfiguration(object):
return os.path.join(self.sample_config_dir, path)
def _parse_config_file_options(self, defaults, listify_defaults, config_kwargs):
for var, defaults in defaults.items():
for var, values in defaults.items():
if config_kwargs.get(var, None) is not None:
path = config_kwargs.get(var)
setattr(self, var + '_set', True)
else:
for default in defaults:
if os.path.exists(resolve_path(default, self.root)):
path = default
for value in values:
if os.path.exists(resolve_path(value, self.root)):
path = value
break
else:
path = defaults[-1]
path = values[-1]
setattr(self, var + '_set', False)
setattr(self, var, resolve_path(path, self.root))
for var, defaults in listify_defaults.items():
for var, values in listify_defaults.items():
paths = []
if config_kwargs.get(var, None) is not None:
paths = listify(config_kwargs.get(var))
else:
for default in defaults:
for path in listify(default):
for value in values:
for path in listify(value):
if not os.path.exists(resolve_path(path, self.root)):
break
else:
paths = listify(default)
paths = listify(value)
break
else:
paths = listify(defaults[-1])
paths = listify(values[-1])
setattr(self, var, [resolve_path(x, self.root) for x in paths])
@@ -1701,6 +1701,10 @@ galaxy:
# tags to do the mapping).
#workflow_resource_params_mapper: config/workflow_resource_mapper_conf.yml
# Optional configuration file similar to `job_config_file` to specify
# which Galaxy processes should schedule workflows.
#workflow_schedulers_config_file: config/workflow_schedulers_conf.xml
# If using job concurrency limits (configured in job_config_file),
# several extra database queries must be performed to determine the
# number of jobs a user has dispatched to a given destination. By
+1 -1
View File
@@ -136,7 +136,7 @@ class ConditionalDependencies(object):
def check_drmaa(self):
return ("galaxy.jobs.runners.drmaa:DRMAAJobRunner" in self.job_runners or
"galaxy.jobs.runners.slurm:SlurmJobRunner" in self.job_runners or
"galaxy.jobs.runners.drmaauniva:DRMAAUnivaJobRunner" in self.job_runners)
"galaxy.jobs.runners.univa:UnivaJobRunner" in self.job_runners)
def check_galaxycloudrunner(self):
return ("galaxycloudrunner.rules" in self.job_rule_modules)
+2 -2
View File
@@ -231,9 +231,9 @@ class PulsarJobRunner(AsynchronousJobRunner):
if "job_metrics_config_file" not in conf:
conf["job_metrics"] = self.app.job_metrics
if "staging_directory" not in conf:
conf["staging_directory"] = "database/pulsar_staging"
conf["staging_directory"] = os.path.join(self.app.config.data_dir, "pulsar_staging")
if "persistence_directory" not in conf:
conf["persistence_directory"] = "database/pulsar_persisted_data"
conf["persistence_directory"] = os.path.join(self.app.config.data_dir, "pulsar_persisted_data")
if "galaxy_home" not in conf:
conf["galaxy_home"] = galaxy_directory()
self.pulsar_app = pulsar.core.PulsarApp(**conf)
@@ -2,7 +2,7 @@ if [ -n "$SLURM_JOB_ID" ]; then
GALAXY_MEMORY_MB=`scontrol -do show job "$SLURM_JOB_ID" | sed 's/.*\( \|^\)Mem=\([0-9][0-9]*\)\( \|$\).*/\2/p;d'` 2>memory_statement.log
fi
if [ -n "$SGE_HGR_h_vmem" ]; then
GALAXY_MEMORY_MB_PER_SLOT=`echo "$SGE_HGR_h_vmem" | sed 's/G$/ * 1024/' | bc | cut -d"." -f1` 2>memory_statement.log
GALAXY_MEMORY_MB=`echo "$SGE_HGR_h_vmem" | sed 's/G$/ * 1024/' | bc | cut -d"." -f1` 2>memory_statement.log
fi
if [ -z "$GALAXY_MEMORY_MB_PER_SLOT" -a -n "$GALAXY_MEMORY_MB" ]; then
+1 -1
View File
@@ -1064,7 +1064,7 @@ class ToolTestDescription(object):
self.exception = processed_test_dict.get("exception", None)
self.output_collections = map(TestCollectionOutputDef.from_dict, processed_test_dict.get("output_collections", []))
self.command_line = processed_test_dict.get("command", None)
self.command_line = processed_test_dict.get("command_line", None)
self.stdout = processed_test_dict.get("stdout", None)
self.stderr = processed_test_dict.get("stderr", None)
self.expect_exit_code = processed_test_dict.get("expect_exit_code", None)
+7
View File
@@ -1245,6 +1245,13 @@ class Tool(Dictifiable):
self.__ensure_help()
return self.__help_by_page
@property
def raw_help(self):
# may return rst (or Markdown in the future)
tool_source = self.__help_source
help_text = tool_source.parse_help()
return help_text
def __ensure_help(self):
with HELP_UNINITIALIZED:
if self.__help is HELP_UNINITIALIZED:
+24 -10
View File
@@ -1,5 +1,6 @@
import logging
import os
from collections import defaultdict
from threading import (
local,
Lock,
@@ -141,6 +142,7 @@ class ToolShedRepositoryCache(object):
def add_local_repository(self, repository):
self.cache.repositories.append(repository)
self.cache.repos_by_tuple[(repository.tool_shed, repository.owner, repository.name)].append(repository)
@property
def tool_shed_repositories(self):
@@ -155,8 +157,21 @@ class ToolShedRepositoryCache(object):
repositories = self.cache.repositories
return repositories
@property
def tool_shed_repos_by_tuple(self):
try:
return self.cache.repos_by_tuple
except AttributeError:
self.rebuild()
return self.cache.repos_by_tuple
def rebuild(self):
self.cache.repositories = self.app.install_model.context.current.query(self.app.install_model.ToolShedRepository).all()
repositories = self.app.install_model.context.current.query(self.app.install_model.ToolShedRepository).all()
self.cache.repositories = repositories
repos_by_tuple = defaultdict(list)
for repository in repositories:
repos_by_tuple[(repository.tool_shed, repository.owner, repository.name)].append(repository)
self.cache.repos_by_tuple = repos_by_tuple
def get_installed_repository(self, tool_shed=None, name=None, owner=None, installed_changeset_revision=None, changeset_revision=None, repository_id=None):
try:
@@ -182,12 +197,11 @@ class ToolShedRepositoryCache(object):
return repos[0]
else:
return None
repos = [repo for repo in self.tool_shed_repositories if repo.tool_shed == tool_shed and repo.owner == owner and repo.name == name]
if installed_changeset_revision:
repos = [repo for repo in repos if repo.installed_changeset_revision == installed_changeset_revision]
if changeset_revision:
repos = [repo for repo in repos if repo.changeset_revision == changeset_revision]
if repos:
return repos[0]
else:
return None
repos = self.tool_shed_repos_by_tuple[(tool_shed, owner, name)]
for repo in repos:
if installed_changeset_revision and repo.installed_changeset_revision != installed_changeset_revision:
continue
if changeset_revision and repo.changeset_revision != changeset_revision:
continue
return repo
return None
+9 -10
View File
@@ -6,7 +6,6 @@ import logging
import re
import tempfile
from bleach import clean
from whoosh import analysis
from whoosh.analysis import StandardAnalyzer
from whoosh.fields import (
@@ -104,15 +103,15 @@ class ToolBoxSearch(object):
add_doc_kwds['stub'] = to_unicode(id)
if tool.labels:
add_doc_kwds['labels'] = to_unicode(" ".join(tool.labels))
if index_help and tool.help:
try:
raw_html = tool.help.render(host_url="", static_path="")
cleantext = clean(raw_html, tags=[''], strip=True).replace('\n', ' ')
add_doc_kwds['help'] = to_unicode(cleantext)
except Exception:
# Don't fail to build index just because a help message
# won't render.
pass
if index_help:
raw_help = tool.raw_help
if raw_help:
try:
add_doc_kwds['help'] = to_unicode(raw_help)
except Exception:
# Don't fail to build index just because a help message
# won't render.
pass
return add_doc_kwds
def search(self, q, tool_name_boost, tool_section_boost, tool_description_boost, tool_label_boost, tool_stub_boost, tool_help_boost, tool_search_limit, tool_enable_ngram_search, tool_ngram_minsize, tool_ngram_maxsize):
+2 -2
View File
@@ -173,7 +173,7 @@ def _expand_yield_statements(macro_def, expand_el):
if macro_def_el.tag == "yield":
for target in expand_el_children:
i += 1
macro_def.insert(i, deepcopy(target))
macro_def.insert(i, target)
macro_def.remove(macro_def_el)
continue
@@ -269,7 +269,7 @@ def _xml_replace(query, targets, parent_map):
current_index = matching_index
for target in targets:
current_index += 1
parent_el.insert(current_index, deepcopy(target))
parent_el.insert(current_index, target)
parent_el.remove(query)
+9 -2
View File
@@ -72,8 +72,8 @@ class VisualizationPlugin(ServesTemplatesPluginMixin):
self.config = config
base_url = context.get('base_url', '')
self.base_url = '/'.join([base_url, self.name]) if base_url else self.name
self.static_path = os.path.join(self.path.replace('./config', './static'), 'static')
if os.path.exists(os.path.join(self.static_path, 'logo.png')):
self.static_path = self._get_static_path(self.path)
if self.static_path and os.path.exists(os.path.join(self.static_path, 'logo.png')):
self.config['logo'] = '/'.join([self.static_path, 'logo.png'])
template_cache_dir = context.get('template_cache_dir', None)
additional_template_paths = context.get('additional_template_paths', [])
@@ -127,6 +127,13 @@ class VisualizationPlugin(ServesTemplatesPluginMixin):
return url_for(controller='visualization', action=self.name)
return url_for('visualization_plugin', visualization_name=self.name)
def _get_static_path(self, path):
if '/config/' in path:
match = path.split('/config/')[-1]
return os.path.join('./static', match, 'static')
else:
log.debug('Visualization has no static path: %s.' % path)
def _get_saved_visualization_config(self, visualization, revision=None, **kwargs):
"""
Return the config of a saved visualization and revision.
@@ -189,6 +189,8 @@ class VisualizationsRegistry(object):
if config is not None:
plugin = self._build_plugin(plugin_name, plugin_path, config)
return plugin
else:
raise ObjectNotFound('Visualization XML not found: %s.' % config_file)
def _build_plugin(self, plugin_name, plugin_path, config):
# TODO: as builder not factory
+5 -5
View File
@@ -436,14 +436,14 @@ class GalaxyWebTransaction(base.DefaultWebTransaction,
# Decode the cookie value to get the session_key
try:
session_key = self.security.decode_guid(secure_id)
if session_key:
# Retrieve the galaxy_session id via the unique session_key
galaxy_session = self.sa_session.query(self.app.model.GalaxySession) \
.filter(and_(self.app.model.GalaxySession.table.c.session_key == session_key,
self.app.model.GalaxySession.table.c.is_valid == true())).options(joinedload("user")).first()
except Exception:
# We'll end up creating a new galaxy_session
session_key = None
if session_key:
# Retrieve the galaxy_session id via the unique session_key
galaxy_session = self.sa_session.query(self.app.model.GalaxySession) \
.filter(and_(self.app.model.GalaxySession.table.c.session_key == session_key,
self.app.model.GalaxySession.table.c.is_valid == true())).options(joinedload("user")).first()
# If remote user is in use it can invalidate the session and in some
# cases won't have a cookie set above, so we need to to check some
# things now.
@@ -98,7 +98,7 @@ class FolderContentsController(BaseAPIController, UsesLibraryMixin, UsesLibraryM
# Access rights are checked on the dataset level, not on the ld or ldda level to maintain consistency
dataset = content_item.library_dataset_dataset_association.dataset
is_unrestricted = trans.app.security_agent.dataset_is_public(dataset)
if trans.user and trans.app.security_agent.dataset_is_private_to_user(trans, dataset):
if not is_unrestricted and trans.user and trans.app.security_agent.dataset_is_private_to_user(trans, dataset):
is_private = True
else:
is_private = False
+2 -4
View File
@@ -552,7 +552,8 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn
if (trans.security.decode_id(id) in entries):
trans.get_user().stored_workflow_menu_entries.remove(entries[trans.security.decode_id(id)])
# set tags
trans.app.tag_handler.set_tags_from_list(user=trans.user, item=stored_workflow, new_tags_list=workflow_dict.get('tags', []))
if 'tags' in workflow_dict:
trans.app.tag_handler.set_tags_from_list(user=trans.user, item=stored_workflow, new_tags_list=workflow_dict['tags'])
if 'steps' in workflow_dict:
try:
@@ -565,9 +566,6 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn
)
except workflows.MissingToolsException:
raise exceptions.MessageException("This workflow contains missing tools. It cannot be saved until they have been removed from the workflow or installed.")
else:
# We only adjusted tags and menu entry
return payload
else:
message = "Updating workflow requires dictionary containing 'workflow' attribute with new JSON description."
raise exceptions.RequestParameterInvalidException(message)
@@ -2612,6 +2612,14 @@ mapping:
descriptions (additional types of mappings via these files could be implemented but
haven't yet - for instance using workflow tags to do the mapping).
workflow_schedulers_config_file:
type: str
default: config/workflow_schedulers_conf.xml
required: false
desc: |
Optional configuration file similar to `job_config_file` to specify
which Galaxy processes should schedule workflows.
cache_user_job_count:
type: bool
default: false
@@ -1612,7 +1612,8 @@ class AdminToolshed(AdminGalaxy):
tool_shed=tool_shed_url,
name=name,
owner=owner,
changeset_revision=changeset_revision)
changeset_revision=changeset_revision,
refresh=True)
original_metadata_dict = repository.metadata
original_repository_dependencies_dict = original_metadata_dict.get('repository_dependencies', {})
original_repository_dependencies = original_repository_dependencies_dict.get('repository_dependencies', [])
+1 -12
View File
@@ -594,7 +594,7 @@ class InputModule(WorkflowModule):
progress.set_outputs_for_input(invocation_step, step_outputs)
def recover_mapping(self, invocation_step, progress):
progress.set_outputs_for_input(invocation_step)
progress.set_outputs_for_input(invocation_step, already_persisted=True)
class InputDataModule(InputModule):
@@ -1305,17 +1305,6 @@ class ToolModule(WorkflowModule):
return complete
def recover_mapping(self, invocation_step, progress):
outputs = {}
for output_dataset_assoc in invocation_step.output_datasets:
outputs[output_dataset_assoc.output_name] = output_dataset_assoc.dataset
for output_dataset_collection_assoc in invocation_step.output_dataset_collections:
outputs[output_dataset_collection_assoc.output_name] = output_dataset_collection_assoc.dataset_collection
progress.set_step_outputs(invocation_step, outputs)
def _effective_post_job_actions(self, step):
effective_post_job_actions = step.post_job_actions[:]
for key, value in self.runtime_post_job_actions.items():
+2 -2
View File
@@ -405,7 +405,7 @@ class WorkflowProgress(object):
else:
return step_outputs[output_name]
def set_outputs_for_input(self, invocation_step, outputs=None):
def set_outputs_for_input(self, invocation_step, outputs=None, already_persisted=False):
step = invocation_step.workflow_step
if outputs is None:
@@ -420,7 +420,7 @@ class WorkflowProgress(object):
elif step_id in self.inputs_by_step_id:
outputs['output'] = self.inputs_by_step_id[step_id]
self.set_step_outputs(invocation_step, outputs)
self.set_step_outputs(invocation_step, outputs, already_persisted=already_persisted)
def set_step_outputs(self, invocation_step, outputs, already_persisted=False):
step = invocation_step.workflow_step
+1
View File
@@ -0,0 +1 @@
../../../test/unit/tool_util/test_tool_linters.py
+58 -58
View File
@@ -105,15 +105,15 @@ def main():
cutoff_time = datetime.utcnow() - timedelta(days=args.days)
now = strftime("%Y-%m-%d %H:%M:%S")
print("##########################################")
print("\n# %s - Handling stuff older than %i days" % (now, args.days))
log.info("##########################################")
log.info("\n# %s - Handling stuff older than %i days" % (now, args.days))
if args.info_only:
print("# Displaying info only ( --info_only )\n")
log.info("# Displaying info only ( --info_only )\n")
elif args.remove_from_disk:
print("Datasets will be removed from disk.\n")
log.info("Datasets will be removed from disk.\n")
else:
print("Datasets will NOT be removed from disk.\n")
log.info("Datasets will NOT be removed from disk.\n")
if args.delete_userless_histories:
delete_userless_histories(app, cutoff_time, info_only=args.info_only, force_retry=args.force_retry)
@@ -149,15 +149,15 @@ def delete_userless_histories(app, cutoff_time, info_only=False, force_retry=Fal
app.model.History.table.c.update_time < cutoff_time))
for history in histories:
if not info_only:
print("Deleting history id ", history.id)
log.info("Deleting history id ", history.id)
history.deleted = True
app.sa_session.add(history)
app.sa_session.flush()
history_count += 1
stop = time.time()
print("Deleted %d histories" % history_count)
print("Elapsed time: ", stop - start)
print("##########################################")
log.info("Deleted %d histories" % history_count)
log.info("Elapsed time: ", stop - start)
log.info("##########################################")
def purge_histories(app, cutoff_time, remove_from_disk, info_only=False, force_retry=False):
@@ -180,7 +180,7 @@ def purge_histories(app, cutoff_time, remove_from_disk, info_only=False, force_r
app.model.History.table.c.update_time < cutoff_time)) \
.options(eagerload('datasets'))
for history in histories:
print("### Processing history id %d (%s)" % (history.id, unicodify(history.name)))
log.info("### Processing history id %d (%s)" % (history.id, unicodify(history.name)))
for dataset_assoc in history.datasets:
_purge_dataset_instance(dataset_assoc, app, remove_from_disk, info_only=info_only) # mark a DatasetInstance as deleted, clear associated files, and mark the Dataset as deleted if it is deletable
if not info_only:
@@ -189,17 +189,17 @@ def purge_histories(app, cutoff_time, remove_from_disk, info_only=False, force_r
# if we should ever delete info like this from the db though, so commented out for now...
# for dhp in history.default_permissions:
# dhp.delete()
print("Purging history id ", history.id)
log.info("Purging history id ", history.id)
history.purged = True
app.sa_session.add(history)
app.sa_session.flush()
else:
print("History id %d will be purged (without 'info_only' mode)" % history.id)
log.info("History id %d will be purged (without 'info_only' mode)" % history.id)
history_count += 1
stop = time.time()
print('Purged %d histories.' % history_count)
print("Elapsed time: ", stop - start)
print("##########################################")
log.info('Purged %d histories.' % history_count)
log.info("Elapsed time: ", stop - start)
log.info("##########################################")
def purge_libraries(app, cutoff_time, remove_from_disk, info_only=False, force_retry=False):
@@ -222,15 +222,15 @@ def purge_libraries(app, cutoff_time, remove_from_disk, info_only=False, force_r
for library in libraries:
_purge_folder(library.root_folder, app, remove_from_disk, info_only=info_only)
if not info_only:
print("Purging library id ", library.id)
log.info("Purging library id ", library.id)
library.purged = True
app.sa_session.add(library)
app.sa_session.flush()
library_count += 1
stop = time.time()
print('# Purged %d libraries .' % library_count)
print("Elapsed time: ", stop - start)
print("##########################################")
log.info('# Purged %d libraries .' % library_count)
log.info("Elapsed time: ", stop - start)
log.info("##########################################")
def purge_folders(app, cutoff_time, remove_from_disk, info_only=False, force_retry=False):
@@ -254,9 +254,9 @@ def purge_folders(app, cutoff_time, remove_from_disk, info_only=False, force_ret
_purge_folder(folder, app, remove_from_disk, info_only=info_only)
folder_count += 1
stop = time.time()
print('# Purged %d folders.' % folder_count)
print("Elapsed time: ", stop - start)
print("##########################################")
log.info('# Purged %d folders.' % folder_count)
log.info("Elapsed time: ", stop - start)
log.info("##########################################")
def delete_datasets(app, cutoff_time, remove_from_disk, info_only=False, force_retry=False):
@@ -301,7 +301,7 @@ def delete_datasets(app, cutoff_time, remove_from_disk, info_only=False, force_r
library_dataset_ids = [row.id for row in library_dataset_ids_query.execute()]
dataset_ids = []
for library_dataset_id in library_dataset_ids:
print("######### Processing LibraryDataset id:", library_dataset_id)
log.info("######### Processing LibraryDataset id:", library_dataset_id)
# Get the LibraryDataset and the current LibraryDatasetDatasetAssociation objects
ld = app.sa_session.query(app.model.LibraryDataset).get(library_dataset_id)
ldda = ld.library_dataset_dataset_association
@@ -311,16 +311,16 @@ def delete_datasets(app, cutoff_time, remove_from_disk, info_only=False, force_r
if not ldda.deleted:
ldda.deleted = True
app.sa_session.add(ldda)
print("Marked associated LibraryDatasetDatasetAssociation id %d as deleted" % ldda.id)
log.info("Marked associated LibraryDatasetDatasetAssociation id %d as deleted" % ldda.id)
for expired_ldda in ld.expired_datasets:
if not expired_ldda.deleted:
expired_ldda.deleted = True
app.sa_session.add(expired_ldda)
print("Marked associated expired LibraryDatasetDatasetAssociation id %d as deleted" % ldda.id)
log.info("Marked associated expired LibraryDatasetDatasetAssociation id %d as deleted" % ldda.id)
# Mark the LibraryDataset as purged
ld.purged = True
app.sa_session.add(ld)
print("Marked LibraryDataset id %d as purged" % ld.id)
log.info("Marked LibraryDataset id %d as purged" % ld.id)
app.sa_session.flush()
# Add all datasets associated with Histories to our list
dataset_ids.extend([row.id for row in history_dataset_ids_query.execute()])
@@ -330,9 +330,9 @@ def delete_datasets(app, cutoff_time, remove_from_disk, info_only=False, force_r
if dataset.id in skip:
continue
skip.append(dataset.id)
print("######### Processing dataset id:", dataset_id)
log.info("######### Processing dataset id:", dataset_id)
if not _dataset_is_deletable(dataset):
print("Dataset is not deletable (shared between multiple histories/libraries, at least one is not deleted)")
log.info("Dataset is not deletable (shared between multiple histories/libraries, at least one is not deleted)")
continue
deleted_dataset_count += 1
for dataset_instance in dataset.history_associations + dataset.library_associations:
@@ -340,9 +340,9 @@ def delete_datasets(app, cutoff_time, remove_from_disk, info_only=False, force_r
_purge_dataset_instance(dataset_instance, app, remove_from_disk, info_only=info_only, is_deletable=True)
deleted_instance_count += 1
stop = time.time()
print("Examined %d datasets, marked %d datasets and %d dataset instances (HDA) as deleted" % (len(skip), deleted_dataset_count, deleted_instance_count))
print("Total elapsed time: ", stop - start)
print("##########################################")
log.info("Examined %d datasets, marked %d datasets and %d dataset instances (HDA) as deleted" % (len(skip), deleted_dataset_count, deleted_instance_count))
log.info("Total elapsed time: ", stop - start)
log.info("##########################################")
def purge_datasets(app, cutoff_time, remove_from_disk, info_only=False, force_retry=False):
@@ -371,18 +371,18 @@ def purge_datasets(app, cutoff_time, remove_from_disk, info_only=False, force_re
except Exception:
pass
stop = time.time()
print('Purged %d datasets' % dataset_count)
log.info('Purged %d datasets' % dataset_count)
if remove_from_disk:
print('Freed disk space: ', disk_space)
print("Elapsed time: ", stop - start)
print("##########################################")
log.info('Freed disk space: ', disk_space)
log.info("Elapsed time: ", stop - start)
log.info("##########################################")
def _purge_dataset_instance(dataset_instance, app, remove_from_disk, info_only=False, is_deletable=False):
# A dataset_instance is either a HDA or an LDDA. Purging a dataset instance marks the instance as deleted,
# and marks the associated dataset as deleted if it is not associated with another active DatsetInstance.
if not info_only:
print("Marking as deleted: %s id %d (for dataset id %d)" %
log.info("Marking as deleted: %s id %d (for dataset id %d)" %
(dataset_instance.__class__.__name__, dataset_instance.id, dataset_instance.dataset.id))
dataset_instance.mark_deleted()
dataset_instance.clear_associated_files()
@@ -390,16 +390,16 @@ def _purge_dataset_instance(dataset_instance, app, remove_from_disk, info_only=F
app.sa_session.flush()
app.sa_session.refresh(dataset_instance.dataset)
else:
print("%s id %d (for dataset id %d) will be marked as deleted (without 'info_only' mode)" %
log.info("%s id %d (for dataset id %d) will be marked as deleted (without 'info_only' mode)" %
(dataset_instance.__class__.__name__, dataset_instance.id, dataset_instance.dataset.id))
if is_deletable or _dataset_is_deletable(dataset_instance.dataset):
# Calling methods may have already checked _dataset_is_deletable, if so, is_deletable should be True
_delete_dataset(dataset_instance.dataset, app, remove_from_disk, info_only=info_only, is_deletable=is_deletable)
else:
if info_only:
print("Not deleting dataset ", dataset_instance.dataset.id, " (will be possibly deleted without 'info_only' mode)")
log.info("Not deleting dataset ", dataset_instance.dataset.id, " (will be possibly deleted without 'info_only' mode)")
else:
print("Not deleting dataset %d (shared between multiple histories/libraries, at least one not deleted)" % dataset_instance.dataset.id)
log.info("Not deleting dataset %d (shared between multiple histories/libraries, at least one not deleted)" % dataset_instance.dataset.id)
def _dataset_is_deletable(dataset):
@@ -411,7 +411,7 @@ def _delete_dataset(dataset, app, remove_from_disk, info_only=False, is_deletabl
# Marks a base dataset as deleted, hdas/lddas associated with dataset can no longer be undeleted.
# Metadata files attached to associated dataset Instances is removed now.
if not is_deletable and not _dataset_is_deletable(dataset):
print("This Dataset (%i) is not deletable, associated Metadata Files will not be removed.\n" % (dataset.id))
log.info("This Dataset (%i) is not deletable, associated Metadata Files will not be removed.\n" % (dataset.id))
else:
# Mark all associated MetadataFiles as deleted and purged and remove them from disk
metadata_files = []
@@ -429,29 +429,29 @@ def _delete_dataset(dataset, app, remove_from_disk, info_only=False, is_deletabl
if remove_from_disk:
op_description = op_description + " and purged from disk"
if info_only:
print("The following metadata files attached to associations of Dataset '%s' will be %s (without 'info_only' mode):" % (dataset.id, op_description))
log.info("The following metadata files attached to associations of Dataset '%s' will be %s (without 'info_only' mode):" % (dataset.id, op_description))
else:
print("The following metadata files attached to associations of Dataset '%s' have been %s:" % (dataset.id, op_description))
log.info("The following metadata files attached to associations of Dataset '%s' have been %s:" % (dataset.id, op_description))
if remove_from_disk:
try:
print("Removing disk file ", metadata_file.file_name)
log.info("Removing disk file ", metadata_file.file_name)
os.unlink(metadata_file.file_name)
except Exception as e:
print("Error, exception: %s caught attempting to purge metadata file %s\n" % (str(e), metadata_file.file_name))
log.info("Error, exception: %s caught attempting to purge metadata file %s\n" % (str(e), metadata_file.file_name))
metadata_file.purged = True
app.sa_session.add(metadata_file)
app.sa_session.flush()
metadata_file.deleted = True
app.sa_session.add(metadata_file)
app.sa_session.flush()
print("%s" % metadata_file.file_name)
log.info("%s" % metadata_file.file_name)
if not info_only:
print("Deleting dataset id", dataset.id)
log.info("Deleting dataset id", dataset.id)
dataset.deleted = True
app.sa_session.add(dataset)
app.sa_session.flush()
else:
print("Dataset %i will be deleted (without 'info_only' mode)" % (dataset.id))
log.info("Dataset %i will be deleted (without 'info_only' mode)" % (dataset.id))
def _purge_dataset(app, dataset, remove_from_disk, info_only=False):
@@ -462,7 +462,7 @@ def _purge_dataset(app, dataset, remove_from_disk, info_only=False):
# Remove files from disk and update the database
if remove_from_disk:
# TODO: should permissions on the dataset be deleted here?
print("Removing disk, file ", dataset.file_name)
log.info("Removing disk, file ", dataset.file_name)
os.unlink(dataset.file_name)
# Remove associated extra files from disk if they exist
if dataset.extra_files_path and os.path.exists(dataset.extra_files_path):
@@ -476,32 +476,32 @@ def _purge_dataset(app, dataset, remove_from_disk, info_only=False):
for user in usage_users:
user.adjust_total_disk_usage(-dataset.get_total_size())
app.sa_session.add(user)
print("Purging dataset id", dataset.id)
log.info("Purging dataset id", dataset.id)
dataset.purged = True
app.sa_session.add(dataset)
app.sa_session.flush()
else:
print("Dataset %i will be purged (without 'info_only' mode)" % (dataset.id))
log.info("Dataset %i will be purged (without 'info_only' mode)" % (dataset.id))
else:
print("This dataset (%i) is not purgable, the file (%s) will not be removed.\n" % (dataset.id, dataset.file_name))
log.info("This dataset (%i) is not purgable, the file (%s) will not be removed.\n" % (dataset.id, dataset.file_name))
except OSError as exc:
print("Error, dataset file has already been removed: %s" % str(exc))
print("Purging dataset id", dataset.id)
log.error("Error, dataset file has already been removed: %s" % str(exc))
log.error("Purging dataset id", dataset.id)
dataset.purged = True
app.sa_session.add(dataset)
app.sa_session.flush()
except ObjectNotFound:
print("Dataset %i cannot be found in the object store" % dataset.id)
log.error("Dataset %i cannot be found in the object store" % dataset.id)
except Exception as exc:
print("Error attempting to purge data file: ", dataset.file_name, " error: ", str(exc))
log.error("Error attempting to purge data file: ", dataset.file_name, " error: ", str(exc))
else:
print("Error: '%s' has not previously been deleted, so it cannot be purged\n" % dataset.file_name)
log.info("Error: '%s' has not previously been deleted, so it cannot be purged\n" % dataset.file_name)
def _purge_folder(folder, app, remove_from_disk, info_only=False):
"""Purges a folder and its contents, recursively"""
for ld in folder.datasets:
print("Deleting library dataset id ", ld.id)
log.info("Deleting library dataset id ", ld.id)
ld.deleted = True
for ldda in [ld.library_dataset_dataset_association] + ld.expired_datasets:
_purge_dataset_instance(ldda, app, remove_from_disk, info_only=info_only) # mark a DatasetInstance as deleted, clear associated files, and mark the Dataset as deleted if it is deletable
@@ -509,7 +509,7 @@ def _purge_folder(folder, app, remove_from_disk, info_only=False):
_purge_folder(sub_folder, app, remove_from_disk, info_only=info_only)
if not info_only:
# TODO: should the folder permissions be deleted here?
print("Purging folder id ", folder.id)
log.info("Purging folder id ", folder.id)
folder.purged = True
app.sa_session.add(folder)
app.sa_session.flush()
+1 -1
View File
@@ -13,7 +13,7 @@ from galaxy.model.orm.scripts import get_config
def invoke_migrate_main():
# Migrate has its own args, so cannot use argparse
config = get_config(sys.argv, use_argparse=False)
config = get_config(sys.argv, use_argparse=False, cwd=os.getcwd())
db_url = config['db_url']
repo = config['repo']
+14
View File
@@ -343,6 +343,20 @@ class WorkflowsApiTestCase(BaseWorkflowsApiTestCase):
# Make sure the positions have been updated.
map(tweak_step, updated_workflow_content['steps'].items())
def test_update_tags(self):
workflow_object = self.workflow_populator.load_workflow(name="test_import")
upload_response = self.__test_upload(workflow=workflow_object)
workflow = upload_response.json()
workflow['tags'] = ['a_tag', 'b_tag']
update_response = self._update_workflow(workflow['id'], workflow).json()
assert update_response['tags'] == ['a_tag', 'b_tag']
del workflow['tags']
update_response = self._update_workflow(workflow['id'], workflow).json()
assert update_response['tags'] == ['a_tag', 'b_tag']
workflow['tags'] = []
update_response = self._update_workflow(workflow['id'], workflow).json()
assert update_response['tags'] == []
def test_update_no_tool_id(self):
workflow_object = self.workflow_populator.load_workflow(name="test_import")
upload_response = self.__test_upload(workflow=workflow_object)
+41
View File
@@ -0,0 +1,41 @@
import xml.etree.ElementTree as ET
import pytest
from galaxy.tool_util.lint import LintContext
from galaxy.tool_util.linters import inputs
NO_SECTIONS_XML = """
<tool name="BWA Mapper" id="bwa" version="1.0.1" is_multi_byte="true" display_interface="true" require_login="true" hidden="true">
<description>The BWA Mapper</description>
<version_command interpreter="python">bwa.py --version</version_command>
</tool>
"""
NO_WHEN_IN_CONDITIONAL_XML = """
<tool name="BWA Mapper" id="bwa" version="1.0.1" is_multi_byte="true" display_interface="true" require_login="true" hidden="true">
<description>The BWA Mapper</description>
<version_command interpreter="python">bwa.py --version</version_command>
<inputs>
<conditional name="labels">
<param name="label_select" type="select" label="Points to label">
<option value="none" selected="True">None</option>
</param>
</conditional>
</inputs>
</tool>
"""
TESTS = [
(NO_SECTIONS_XML, inputs.lint_inputs, lambda x: 'Found no input parameters.' in x.warn_messages),
(NO_WHEN_IN_CONDITIONAL_XML, inputs.lint_inputs, lambda x: 'No <when /> block found for select option \'none\' inside conditional \'labels\'' in x.warn_messages),
]
@pytest.mark.parametrize('tool_xml,lint_func,assert_func', TESTS, ids=['Lint no sections', 'lint no when'])
def test_tool_xml(tool_xml, lint_func, assert_func):
lint_ctx = LintContext('all')
tree = ET.ElementTree(element=ET.fromstring(tool_xml))
lint_ctx.lint(name="test_lint", lint_func=lint_func, lint_target=tree)
assert assert_func(lint_ctx)
+43 -2
View File
@@ -62,8 +62,19 @@ def test_loader():
<expand macro="inputs">
<input name="first_input" />
</expand>
<expand macro="inputs">
<input name="second_input" />
</expand>
<expand macro="inputs">
<input name="third_input" />
</expand>
<macros>
<macro name="inputs">
<expand macro="foo">
<yield />
</expand>
</macro>
<macro name="foo">
<inputs>
<yield />
</inputs>
@@ -71,7 +82,9 @@ def test_loader():
</macros>
</tool>''')
xml = tool_dir.load()
assert xml.find("inputs").find("input").get("name") == "first_input"
assert xml.findall("inputs")[0].find("input").get("name") == "first_input"
assert xml.findall("inputs")[1].find("input").get("name") == "second_input"
assert xml.findall("inputs")[2].find("input").get("name") == "third_input"
# Test recursive macro applications.
with TestToolDirectory() as tool_dir:
@@ -227,6 +240,8 @@ def test_loader():
tool_dir.write('''
<tool>
<expand macro="inputs" bar="hello" />
<expand macro="inputs" bar="my awesome" />
<expand macro="inputs" bar="doggo" />
<macros>
<xml name="inputs" tokens="bar" token_quote="$$">
<inputs type="the type is $$BAR$$" />
@@ -236,8 +251,10 @@ def test_loader():
''')
xml = tool_dir.load()
input_els = xml.findall("inputs")
assert len(input_els) == 1
assert len(input_els) == 3
assert input_els[0].attrib["type"] == "the type is hello"
assert input_els[1].attrib["type"] == "the type is my awesome"
assert input_els[2].attrib["type"] == "the type is doggo"
# Test macros XML macros with @ expansions in text
with TestToolDirectory() as tool_dir:
@@ -259,3 +276,27 @@ def test_loader():
assert input_els[0].text == "hello"
assert input_els[1].text == "world"
assert input_els[2].text == "the_default"
# Test macros XML macros with @ expansions and recursive
with TestToolDirectory() as tool_dir:
tool_dir.write('''
<tool>
<expand macro="inputs" foo="hello" />
<expand macro="inputs" foo="world" />
<expand macro="inputs" />
<macros>
<xml name="inputs" token_foo="the_default">
<expand macro="real_inputs"><cow>@FOO@</cow></expand>
</xml>
<xml name="real_inputs">
<inputs><yield /></inputs>
</xml>
</macros>
</tool>
''')
xml = tool_dir.load()
input_els = xml.findall("inputs")
assert len(input_els) == 3
assert input_els[0].find("cow").text == "hello"
assert input_els[1].find("cow").text == "world"
assert input_els[2].find("cow").text == "the_default"