mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-29 17:42:12 +08:00
Merge remote-tracking branch 'upstream/dev' into v-slot-linting
This commit is contained in:
@@ -5,16 +5,16 @@
|
||||
{{ message }}
|
||||
</b-alert>
|
||||
<p>
|
||||
You are logged in as <strong>{{ email }}</strong
|
||||
You are logged in as <strong id="user-preferences-current-email">{{ email }}</strong
|
||||
>.
|
||||
</p>
|
||||
<b-row class="ml-3 mb-1" v-for="(link, index) in activeLinks" :key="index">
|
||||
<i :class="['pref-icon pt-1 fa fa-lg', link.icon]" />
|
||||
<div class="pref-content pr-1">
|
||||
<a v-if="link.onclick" @click="link.onclick" href="javascript:void(0)"
|
||||
<a :id="link.id" v-if="link.onclick" @click="link.onclick" href="javascript:void(0)"
|
||||
><b>{{ link.title }}</b></a
|
||||
>
|
||||
<a v-else :href="`${baseUrl}/${link.action}`"
|
||||
<a :id="link.id" v-else :href="`${baseUrl}/${link.action}`"
|
||||
><b>{{ link.title }}</b></a
|
||||
>
|
||||
<div class="form-text text-muted">
|
||||
@@ -22,6 +22,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</b-row>
|
||||
<b-row class="ml-3 mb-1">
|
||||
<i class="pref-icon pt-1 fa fa-lg fa-plus-square-o" />
|
||||
<div class="pref-content pr-1">
|
||||
<a @click="toggleNotifications" href="javascript:void(0)"><b>Enable notifications</b></a>
|
||||
<div class="form-text text-muted">
|
||||
Allow push and tab notifcations on job completion. To disable, revoke the site notification
|
||||
privilege in your browser.
|
||||
</div>
|
||||
</div>
|
||||
</b-row>
|
||||
<p class="mt-2">
|
||||
You are using <strong>{{ diskUsage }}</strong> of disk space in this Galaxy instance.
|
||||
<span v-html="quotaUsageString"></span>
|
||||
@@ -107,6 +117,18 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleNotifications() {
|
||||
Notification.requestPermission().then(function(permission) {
|
||||
//If the user accepts, let's create a notification
|
||||
if (permission === "granted") {
|
||||
new Notification("Notifications enabled", {
|
||||
icon: "static/favicon.ico"
|
||||
});
|
||||
} else {
|
||||
alert("Notifications disabled, please re-enable through browser settings.");
|
||||
}
|
||||
});
|
||||
},
|
||||
openManageCustomBuilds() {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
Galaxy.page.router.push(`${getAppRoot()}custom_builds`);
|
||||
|
||||
@@ -7,6 +7,7 @@ export const getUserPreferencesModel = () => {
|
||||
return {
|
||||
information: {
|
||||
title: _l("Manage Information"),
|
||||
id: "edit-preferences-information",
|
||||
description: "Edit your email, addresses and custom parameters or change your public name.",
|
||||
url: `api/users/${Galaxy.user.id}/information/inputs`,
|
||||
icon: "fa-user",
|
||||
@@ -15,6 +16,7 @@ export const getUserPreferencesModel = () => {
|
||||
},
|
||||
password: {
|
||||
title: _l("Change Password"),
|
||||
id: "edit-preferences-password",
|
||||
description: _l("Allows you to change your login credentials."),
|
||||
icon: "fa-unlock-alt",
|
||||
url: `api/users/${Galaxy.user.id}/password/inputs`,
|
||||
@@ -24,6 +26,7 @@ export const getUserPreferencesModel = () => {
|
||||
},
|
||||
communication: {
|
||||
title: _l("Change Communication Settings"),
|
||||
id: "edit-preferences-communication",
|
||||
description: _l("Enable or disable the communication feature to chat with other users."),
|
||||
url: `api/users/${Galaxy.user.id}/communication/inputs`,
|
||||
icon: "fa-comments-o",
|
||||
@@ -32,6 +35,7 @@ export const getUserPreferencesModel = () => {
|
||||
},
|
||||
permissions: {
|
||||
title: _l("Set Dataset Permissions for new Histories"),
|
||||
id: "edit-preferences-permissions",
|
||||
description:
|
||||
"Grant others default access to newly created histories. Changes made here will only affect histories created after these settings have been stored.",
|
||||
url: `api/users/${Galaxy.user.id}/permissions/inputs`,
|
||||
@@ -41,11 +45,13 @@ export const getUserPreferencesModel = () => {
|
||||
},
|
||||
make_data_private: {
|
||||
title: _l("Make all Data Private"),
|
||||
id: "edit-preferences-make-data-private",
|
||||
description: _l("Click here to make all data private."),
|
||||
icon: "fa-lock"
|
||||
},
|
||||
api_key: {
|
||||
title: _l("Manage API Key"),
|
||||
id: "edit-preferences-api-key",
|
||||
description: _l("Access your current API key or create a new one."),
|
||||
url: `api/users/${Galaxy.user.id}/api_key/inputs`,
|
||||
icon: "fa-key",
|
||||
@@ -53,6 +59,7 @@ export const getUserPreferencesModel = () => {
|
||||
submit_icon: "fa-check"
|
||||
},
|
||||
cloud_auth: {
|
||||
id: "edit-preferences-cloud-auth",
|
||||
title: _l("Manage Cloud Authorization"),
|
||||
description: _l("Add or modify the configuration that grants Galaxy to access your cloud-based resources."),
|
||||
icon: "fa-cloud",
|
||||
@@ -61,6 +68,7 @@ export const getUserPreferencesModel = () => {
|
||||
},
|
||||
toolbox_filters: {
|
||||
title: _l("Manage Toolbox Filters"),
|
||||
id: "edit-preferences-toolbox-filters",
|
||||
description: _l("Customize your Toolbox by displaying or omitting sets of Tools."),
|
||||
url: `api/users/${Galaxy.user.id}/toolbox_filters/inputs`,
|
||||
icon: "fa-filter",
|
||||
@@ -75,6 +83,7 @@ export const getUserPreferencesModel = () => {
|
||||
},
|
||||
logout: {
|
||||
title: _l("Sign Out"),
|
||||
id: "edit-preferences-custom-builds",
|
||||
description: _l("Click here to sign out of all sessions."),
|
||||
icon: "fa-sign-out",
|
||||
shouldRender: !!Galaxy.session_csrf_token
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="toolFormTitle unselectable clearfix">
|
||||
<i :class="iconClass" />
|
||||
<span class="node-title">{{ title }}</span>
|
||||
<span class="sr-only"> Node</span>
|
||||
<div class="node-header unselectable clearfix">
|
||||
<div class="header float-left">
|
||||
<i :class="iconClass" />
|
||||
<span class="node-title">{{ title }}</span>
|
||||
</div>
|
||||
<div class="buttons float-right">
|
||||
<a
|
||||
v-if="canClone"
|
||||
@@ -22,7 +23,7 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toolFormBody">
|
||||
<div class="node-body">
|
||||
<div>
|
||||
<loading-span message="Loading details" />
|
||||
</div>
|
||||
|
||||
@@ -13,22 +13,20 @@
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-card id="data-table-card" flush>
|
||||
<b-container v-slot:header>
|
||||
<b-row align-v="center">
|
||||
<b-col cols="auto">
|
||||
<b-button
|
||||
@click="reload()"
|
||||
v-b-tooltip.hover
|
||||
:title="'Reload ' + this.dataTable['name'] + ' tool data table'"
|
||||
>
|
||||
<span class="fa fa-refresh" />
|
||||
</b-button>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b>{{ this.dataTable["name"] }}</b>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
<template v-slot:header>
|
||||
<b-container>
|
||||
<b-row align-v="center">
|
||||
<b-col cols="auto">
|
||||
<b-button @click="reload()" v-b-tooltip.hover :title="buttonLabel">
|
||||
<span class="fa fa-refresh" />
|
||||
</b-button>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b>{{ dataTableName }}</b>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-container>
|
||||
</template>
|
||||
<b-table
|
||||
:fields="fields(this.dataTable['columns'])"
|
||||
:items="dataTable['data']"
|
||||
@@ -68,6 +66,12 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dataTableName() {
|
||||
return this.dataTable && this.dataTable.name ? this.dataTable.name : "null";
|
||||
},
|
||||
buttonLabel() {
|
||||
return `Reload ${this.dataTableName} tool data table`;
|
||||
},
|
||||
breadcrumbItems() {
|
||||
return [
|
||||
{
|
||||
@@ -75,7 +79,7 @@ export default {
|
||||
to: "/"
|
||||
},
|
||||
{
|
||||
text: this.dataTable["name"]
|
||||
text: this.dataTableName
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -88,7 +92,7 @@ export default {
|
||||
},
|
||||
reload() {
|
||||
axios
|
||||
.get(`${getAppRoot()}data_manager/reload_tool_data_tables?table_name=${this.dataTable["name"]}`)
|
||||
.get(`${getAppRoot()}data_manager/reload_tool_data_tables?table_name=${this.dataTableName}`)
|
||||
.then(response => {
|
||||
if (response.data.dataTable) {
|
||||
this.dataTable = response.data.dataTable;
|
||||
|
||||
@@ -267,7 +267,6 @@ const Collection = Backbone.Collection.extend({
|
||||
cls: "loggedout-only",
|
||||
tooltip: _l("Login"),
|
||||
url: "login",
|
||||
target: "galaxy_main",
|
||||
noscratchbook: true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import { getAppRoot } from "onload/loadConfig";
|
||||
import STATES from "mvc/dataset/states";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import _l from "utils/localization";
|
||||
import TAB_UPDATES from "mvc/user/tab-updates";
|
||||
|
||||
var logNamespace = "dataset";
|
||||
var hiddenupdates = 1;
|
||||
//==============================================================================
|
||||
var searchableMixin = BASE_MVC.SearchableModelMixin;
|
||||
/** @class base model for any DatasetAssociation (HDAs, LDDAs, DatasetCollectionDAs).
|
||||
@@ -99,6 +101,24 @@ var DatasetAssociation = Backbone.Model.extend(BASE_MVC.LoggableMixin).extend(
|
||||
this.log(`${this} has changed state:`, currModel, newState);
|
||||
if (this.inReadyState()) {
|
||||
this.trigger("state:ready", currModel, newState, this.previous("state"));
|
||||
if (newState != "discarded") {
|
||||
if (newState === "ok") {
|
||||
new Notification(`Job complete: ${this.get("name")}`, {
|
||||
icon: "static/favicon.ico"
|
||||
});
|
||||
if (TAB_UPDATES.is_hidden()) {
|
||||
TAB_UPDATES.hidden_count(hiddenupdates);
|
||||
hiddenupdates++;
|
||||
}
|
||||
} else if (newState == "error") {
|
||||
new Notification(`Job failure: ${this.get("name")}`, {
|
||||
icon: "static/erricon.ico"
|
||||
});
|
||||
if (TAB_UPDATES.is_hidden() && Notification.permission == "granted") {
|
||||
TAB_UPDATES.change_favicon("static/erricon.ico");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// the download url (currently) relies on having a correct file extension
|
||||
@@ -349,6 +369,14 @@ var DatasetAssociationCollection = Backbone.Collection.extend(BASE_MVC.LoggableM
|
||||
}
|
||||
);
|
||||
|
||||
window.addEventListener("focus", function() {
|
||||
const testing = document.getElementById("tabicon");
|
||||
if (testing && testing.href.search("erricon") != -1) {
|
||||
TAB_UPDATES.change_favicon("static/favicon.ico");
|
||||
}
|
||||
document.title = "Galaxy";
|
||||
hiddenupdates = 1;
|
||||
});
|
||||
//==============================================================================
|
||||
export default {
|
||||
DatasetAssociation: DatasetAssociation,
|
||||
|
||||
@@ -177,6 +177,7 @@ export default Backbone.View.extend({
|
||||
this.$title_text.show().text(this.model.get("label"));
|
||||
this.$collapsible.hide();
|
||||
}
|
||||
this.$field.attr("data-label", this.model.get("label"));
|
||||
},
|
||||
|
||||
_template: function() {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
//Allow change tab on event=====================================================
|
||||
function change_favicon(img) {
|
||||
var favicon = document.createElement("link");
|
||||
favicon.id = "tabicon";
|
||||
favicon.setAttribute("rel", "shortcut icon");
|
||||
var head = document.querySelector("head");
|
||||
head.appendChild(favicon);
|
||||
favicon.setAttribute("type", "image/png");
|
||||
favicon.setAttribute("href", img);
|
||||
}
|
||||
|
||||
//Check if browser is hidden-aware==============================================
|
||||
function hidden_permit() {
|
||||
var browsers = ["webkit", "moz", "ms", "o"];
|
||||
if ("hidden" in document) {
|
||||
return "hidden";
|
||||
}
|
||||
for (var i = 0; i < browsers.length; i++) {
|
||||
if (browsers[i] + "Hidden" in document) {
|
||||
return browsers[i] + "Hidden";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//Find out if browser tab is hidden=============================================
|
||||
function is_hidden() {
|
||||
var state = hidden_permit();
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
return document[state];
|
||||
}
|
||||
|
||||
//Generate new tab message to display on update in window=======================
|
||||
function hidden_count(current) {
|
||||
if (is_hidden()) {
|
||||
if (current === 1) {
|
||||
document.title = "Galaxy (1 job completed)";
|
||||
} else {
|
||||
document.title = "Galaxy (" + current + " jobs completed)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
change_favicon: change_favicon,
|
||||
hidden_permit: hidden_permit,
|
||||
is_hidden: is_hidden,
|
||||
hidden_count: hidden_count
|
||||
};
|
||||
@@ -1,19 +1,35 @@
|
||||
import $ from "jquery";
|
||||
|
||||
import * as d3 from "d3";
|
||||
import { Toast } from "ui/toast";
|
||||
|
||||
function Connector(manager, handle1, handle2) {
|
||||
this.manager = manager;
|
||||
this.canvas = null;
|
||||
this.dragging = false;
|
||||
this.inner_color = "#FFFFFF";
|
||||
this.outer_color = "#25537b";
|
||||
if (handle1 && handle2) {
|
||||
this.connect(handle1, handle2);
|
||||
// Rendering parameters
|
||||
const zIndex = 1000;
|
||||
const cpFactor = 10;
|
||||
const canvasExtra = 100;
|
||||
const handleMarginX = 8;
|
||||
const handleMarginY = 5;
|
||||
const ribbonMargin = 4;
|
||||
const ribbonInnerSingle = 4;
|
||||
const ribbonOuterSingle = 6;
|
||||
const ribbonInnerMultiple = 1;
|
||||
const ribbonOuterMultiple = 3;
|
||||
|
||||
class Connector {
|
||||
constructor(manager = {}, handle1 = null, handle2 = null) {
|
||||
this.manager = manager;
|
||||
this.dragging = false;
|
||||
this.innerClass = "ribbon-inner";
|
||||
this.canvas = document.createElement("div");
|
||||
this.canvas.style.position = "absolute";
|
||||
const container = document.getElementById("canvas-container");
|
||||
container.appendChild(this.canvas);
|
||||
this.svg = d3.select(this.canvas).append("svg");
|
||||
this.svg.attr("class", "ribbon");
|
||||
if (handle1 && handle2) {
|
||||
this.connect(handle1, handle2);
|
||||
}
|
||||
}
|
||||
}
|
||||
$.extend(Connector.prototype, {
|
||||
connect: function(t1, t2) {
|
||||
connect(t1, t2) {
|
||||
this.handle1 = t1;
|
||||
if (this.handle1) {
|
||||
this.handle1.connect(this);
|
||||
@@ -22,38 +38,42 @@ $.extend(Connector.prototype, {
|
||||
if (this.handle2) {
|
||||
this.handle2.connect(this);
|
||||
}
|
||||
},
|
||||
destroy: function() {
|
||||
}
|
||||
destroy() {
|
||||
if (this.handle1) {
|
||||
this.handle1.disconnect(this);
|
||||
}
|
||||
if (this.handle2) {
|
||||
this.handle2.disconnect(this);
|
||||
}
|
||||
$(this.canvas).remove();
|
||||
},
|
||||
destroyIfInvalid: function(warn) {
|
||||
this.canvas.remove();
|
||||
}
|
||||
destroyIfInvalid(warn) {
|
||||
if (this.handle1 && this.handle2 && !this.handle2.attachable(this.handle1).canAccept) {
|
||||
if (warn) {
|
||||
Toast.warning("Destroying a connection because collection type has changed.");
|
||||
}
|
||||
this.destroy();
|
||||
}
|
||||
},
|
||||
redraw: function() {
|
||||
}
|
||||
dropStart(canAccept) {
|
||||
if (canAccept) {
|
||||
this.innerClass = "ribbon-inner-valid";
|
||||
} else {
|
||||
this.innerClass = "ribbon-inner-invalid";
|
||||
}
|
||||
}
|
||||
dropEnd() {
|
||||
this.innerClass = "ribbon-inner";
|
||||
}
|
||||
redraw() {
|
||||
// Identify handles
|
||||
const handle1 = this.handle1;
|
||||
const handle2 = this.handle2;
|
||||
const startRibbon = handle1 && handle1.isMappedOver();
|
||||
const endRibbon = handle2 && handle2.isMappedOver();
|
||||
const canvasClass = `${startRibbon ? "start-ribbon" : ""} ${endRibbon ? "end-ribbon" : ""}`;
|
||||
var canvas_container = $("#canvas-container");
|
||||
var canvasZoom = this.manager.canvasZoom;
|
||||
if (!this.canvas) {
|
||||
this.canvas = document.createElement("canvas");
|
||||
canvas_container.append($(this.canvas));
|
||||
if (this.dragging) {
|
||||
this.canvas.style.zIndex = "300";
|
||||
}
|
||||
const canvas_container = $("#canvas-container");
|
||||
const canvasZoom = this.manager.canvasZoom;
|
||||
if (this.dragging) {
|
||||
this.canvas.style.zIndex = zIndex;
|
||||
}
|
||||
this.canvas.setAttribute(
|
||||
"handle1-id",
|
||||
@@ -63,66 +83,75 @@ $.extend(Connector.prototype, {
|
||||
"handle2-id",
|
||||
handle2 && handle2.element.getAttribute ? handle2.element.getAttribute("id") : ""
|
||||
);
|
||||
var relativeLeft = e => ($(e).offset().left - canvas_container.offset().left) / canvasZoom;
|
||||
var relativeTop = e => ($(e).offset().top - canvas_container.offset().top) / canvasZoom;
|
||||
const relativeLeft = e => ($(e).offset().left - canvas_container.offset().left) / canvasZoom;
|
||||
const relativeTop = e => ($(e).offset().top - canvas_container.offset().top) / canvasZoom;
|
||||
if (!handle1 || !handle2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the position of each handle
|
||||
var start_x = relativeLeft(handle1.element) + 5;
|
||||
var start_y = relativeTop(handle1.element) + 5;
|
||||
var end_x = relativeLeft(handle2.element) + 5;
|
||||
var end_y = relativeTop(handle2.element) + 5;
|
||||
let start_x = relativeLeft(handle1.element) + handleMarginX;
|
||||
let start_y = relativeTop(handle1.element) + handleMarginY;
|
||||
let end_x = relativeLeft(handle2.element) + handleMarginX;
|
||||
let end_y = relativeTop(handle2.element) + handleMarginY;
|
||||
|
||||
// Calculate canvas area
|
||||
var canvas_extra = 100;
|
||||
var canvas_min_x = Math.min(start_x, end_x);
|
||||
var canvas_max_x = Math.max(start_x, end_x);
|
||||
var canvas_min_y = Math.min(start_y, end_y);
|
||||
var canvas_max_y = Math.max(start_y, end_y);
|
||||
var cp_shift = Math.min(Math.max(Math.abs(canvas_max_y - canvas_min_y) / 2, 100), 300);
|
||||
var canvas_left = canvas_min_x - canvas_extra;
|
||||
var canvas_top = canvas_min_y - canvas_extra;
|
||||
var canvas_width = canvas_max_x - canvas_min_x + 2 * canvas_extra;
|
||||
var canvas_height = canvas_max_y - canvas_min_y + 2 * canvas_extra;
|
||||
const canvas_min_x = Math.min(start_x, end_x);
|
||||
const canvas_max_x = Math.max(start_x, end_x);
|
||||
const canvas_min_y = Math.min(start_y, end_y);
|
||||
const canvas_max_y = Math.max(start_y, end_y);
|
||||
const canvas_left = canvas_min_x - canvasExtra;
|
||||
const canvas_top = canvas_min_y - canvasExtra;
|
||||
const canvas_width = canvas_max_x - canvas_min_x + 2 * canvasExtra;
|
||||
const canvas_height = canvas_max_y - canvas_min_y + 2 * canvasExtra;
|
||||
|
||||
// Place the canvas
|
||||
this.canvas.style.left = `${canvas_left}px`;
|
||||
this.canvas.style.top = `${canvas_top}px`;
|
||||
this.canvas.setAttribute("width", canvas_width);
|
||||
this.canvas.setAttribute("height", canvas_height);
|
||||
this.canvas.setAttribute("class", canvasClass);
|
||||
|
||||
// Resize the svg
|
||||
this.svg.style("width", `${canvas_width}px`);
|
||||
this.svg.style("height", `${canvas_height}px`);
|
||||
|
||||
// Adjust points to be relative to the canvas
|
||||
start_x -= canvas_left;
|
||||
start_y -= canvas_top;
|
||||
end_x -= canvas_left;
|
||||
end_y -= canvas_top;
|
||||
|
||||
// Draw the line
|
||||
// Determine line shift
|
||||
const cp_shift = Math.min(Math.max(Math.abs(canvas_max_y - canvas_min_y) / 2, cpFactor), 3 * cpFactor);
|
||||
|
||||
var start_offsets = null;
|
||||
var end_offsets = null;
|
||||
var num_offsets = 1;
|
||||
// Draw ribbons
|
||||
this.drawRibbon(handle1, handle2, cp_shift, start_x, start_y, end_x, end_y);
|
||||
}
|
||||
drawRibbon(handle1, handle2, cp_shift, start_x, start_y, end_x, end_y) {
|
||||
// Check ribbon type
|
||||
const startRibbon = handle1 && handle1.isMappedOver();
|
||||
const endRibbon = handle2 && handle2.isMappedOver();
|
||||
|
||||
// Draw the line
|
||||
let start_offsets = [0];
|
||||
let end_offsets = [0];
|
||||
const offsets = [-2 * ribbonMargin, -ribbonMargin, 0, ribbonMargin, 2 * ribbonMargin];
|
||||
let num_offsets = 1;
|
||||
if (startRibbon) {
|
||||
start_offsets = [-6, -3, 0, 3, 6];
|
||||
num_offsets = 5;
|
||||
} else {
|
||||
start_offsets = [0];
|
||||
start_offsets = offsets;
|
||||
num_offsets = offsets.length;
|
||||
}
|
||||
if (endRibbon) {
|
||||
end_offsets = [-6, -3, 0, 3, 6];
|
||||
num_offsets = 5;
|
||||
} else {
|
||||
end_offsets = [0];
|
||||
end_offsets = offsets;
|
||||
num_offsets = offsets.length;
|
||||
}
|
||||
var connector = this;
|
||||
for (var i = 0; i < num_offsets; i++) {
|
||||
var inner_width = 5;
|
||||
var outer_width = 7;
|
||||
this.svg.selectAll("*").remove();
|
||||
for (let i = 0; i < num_offsets; i++) {
|
||||
let inner_width = ribbonInnerSingle;
|
||||
let outer_width = ribbonOuterSingle;
|
||||
if (start_offsets.length > 1 || end_offsets.length > 1) {
|
||||
// We have a multi-run, using many lines, make them small.
|
||||
inner_width = 1;
|
||||
outer_width = 3;
|
||||
inner_width = ribbonInnerMultiple;
|
||||
outer_width = ribbonOuterMultiple;
|
||||
}
|
||||
connector.draw_outlined_curve(
|
||||
this.drawCurve(
|
||||
start_x,
|
||||
start_y,
|
||||
end_x,
|
||||
@@ -134,49 +163,37 @@ $.extend(Connector.prototype, {
|
||||
end_offsets[i % end_offsets.length]
|
||||
);
|
||||
}
|
||||
},
|
||||
draw_outlined_curve: function(
|
||||
start_x,
|
||||
start_y,
|
||||
end_x,
|
||||
end_y,
|
||||
cp_shift,
|
||||
inner_width,
|
||||
outer_width,
|
||||
offset_start,
|
||||
offset_end
|
||||
) {
|
||||
var c = this.canvas.getContext("2d");
|
||||
}
|
||||
drawCurve(start_x, start_y, end_x, end_y, cp_shift, inner_width, outer_width, offset_start, offset_end) {
|
||||
offset_start = offset_start || 0;
|
||||
offset_end = offset_end || 0;
|
||||
c.lineCap = "round";
|
||||
c.strokeStyle = this.outer_color;
|
||||
c.lineWidth = outer_width;
|
||||
c.beginPath();
|
||||
c.moveTo(start_x, start_y + offset_start);
|
||||
c.bezierCurveTo(
|
||||
start_x + cp_shift,
|
||||
start_y + offset_start,
|
||||
end_x - cp_shift,
|
||||
end_y + offset_end,
|
||||
end_x,
|
||||
end_y + offset_end
|
||||
);
|
||||
c.stroke();
|
||||
// Inner line
|
||||
c.strokeStyle = this.inner_color;
|
||||
c.lineWidth = inner_width;
|
||||
c.beginPath();
|
||||
c.moveTo(start_x, start_y + offset_start);
|
||||
c.bezierCurveTo(
|
||||
start_x + cp_shift,
|
||||
start_y + offset_start,
|
||||
end_x - cp_shift,
|
||||
end_y + offset_end,
|
||||
end_x,
|
||||
end_y + offset_end
|
||||
);
|
||||
c.stroke();
|
||||
const lineData = [
|
||||
{ x: start_x, y: start_y + offset_start },
|
||||
{ x: start_x + cp_shift, y: start_y + offset_start },
|
||||
{ x: end_x - cp_shift, y: end_y + offset_end },
|
||||
{ x: end_x, y: end_y + offset_end }
|
||||
];
|
||||
const lineFunction = d3.svg
|
||||
.line()
|
||||
.x(function(d) {
|
||||
return d.x;
|
||||
})
|
||||
.y(function(d) {
|
||||
return d.y;
|
||||
})
|
||||
.interpolate("basis");
|
||||
this.svg
|
||||
.append("path")
|
||||
.attr("class", "ribbon-outer")
|
||||
.attr("d", lineFunction(lineData))
|
||||
.attr("stroke-width", outer_width)
|
||||
.attr("fill", "none");
|
||||
this.svg
|
||||
.append("path")
|
||||
.attr("class", this.innerClass)
|
||||
.attr("d", lineFunction(lineData))
|
||||
.attr("stroke-width", inner_width)
|
||||
.attr("fill", "none");
|
||||
}
|
||||
});
|
||||
}
|
||||
export default Connector;
|
||||
|
||||
@@ -134,7 +134,7 @@ class Workflow extends EventEmitter {
|
||||
|
||||
// Create node wrapper
|
||||
const container = document.createElement("div");
|
||||
container.className = "toolForm toolFormInCanvas";
|
||||
container.className = "workflow-node";
|
||||
document.getElementById("canvas-container").appendChild(container);
|
||||
var $f = $(container);
|
||||
|
||||
@@ -380,15 +380,15 @@ class Workflow extends EventEmitter {
|
||||
wf.id_counter = max_id + 1;
|
||||
// Second pass, connections
|
||||
$.each(data.steps, (id, step) => {
|
||||
var node = wf.nodes[parseInt(id) + offset];
|
||||
const node = wf.nodes[parseInt(id) + offset];
|
||||
$.each(step.input_connections, (k, v) => {
|
||||
if (v) {
|
||||
if (!$.isArray(v)) {
|
||||
v = [v];
|
||||
}
|
||||
$.each(v, (l, x) => {
|
||||
var other_node = wf.nodes[parseInt(x.id) + offset];
|
||||
var c = new Connector(this.canvas_manager);
|
||||
const other_node = wf.nodes[parseInt(x.id) + offset];
|
||||
const c = new Connector(this.canvas_manager);
|
||||
c.connect(other_node.output_terminals[x.output_name], node.input_terminals[k]);
|
||||
c.redraw();
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ export class Node {
|
||||
this.element = attr.element;
|
||||
this.input_terminals = {};
|
||||
this.output_terminals = {};
|
||||
this.errors = {};
|
||||
this.errors = null;
|
||||
this.workflow_outputs = [];
|
||||
}
|
||||
getWorkflowOutput(outputName) {
|
||||
@@ -191,7 +191,7 @@ export class Node {
|
||||
$(this.element).remove();
|
||||
}
|
||||
make_active() {
|
||||
$(this.element).addClass("toolForm-active");
|
||||
$(this.element).addClass("node-active");
|
||||
}
|
||||
make_inactive() {
|
||||
// Keep inactive nodes stacked from most to least recently active
|
||||
@@ -202,7 +202,7 @@ export class Node {
|
||||
p.appendChild(element);
|
||||
})(element.parentNode);
|
||||
// Remove active class
|
||||
$(element).removeClass("toolForm-active");
|
||||
$(element).removeClass("node-active");
|
||||
}
|
||||
set_tool_version() {
|
||||
if (this.type === "tool" && this.config_form) {
|
||||
@@ -309,7 +309,7 @@ export class Node {
|
||||
var pja_in = data.post_job_actions;
|
||||
this.post_job_actions = pja_in ? pja_in : {};
|
||||
}
|
||||
node.nodeView.renderToolErrors();
|
||||
node.nodeView.renderErrors();
|
||||
// Update input rows
|
||||
var old_body = nodeView.$el.find("div.inputs");
|
||||
var new_body = nodeView.newInputsDiv();
|
||||
@@ -340,14 +340,6 @@ export class Node {
|
||||
this.markChanged();
|
||||
this.redraw();
|
||||
}
|
||||
error(text) {
|
||||
var b = $(this.element).find(".toolFormBody");
|
||||
b.find("div").remove();
|
||||
var tmp = `<div style='color: red; text-style: italic;'>${text}</div>`;
|
||||
this.config_form = tmp;
|
||||
b.html(tmp);
|
||||
this.app.node_changed(this);
|
||||
}
|
||||
markChanged() {
|
||||
this.app.node_changed(this);
|
||||
}
|
||||
|
||||
@@ -9,26 +9,31 @@ export class NodeView {
|
||||
this.$el = options.$el;
|
||||
this.node = options.node;
|
||||
this.output_width = Math.max(150, this.$el.width());
|
||||
this.tool_body = this.$el.find(".toolFormBody");
|
||||
this.tool_body.find("div").remove();
|
||||
this.newInputsDiv().appendTo(this.tool_body);
|
||||
this.node_body = this.$el.find(".node-body");
|
||||
this.node_body.find("div").remove();
|
||||
this.newInputsDiv().appendTo(this.node_body);
|
||||
this.terminalViews = {};
|
||||
this.outputViews = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
this.renderToolLabel();
|
||||
this.renderToolErrors();
|
||||
this.renderLabel();
|
||||
this.renderErrors();
|
||||
this.$el.css("width", Math.min(250, Math.max(this.$el.width(), this.output_width)));
|
||||
}
|
||||
|
||||
renderToolLabel() {
|
||||
renderLabel() {
|
||||
this.$el.find(".node-title").text(this.node.label || this.node.name);
|
||||
this.$el.attr("node-label", this.node.label);
|
||||
}
|
||||
|
||||
renderToolErrors() {
|
||||
this.node.errors ? this.$el.addClass("tool-node-error") : this.$el.removeClass("tool-node-error");
|
||||
renderErrors() {
|
||||
if (this.node.errors) {
|
||||
this.$el.addClass("node-error");
|
||||
this.node_body.text(this.node.errors);
|
||||
} else {
|
||||
this.$el.removeClass("node-error");
|
||||
}
|
||||
}
|
||||
|
||||
newInputsDiv() {
|
||||
@@ -40,7 +45,7 @@ export class NodeView {
|
||||
}
|
||||
|
||||
addRule() {
|
||||
this.tool_body.append($("<div/>").addClass("rule"));
|
||||
this.node_body.append($("<div/>").addClass("rule"));
|
||||
}
|
||||
|
||||
addDataInput(input, body) {
|
||||
@@ -111,7 +116,7 @@ export class NodeView {
|
||||
const terminalView = this.terminalViewForOutput(output);
|
||||
const outputView = this.outputViewforOutput(output, terminalView);
|
||||
this.outputViews[output.name] = outputView;
|
||||
this.tool_body.append(outputView.$el.append(terminalView.terminalElements()));
|
||||
this.node_body.append(outputView.$el.append(terminalView.terminalElements()));
|
||||
}
|
||||
|
||||
redrawWorkflowOutputs() {
|
||||
|
||||
@@ -91,10 +91,10 @@ class BaseInputTerminalView extends TerminalView {
|
||||
onDropStart(e, d = {}) {
|
||||
if (d.proxy.terminal) {
|
||||
if (this.$el.hasClass("can-accept")) {
|
||||
d.proxy.terminal.connectors[0].inner_color = "#BBFFBB";
|
||||
d.proxy.terminal.connectors[0].dropStart(true);
|
||||
d.proxy.dropTooltip = "";
|
||||
} else {
|
||||
d.proxy.terminal.connectors[0].inner_color = "#fe7f02";
|
||||
d.proxy.terminal.connectors[0].dropStart(false);
|
||||
if (this.reason) {
|
||||
d.proxy.dropTooltip = this.reason;
|
||||
$(d.proxy).tooltip("show");
|
||||
@@ -107,14 +107,15 @@ class BaseInputTerminalView extends TerminalView {
|
||||
onDropEnd(e, d = {}) {
|
||||
d.proxy.dropTooltip = "";
|
||||
if (d.proxy.terminal) {
|
||||
d.proxy.terminal.connectors[0].inner_color = "#FFFFFF";
|
||||
d.proxy.terminal.connectors[0].dropEnd();
|
||||
}
|
||||
}
|
||||
onDrop(e, d = {}) {
|
||||
d.proxy.dropTooltip = "";
|
||||
if (this.$el.hasClass("can-accept")) {
|
||||
const terminal = this.el.terminal;
|
||||
new Connector(this.app.canvas_manager, d.drag.terminal, terminal).redraw();
|
||||
const c = new Connector(this.app.canvas_manager, d.drag.terminal, terminal);
|
||||
c.redraw();
|
||||
}
|
||||
}
|
||||
onHover() {
|
||||
@@ -357,7 +358,7 @@ export class BaseOutputTerminalView extends TerminalView {
|
||||
}
|
||||
});
|
||||
h.terminal = new Terminals.OutputTerminal({ element: h });
|
||||
var c = new Connector(this.app.canvas_manager);
|
||||
const c = new Connector(this.app.canvas_manager);
|
||||
c.dragging = true;
|
||||
c.connect(this.el.terminal, h.terminal);
|
||||
return h;
|
||||
|
||||
@@ -242,9 +242,7 @@ QUnit.test("default attributes", function(assert) {
|
||||
const output = { connect: sinon.spy() };
|
||||
const connector = new Connector({}, input, output);
|
||||
assert.equal(connector.dragging, false);
|
||||
assert.equal(connector.canvas, null);
|
||||
assert.equal(connector.inner_color, "#FFFFFF");
|
||||
assert.equal(connector.outer_color, "#25537b");
|
||||
assert.equal(connector.svg.attr("class"), "ribbon");
|
||||
});
|
||||
|
||||
QUnit.test("destroy", function(assert) {
|
||||
@@ -271,13 +269,12 @@ QUnit.test("initial redraw", function(assert) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const n = $("#canvas-container").find("svg").length;
|
||||
const connector = new Connector({}, input, output);
|
||||
const n = $("#canvas-container").find("canvas").length;
|
||||
connector.redraw();
|
||||
// Ensure canvas gets set
|
||||
assert.ok(connector.canvas);
|
||||
// Ensure it got added to canvas container
|
||||
assert.equal(n + 1, $("#canvas-container").find("canvas").length);
|
||||
assert.equal(n + 1, $("#canvas-container").find("svg").length);
|
||||
});
|
||||
|
||||
QUnit.module("Input collection terminal model test", {
|
||||
@@ -366,9 +363,9 @@ QUnit.module("Node unit test", {
|
||||
});
|
||||
|
||||
QUnit.test("make active", function(assert) {
|
||||
assert.ok(!this.element.hasClass("toolForm-active"));
|
||||
assert.ok(!this.element.hasClass("node-active"));
|
||||
this.node.make_active();
|
||||
assert.ok(this.element.hasClass("toolForm-active"));
|
||||
assert.ok(this.element.hasClass("node-active"));
|
||||
});
|
||||
|
||||
QUnit.test("destroy", function(assert) {
|
||||
@@ -380,11 +377,11 @@ QUnit.test("destroy", function(assert) {
|
||||
});
|
||||
|
||||
QUnit.test("error", function(assert) {
|
||||
// Test body of toolFormBody div updated and workflow notified of change.
|
||||
// Test body of div updated and workflow notified of change.
|
||||
var test = this;
|
||||
this.expect_workflow_node_changed(assert, function() {
|
||||
test.node.error("TOOL ERROR");
|
||||
assert.equal($(test.$(".toolFormBody").children()[0]).html(), "TOOL ERROR");
|
||||
test.node.init_field_data({ errors: "NODE ERROR", inputs: [], outputs: [] });
|
||||
assert.equal(test.$(".node-body").html(), "NODE ERROR");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -428,10 +425,10 @@ QUnit.test("init_field_data data", function(assert) {
|
||||
assert.equal(this.$(".output-terminal").length, 1);
|
||||
assert.equal(this.$(".input-terminal").length, 1);
|
||||
assert.equal(this.$(".rule").length, 1);
|
||||
assert.equal(this.$(".toolFormBody").children().length, 3);
|
||||
assert.equal(this.$(".node-body").children().length, 3);
|
||||
assert.equal(this.$(".node-title").text(), "newnode");
|
||||
assert.ok(
|
||||
this.$(".toolFormTitle")
|
||||
this.$(".node-header")
|
||||
.find("i")
|
||||
.hasClass("fa-wrench")
|
||||
);
|
||||
@@ -607,10 +604,10 @@ QUnit.module("Node view ", {
|
||||
QUnit.test("tool error styling", function(assert) {
|
||||
this.set_for_node({ errors: false });
|
||||
this.view.render();
|
||||
assert.ok(!this.view.$el.hasClass("tool-node-error"));
|
||||
assert.ok(!this.view.$el.hasClass("node-error"));
|
||||
this.set_for_node({ errors: true });
|
||||
this.view.render();
|
||||
assert.ok(this.view.$el.hasClass("tool-node-error"));
|
||||
assert.ok(this.view.$el.hasClass("node-error"));
|
||||
});
|
||||
|
||||
QUnit.test("rendering correct width", function(assert) {
|
||||
|
||||
@@ -7,40 +7,44 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
.toolForm.toolFormInCanvas {
|
||||
.workflow-node {
|
||||
@extend .card;
|
||||
@extend .position-absolute;
|
||||
z-index: 100;
|
||||
width: $worfklow-node-width;
|
||||
position: absolute;
|
||||
border-color: $brand-primary;
|
||||
&.toolForm-active {
|
||||
border: solid $brand-primary 1px;
|
||||
&.node-active {
|
||||
z-index: 1001;
|
||||
border: solid $white 1px;
|
||||
box-shadow: 0 0 0 2px $brand-primary;
|
||||
}
|
||||
.toolFormTitle {
|
||||
.node-header {
|
||||
@extend .card-header;
|
||||
@extend .py-1;
|
||||
@extend .px-2;
|
||||
cursor: move;
|
||||
background: $brand-secondary;
|
||||
.header {
|
||||
width: $worfklow-node-width - 2rem;
|
||||
}
|
||||
}
|
||||
.toolFormBody {
|
||||
.node-body {
|
||||
@extend .card-body;
|
||||
@extend .p-0;
|
||||
@extend .mx-2;
|
||||
.rule {
|
||||
height: 0;
|
||||
border: none;
|
||||
border-bottom: dotted black 1px;
|
||||
border-bottom: dotted $brand-primary 1px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.toolForm.toolFormInCanvas.tool-node-error {
|
||||
border-color: $state-danger-border;
|
||||
.toolFormTitle {
|
||||
background: $state-danger-bg;
|
||||
border-color: $state-danger-border;
|
||||
.workflow-node.node-error {
|
||||
border-color: $brand-danger;
|
||||
.node-header {
|
||||
background: $brand-danger;
|
||||
border-color: $brand-danger;
|
||||
}
|
||||
}
|
||||
.workflow-canvas {
|
||||
@@ -55,6 +59,20 @@
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ribbon {
|
||||
.ribbon-outer {
|
||||
stroke: $brand-primary;
|
||||
}
|
||||
.ribbon-inner {
|
||||
stroke: $white;
|
||||
}
|
||||
.ribbon-inner-valid {
|
||||
stroke: $brand-success;
|
||||
}
|
||||
.ribbon-inner-invalid {
|
||||
stroke: $brand-warning;
|
||||
}
|
||||
}
|
||||
.base-terminal {
|
||||
@extend .fa;
|
||||
@extend .fa-circle;
|
||||
|
||||
@@ -711,19 +711,6 @@
|
||||
:Type: int
|
||||
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
``manage_dependency_relationships``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:Description:
|
||||
Enable use of an in-memory registry with bi-directional
|
||||
relationships between repositories (i.e., in addition to lists of
|
||||
dependencies for a repository, keep an in-memory registry of
|
||||
dependent items for each repository.
|
||||
:Default: ``false``
|
||||
:Type: bool
|
||||
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
``tool_data_table_config_path``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -1234,6 +1221,21 @@
|
||||
:Type: str
|
||||
|
||||
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
``whitelist_file``
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:Description:
|
||||
E-mail domains whitelist is used to specify allowed email address
|
||||
domains. If the list is non-empty and a user attempts registration
|
||||
using an email address belonging to a domain that is not on the
|
||||
list, registration will be enied. This is a more restrictive
|
||||
option than <blacklist_file>, and therefore, in case
|
||||
<whitelist_file> is defined, <blacklist_file> will be ignored.
|
||||
:Default: ``disposable_email_whitelist.conf``
|
||||
:Type: str
|
||||
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
``registration_warning_message``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -3271,6 +3273,19 @@
|
||||
:Type: bool
|
||||
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
``job_metrics_config_file``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:Description:
|
||||
XML config file that contains the job metric collection
|
||||
configuration.
|
||||
The value of this option will be resolved with respect to
|
||||
<config_dir>.
|
||||
:Default: ``job_metrics_conf.xml``
|
||||
:Type: str
|
||||
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
``expose_potentially_sensitive_job_metrics``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -3673,7 +3688,7 @@
|
||||
|
||||
:Description:
|
||||
CSS file to apply to "Galaxy Page" exports to PDF. Generally
|
||||
prefer markdown_export_css, but this is here for deployments that
|
||||
prefer markdown_export_css, but this is here for deployments that
|
||||
would like to tailor different kinds of exports.
|
||||
:Default: ``markdown_export_pages.css``
|
||||
:Type: str
|
||||
@@ -3685,7 +3700,7 @@
|
||||
|
||||
:Description:
|
||||
CSS file to apply to invocation report exports to PDF. Generally
|
||||
prefer markdown_export_css, but this is here for deployments that
|
||||
prefer markdown_export_css, but this is here for deployments that
|
||||
would like to tailor different kinds of exports.
|
||||
:Default: ``markdown_export_invocation_reports.css``
|
||||
:Type: str
|
||||
|
||||
@@ -45,7 +45,7 @@ class AdminActions(object):
|
||||
# If this is a default quota, create the DefaultQuotaAssociation
|
||||
if params.default != 'no':
|
||||
self.app.quota_agent.set_default_quota(params.default, quota)
|
||||
message = "Default quota '%s' has been created."
|
||||
message = "Default quota '%s' has been created." % quota.name
|
||||
else:
|
||||
# Create the UserQuotaAssociations
|
||||
in_users = [self.sa_session.query(self.app.model.User).get(decode_id(x) if decode_id else x) for x in util.listify(params.in_users)]
|
||||
|
||||
@@ -91,9 +91,7 @@ class UniverseApplication(config.ConfiguresGalaxyMixin):
|
||||
check_migrate_tools = self.config.check_migrate_tools
|
||||
self._configure_models(check_migrate_databases=self.config.check_migrate_databases, check_migrate_tools=check_migrate_tools, config_file=config_file)
|
||||
|
||||
# Manage installed tool shed repositories.
|
||||
self.installed_repository_manager = InstalledRepositoryManager(self)
|
||||
|
||||
self._configure_datatypes_registry(self.installed_repository_manager)
|
||||
galaxy.model.set_datatypes_registry(self.datatypes_registry)
|
||||
|
||||
|
||||
+98
-126
@@ -100,11 +100,24 @@ LOGGING_CONFIG_DEFAULT = {
|
||||
|
||||
|
||||
def find_root(kwargs):
|
||||
root = os.path.abspath(kwargs.get('root_dir', '.'))
|
||||
return root
|
||||
return os.path.abspath(kwargs.get('root_dir', '.'))
|
||||
|
||||
|
||||
class BaseAppConfiguration(object):
|
||||
# Override in subclasses (optional): {KEY: config option, VALUE: deprecated directory name}
|
||||
# If VALUE == first directory in a user-supplied path that resolves to KEY, it will be stripped from that path
|
||||
deprecated_dirs = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.config_dict = kwargs
|
||||
self.root = find_root(kwargs)
|
||||
self._set_config_base(kwargs)
|
||||
self.schema = self._load_schema() # Load schema from schema definition file
|
||||
self._raw_config = self.schema.defaults.copy() # Save schema defaults as initial config values (raw_config)
|
||||
self._update_raw_config_from_kwargs(kwargs) # Overwrite raw_config with values passed in kwargs
|
||||
self._create_attributes_from_raw_config() # Create attributes based on raw_config
|
||||
self._resolve_paths() # Overwrite attribute values with resolved paths
|
||||
|
||||
def _set_config_base(self, config_kwargs):
|
||||
|
||||
def _set_global_conf():
|
||||
@@ -163,6 +176,75 @@ class BaseAppConfiguration(object):
|
||||
_set_global_conf()
|
||||
_set_config_directories()
|
||||
|
||||
def _load_schema(self):
|
||||
# Override in subclasses
|
||||
raise Exception('Not implemented')
|
||||
|
||||
def _update_raw_config_from_kwargs(self, kwargs):
|
||||
|
||||
def convert_datatype(key, value):
|
||||
datatype = self.schema.app_schema[key].get('type')
|
||||
# check for `not None` explicitly (value can be falsy)
|
||||
if value is not None and datatype in type_converters:
|
||||
return type_converters[datatype](value)
|
||||
return value
|
||||
|
||||
def strip_deprecated_dir(key, value):
|
||||
resolves_to = self.schema.paths_to_resolve.get(key)
|
||||
if resolves_to: # value is a path that will be resolved
|
||||
first_dir = value.split(os.sep)[0] # get first directory component
|
||||
if first_dir == self.deprecated_dirs.get(resolves_to): # first_dir is deprecated for this option
|
||||
ignore = first_dir + os.sep
|
||||
log.warning(
|
||||
"Paths for the '%s' option are now relative to '%s', remove the leading '%s' "
|
||||
"to suppress this warning: %s", key, resolves_to, ignore, value
|
||||
)
|
||||
return value[len(ignore):]
|
||||
return value
|
||||
|
||||
type_converters = {'bool': string_as_bool, 'int': int, 'float': float, 'str': str}
|
||||
|
||||
for key, value in kwargs.items():
|
||||
if key in self.schema.app_schema:
|
||||
value = convert_datatype(key, value)
|
||||
if value and self.deprecated_dirs:
|
||||
value = strip_deprecated_dir(key, value)
|
||||
self._raw_config[key] = value
|
||||
|
||||
def _create_attributes_from_raw_config(self):
|
||||
# `base_configs` are a special case: these attributes have been created and will be ignored
|
||||
# by the code below. Trying to overwrite any other existing attributes will raise an error.
|
||||
base_configs = {'config_dir', 'data_dir', 'managed_config_dir'}
|
||||
for key, value in self._raw_config.items():
|
||||
if not hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
elif key not in base_configs:
|
||||
raise ConfigurationError("Attempting to override existing attribute '%s'" % key)
|
||||
|
||||
def _resolve_paths(self):
|
||||
|
||||
def resolve(key):
|
||||
if key in _cache: # resolve each path only once
|
||||
return _cache[key]
|
||||
|
||||
path = getattr(self, key) # path prior to being resolved
|
||||
parent = self.schema.paths_to_resolve.get(key)
|
||||
if not parent: # base case: nothing else needs resolving
|
||||
return path
|
||||
parent_path = resolve(parent) # recursively resolve parent path
|
||||
if path is not None:
|
||||
path = os.path.join(parent_path, path) # resolve path
|
||||
else:
|
||||
path = parent_path # or use parent path
|
||||
|
||||
setattr(self, key, path) # update property
|
||||
_cache[key] = path # cache it!
|
||||
return path
|
||||
|
||||
_cache = {}
|
||||
for key in self.schema.paths_to_resolve:
|
||||
resolve(key)
|
||||
|
||||
def _in_managed_config_dir(self, path):
|
||||
return os.path.join(self.managed_config_dir, path)
|
||||
|
||||
@@ -212,133 +294,14 @@ class BaseAppConfiguration(object):
|
||||
class GalaxyAppConfiguration(BaseAppConfiguration):
|
||||
deprecated_options = ('database_file', 'track_jobs_in_database')
|
||||
default_config_file_name = 'galaxy.yml'
|
||||
# {key: config option, value: deprecated directory name}
|
||||
# If value == first dir in a user path that resolves to key, it will be stripped from the path
|
||||
deprecated_dirs = {'config_dir': 'config', 'data_dir': 'database'}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._load_schema() # Load schema from schema definition file
|
||||
self._load_config_from_schema() # Load default propery values from schema
|
||||
self._validate_schema_paths() # check that paths can be resolved
|
||||
self._update_raw_config_from_kwargs(kwargs) # Overwrite default values passed as kwargs
|
||||
self._create_attributes_from_raw_config() # Create attributes for LOADED properties
|
||||
|
||||
self.config_dict = kwargs
|
||||
self.root = find_root(kwargs)
|
||||
self._set_config_base(kwargs) # must be called prior to _resolve_paths()
|
||||
|
||||
self._resolve_paths(kwargs) # Overwrite attributes (not _raw_config) w/resolved paths
|
||||
self._process_config(kwargs) # Finish processing configuration
|
||||
super(GalaxyAppConfiguration, self).__init__(**kwargs)
|
||||
self._process_config(kwargs)
|
||||
|
||||
def _load_schema(self):
|
||||
self.schema = AppSchema(GALAXY_CONFIG_SCHEMA_PATH, GALAXY_APP_NAME)
|
||||
self.appschema = self.schema.app_schema
|
||||
|
||||
def _load_config_from_schema(self):
|
||||
self._raw_config = {} # keeps track of startup values (kwargs or schema default)
|
||||
self.reloadable_options = set() # config options we can reload at runtime
|
||||
self._paths_to_resolve = {} # {config option: referenced config option}
|
||||
for key, data in self.appschema.items():
|
||||
self._raw_config[key] = data.get('default')
|
||||
if data.get('reloadable'):
|
||||
self.reloadable_options.add(key)
|
||||
if data.get('path_resolves_to'):
|
||||
self._paths_to_resolve[key] = data.get('path_resolves_to')
|
||||
|
||||
def _validate_schema_paths(self):
|
||||
|
||||
def check_exists(option, key):
|
||||
if not option:
|
||||
message = "Invalid schema: property '{}' listed as path resolution target " \
|
||||
"for '{}' does not exist".format(resolves_to, key)
|
||||
raise_error(message)
|
||||
|
||||
def check_type_is_str(option, key):
|
||||
if option.get('type') != 'str':
|
||||
message = "Invalid schema: property '{}' should have type 'str'".format(key)
|
||||
raise_error(message)
|
||||
|
||||
def check_is_dag():
|
||||
visited = set()
|
||||
for key in self._paths_to_resolve:
|
||||
visited.clear()
|
||||
while key:
|
||||
visited.add(key)
|
||||
key = self.appschema[key].get('path_resolves_to')
|
||||
if key and key in visited:
|
||||
raise_error('Invalid schema: cycle detected')
|
||||
|
||||
def raise_error(message):
|
||||
log.error(message)
|
||||
raise ConfigurationError(message)
|
||||
|
||||
for key, resolves_to in self._paths_to_resolve.items():
|
||||
parent = self.appschema.get(resolves_to)
|
||||
check_exists(parent, key)
|
||||
check_type_is_str(parent, key)
|
||||
check_type_is_str(self.appschema[key], key)
|
||||
check_is_dag() # must be called last: walks entire graph
|
||||
|
||||
def _update_raw_config_from_kwargs(self, kwargs):
|
||||
|
||||
def convert_datatype(key, value):
|
||||
datatype = self.appschema[key].get('type')
|
||||
# check for `not None` explicitly (value can be falsy)
|
||||
if value is not None and datatype in type_converters:
|
||||
return type_converters[datatype](value)
|
||||
return value
|
||||
|
||||
def strip_deprecated_dir(key, value):
|
||||
resolves_to = self.appschema[key].get('path_resolves_to')
|
||||
if resolves_to: # value is a path that will be resolved
|
||||
first_dir = value.split(os.sep)[0] # get first directory component
|
||||
if first_dir == self.deprecated_dirs[resolves_to]: # first_dir is deprecated for this option
|
||||
ignore = first_dir + os.sep
|
||||
log.warning(
|
||||
"Paths for the '%s' option are now relative to '%s', remove the leading '%s' "
|
||||
"to suppress this warning: %s", key, resolves_to, ignore, value
|
||||
)
|
||||
return value[len(ignore):]
|
||||
return value
|
||||
|
||||
type_converters = {'bool': string_as_bool, 'int': int, 'float': float, 'str': str}
|
||||
|
||||
for key, value in kwargs.items():
|
||||
if key in self.appschema:
|
||||
value = convert_datatype(key, value)
|
||||
if value:
|
||||
value = strip_deprecated_dir(key, value)
|
||||
self._raw_config[key] = value
|
||||
|
||||
def _create_attributes_from_raw_config(self):
|
||||
for key, value in self._raw_config.items():
|
||||
if hasattr(self, key):
|
||||
raise ConfigurationError("Attempting to override existing attribute '%s'" % key)
|
||||
setattr(self, key, value)
|
||||
|
||||
def _resolve_paths(self, kwargs):
|
||||
|
||||
def resolve(key):
|
||||
if key in _cache: # resolve each path only once
|
||||
return _cache[key]
|
||||
|
||||
path = getattr(self, key) # path prior to being resolved
|
||||
parent = self.appschema[key].get('path_resolves_to')
|
||||
if not parent: # base case: nothing else needs resolving
|
||||
return path
|
||||
parent_path = resolve(parent) # recursively resolve parent path
|
||||
if path is not None:
|
||||
path = os.path.join(parent_path, path) # resolve path
|
||||
else:
|
||||
path = parent_path # or use parent path
|
||||
|
||||
setattr(self, key, path) # update property
|
||||
_cache[key] = path # cache it!
|
||||
return path
|
||||
|
||||
_cache = {}
|
||||
for key in self._paths_to_resolve:
|
||||
resolve(key)
|
||||
return AppSchema(GALAXY_CONFIG_SCHEMA_PATH, GALAXY_APP_NAME)
|
||||
|
||||
def _process_config(self, kwargs):
|
||||
# Resolve paths of other config files
|
||||
@@ -465,6 +428,15 @@ class GalaxyAppConfiguration(BaseAppConfiguration):
|
||||
except IOError:
|
||||
log.error("CONFIGURATION ERROR: Can't open supplied blacklist file from path: %s", self.blacklist_file)
|
||||
|
||||
# Create whitelist file to accept only certain email domains
|
||||
self.whitelist_content = None
|
||||
if self.whitelist_file:
|
||||
try:
|
||||
with open(self.whitelist_file) as f:
|
||||
self.whitelist_content = [line.rstrip() for line in f]
|
||||
except IOError:
|
||||
log.error("CONFIGURATION ERROR: Can't open supplied whitelist file from path: %s", self.whitelist_file)
|
||||
|
||||
self.persistent_communication_rooms = listify(self.persistent_communication_rooms, do_strip=True)
|
||||
# The transfer manager and deferred job queue
|
||||
self.enable_beta_job_managers = string_as_bool(kwargs.get('enable_beta_job_managers', 'False'))
|
||||
@@ -526,7 +498,7 @@ class GalaxyAppConfiguration(BaseAppConfiguration):
|
||||
if self.tool_dependency_dir and self.tool_dependency_dir.lower() == 'none':
|
||||
self.tool_dependency_dir = None
|
||||
if self.involucro_path is None:
|
||||
target_dir = self.tool_dependency_dir or self.appschema['tool_dependency_dir'].get('default')
|
||||
target_dir = self.tool_dependency_dir or self.schema.defaults['tool_dependency_dir']
|
||||
self.involucro_path = os.path.join(self.data_dir, target_dir, "involucro")
|
||||
self.involucro_path = os.path.join(self.root, self.involucro_path)
|
||||
if self.mulled_channels:
|
||||
@@ -897,7 +869,7 @@ Configuration = GalaxyAppConfiguration
|
||||
def reload_config_options(current_config):
|
||||
""" Reload modified reloadable config options """
|
||||
modified_config = read_properties_from_file(current_config.config_file)
|
||||
for option in current_config.reloadable_options:
|
||||
for option in current_config.schema.reloadable_options:
|
||||
if option in modified_config:
|
||||
# compare to raw value, as that one is set only on load and reload
|
||||
if current_config._raw_config[option] != modified_config[option]:
|
||||
|
||||
@@ -30,12 +30,12 @@ if __name__ == '__main__':
|
||||
from galaxy.config import GALAXY_CONFIG_SCHEMA_PATH
|
||||
from galaxy.config.schema import (
|
||||
AppSchema,
|
||||
OPTION_DEFAULTS,
|
||||
Schema,
|
||||
)
|
||||
from galaxy.util import safe_makedirs
|
||||
from galaxy.util.properties import nice_config_parser
|
||||
from galaxy.util.yaml_util import (
|
||||
OPTION_DEFAULTS,
|
||||
ordered_dump,
|
||||
ordered_load,
|
||||
)
|
||||
|
||||
@@ -561,8 +561,17 @@
|
||||
<datatype extension="cel" type="galaxy.datatypes.microarrays:Cel" display_in_upload="true"/>
|
||||
<datatype extension="gpr" type="galaxy.datatypes.microarrays:Gpr" display_in_upload="true"/>
|
||||
<datatype extension="gal" type="galaxy.datatypes.microarrays:Gal" display_in_upload="true"/>
|
||||
<datatype extension="rdata" type="galaxy.datatypes.binary:RData" display_in_upload="true" description="Stored data from an R session"/>
|
||||
<datatype extension="rdata.sce" type="galaxy.datatypes.binary:RData" description="Stored RDS from a SingleCellObject" subclass="true" display_in_upload="true"/>
|
||||
<datatype extension="rdata" type="galaxy.datatypes.binary:RData" mimetype="application/x-gzip" display_in_upload="true" description="Stored data from an R session"/>
|
||||
<datatype extension="rdata.sce" type="galaxy.datatypes.binary:RData" mimetype="application/x-gzip" subclass="true" display_in_upload="true" description="Stored RDS from a SingleCellObject"/>
|
||||
<datatype extension="rdata.xcms.raw" type="galaxy.datatypes.binary:RData" mimetype="application/x-gzip" subclass="true" display_in_upload="true"/>
|
||||
<datatype extension="rdata.msnbase.raw" type="galaxy.datatypes.binary:RData" mimetype="application/x-gzip" subclass="true" display_in_upload="true"/>
|
||||
<datatype extension="rdata.xcms.findchrompeaks" type="galaxy.datatypes.binary:RData" mimetype="application/x-gzip" subclass="true" display_in_upload="true"/>
|
||||
<datatype extension="rdata.xcms.group" type="galaxy.datatypes.binary:RData" mimetype="application/x-gzip" subclass="true" display_in_upload="true"/>
|
||||
<datatype extension="rdata.xcms.retcor" type="galaxy.datatypes.binary:RData" mimetype="application/x-gzip" subclass="true" display_in_upload="true"/>
|
||||
<datatype extension="rdata.xcms.fillpeaks" type="galaxy.datatypes.binary:RData" mimetype="application/x-gzip" subclass="true" display_in_upload="true"/>
|
||||
<datatype extension="rdata.camera.positive" type="galaxy.datatypes.binary:RData" mimetype="application/x-gzip" subclass="true" display_in_upload="true"/>
|
||||
<datatype extension="rdata.camera.negative" type="galaxy.datatypes.binary:RData" mimetype="application/x-gzip" subclass="true" display_in_upload="true"/>
|
||||
<datatype extension="rdata.camera.quick" type="galaxy.datatypes.binary:RData" mimetype="application/x-gzip" subclass="true" display_in_upload="true"/>
|
||||
<datatype extension="rdock_as" type="galaxy.datatypes.binary:Binary" description="rDock active site format" subclass="true" display_in_upload="true"/>
|
||||
<datatype extension="oxlicg" type="galaxy.datatypes.binary:OxliCountGraph" mimetype="application/octet-stream" display_in_upload="true"/>
|
||||
<datatype extension="oxling" type="galaxy.datatypes.binary:OxliNodeGraph" mimetype="application/octet-stream" display_in_upload="true"/>
|
||||
|
||||
@@ -451,12 +451,6 @@ galaxy:
|
||||
# between 1 and 24.
|
||||
#hours_between_check: 12
|
||||
|
||||
# Enable use of an in-memory registry with bi-directional
|
||||
# relationships between repositories (i.e., in addition to lists of
|
||||
# dependencies for a repository, keep an in-memory registry of
|
||||
# dependent items for each repository.
|
||||
#manage_dependency_relationships: false
|
||||
|
||||
# XML config file that contains data table entries for the
|
||||
# ToolDataTableManager. This file is manually # maintained by the
|
||||
# Galaxy administrator (.sample used if default does not exist).
|
||||
@@ -693,6 +687,14 @@ galaxy:
|
||||
# Example value 'config/disposable_email_blacklist.conf'
|
||||
#blacklist_file: null
|
||||
|
||||
# E-mail domains whitelist is used to specify allowed email address
|
||||
# domains. If the list is non-empty and a user attempts registration
|
||||
# using an email address belonging to a domain that is not on the
|
||||
# list, registration will be enied. This is a more restrictive option
|
||||
# than <blacklist_file>, and therefore, in case <whitelist_file> is
|
||||
# defined, <blacklist_file> will be ignored.
|
||||
#whitelist_file: disposable_email_whitelist.conf
|
||||
|
||||
# Registration warning message is used to discourage people from
|
||||
# registering multiple accounts. Applies mostly for the main Galaxy
|
||||
# instance. If no message specified the warning box will not be shown.
|
||||
@@ -1611,6 +1613,12 @@ galaxy:
|
||||
# see dataset paths.
|
||||
#expose_dataset_path: false
|
||||
|
||||
# XML config file that contains the job metric collection
|
||||
# configuration.
|
||||
# The value of this option will be resolved with respect to
|
||||
# <config_dir>.
|
||||
#job_metrics_config_file: job_metrics_conf.xml
|
||||
|
||||
# This option allows users to see the job metrics (except for
|
||||
# environment variables).
|
||||
#expose_potentially_sensitive_job_metrics: false
|
||||
@@ -1815,7 +1823,7 @@ galaxy:
|
||||
#markdown_export_css_pages: markdown_export_pages.css
|
||||
|
||||
# CSS file to apply to invocation report exports to PDF. Generally
|
||||
# prefer markdown_export_css, but this is here for deployments that
|
||||
# prefer markdown_export_css, but this is here for deployments that
|
||||
# would like to tailor different kinds of exports.
|
||||
#markdown_export_css_invocation_reports: markdown_export_invocation_reports.css
|
||||
|
||||
|
||||
@@ -344,6 +344,14 @@ tool_shed:
|
||||
# the registration.
|
||||
#blacklist_file: config/disposable_email_blacklist.conf
|
||||
|
||||
# E-mail domains whitelist is used to specify allowed email address
|
||||
# domains. If the list is non-empty and a user attempts registration
|
||||
# using an email address belonging to a domain that is not on the
|
||||
# list, registration will be enied. This is a more restrictive option
|
||||
# than <blacklist_file>, and therefore, in case <whitelist_file> is
|
||||
# defined, <blacklist_file> will be ignored.
|
||||
#whitelist_file: disposable_email_whitelist.conf
|
||||
|
||||
# Append "/{brand}" to the "Galaxy" text in the masthead.
|
||||
#brand: null
|
||||
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
from galaxy.util.yaml_util import (
|
||||
OPTION_DEFAULTS,
|
||||
ordered_load,
|
||||
)
|
||||
import logging
|
||||
|
||||
from galaxy.exceptions import ConfigurationError
|
||||
from galaxy.util.yaml_util import ordered_load
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
OPTION_DEFAULTS = {
|
||||
"type": "str",
|
||||
"unknown_option": False,
|
||||
"default": None,
|
||||
"desc": None,
|
||||
}
|
||||
|
||||
UNKNOWN_OPTION = {
|
||||
"type": "str",
|
||||
@@ -33,8 +41,70 @@ class AppSchema(Schema):
|
||||
self.raw_schema = self._read_schema(schema_path)
|
||||
self.description = self.raw_schema.get("desc", None)
|
||||
app_schema = self.raw_schema['mapping'][app_name]['mapping']
|
||||
self._preprocess(app_schema)
|
||||
super(AppSchema, self).__init__(app_schema)
|
||||
|
||||
def _read_schema(self, path):
|
||||
with open(path, "r") as f:
|
||||
return ordered_load(f)
|
||||
|
||||
def _preprocess(self, app_schema):
|
||||
"""Populate schema collections used for app configuration."""
|
||||
self._defaults = {} # {config option: default value or null}
|
||||
self._reloadable_options = set() # config options we can reload at runtime
|
||||
self._paths_to_resolve = {} # {config option: referenced config option}
|
||||
for key, data in app_schema.items():
|
||||
self._defaults[key] = data.get('default')
|
||||
if data.get('reloadable'):
|
||||
self._reloadable_options.add(key)
|
||||
if data.get('path_resolves_to'):
|
||||
self._paths_to_resolve[key] = data.get('path_resolves_to')
|
||||
|
||||
@property
|
||||
def defaults(self):
|
||||
return self._defaults
|
||||
|
||||
@property
|
||||
def paths_to_resolve(self):
|
||||
return self._paths_to_resolve
|
||||
|
||||
@property
|
||||
def reloadable_options(self):
|
||||
return self._reloadable_options
|
||||
|
||||
def validate_path_resolution_graph(self):
|
||||
"""This method is for tests only: we SHOULD validate the schema's path resolution graph
|
||||
as part of automated testing; but we should NOT validate it at runtime.
|
||||
"""
|
||||
def check_exists(option, key):
|
||||
if not option:
|
||||
message = "Invalid schema: property '{}' listed as path resolution target " \
|
||||
"for '{}' does not exist".format(resolves_to, key)
|
||||
raise_error(message)
|
||||
|
||||
def check_type_is_str(option, key):
|
||||
if option.get('type') != 'str':
|
||||
message = "Invalid schema: property '{}' should have type 'str'".format(key)
|
||||
raise_error(message)
|
||||
|
||||
def check_is_dag():
|
||||
visited = set()
|
||||
for key in self.paths_to_resolve:
|
||||
visited.clear()
|
||||
while key:
|
||||
visited.add(key)
|
||||
key = self.app_schema[key].get('path_resolves_to')
|
||||
if key and key in visited:
|
||||
raise_error('Invalid schema: cycle detected')
|
||||
|
||||
def raise_error(message):
|
||||
log.error(message)
|
||||
raise ConfigurationError(message)
|
||||
|
||||
for key, resolves_to in self.paths_to_resolve.items():
|
||||
print(key)
|
||||
parent = self.app_schema.get(resolves_to)
|
||||
check_exists(parent, key)
|
||||
check_type_is_str(parent, key)
|
||||
check_type_is_str(self.app_schema[key], key)
|
||||
check_is_dag() # must be called last: walks entire graph
|
||||
|
||||
@@ -61,8 +61,7 @@ def create_or_verify_database(url, galaxy_config_file, engine_options={}, app=No
|
||||
# Apply all scripts to get to current version
|
||||
migrate_to_current_version(engine, db_schema)
|
||||
|
||||
meta = MetaData(bind=engine)
|
||||
if new_database:
|
||||
def migrate_from_scratch():
|
||||
log.info("Creating new database from scratch, skipping migrations")
|
||||
current_version = migrate_repository.version().version
|
||||
mapping.init(file_path='/tmp', url=url, map_install_models=map_install_models, create_tables=True)
|
||||
@@ -70,6 +69,13 @@ def create_or_verify_database(url, galaxy_config_file, engine_options={}, app=No
|
||||
db_schema = schema.ControlledSchema(engine, migrate_repository)
|
||||
assert db_schema.version == current_version
|
||||
migrate()
|
||||
if app:
|
||||
# skips the tool migration process.
|
||||
app.new_installation = True
|
||||
|
||||
meta = MetaData(bind=engine)
|
||||
if new_database:
|
||||
migrate_from_scratch()
|
||||
return
|
||||
elif app and getattr(app.config, 'database_auto_migrate', False):
|
||||
migrate()
|
||||
@@ -79,12 +85,9 @@ def create_or_verify_database(url, galaxy_config_file, engine_options={}, app=No
|
||||
try:
|
||||
Table("dataset", meta, autoload=True)
|
||||
except NoSuchTableError:
|
||||
# No 'dataset' table means a completely uninitialized database. If we have an app, we'll
|
||||
# set its new_installation setting to True so the tool migration process will be skipped.
|
||||
if app:
|
||||
app.new_installation = True
|
||||
# No 'dataset' table means a completely uninitialized database.
|
||||
log.info("No database, initializing")
|
||||
migrate()
|
||||
migrate_from_scratch()
|
||||
return
|
||||
try:
|
||||
hda_table = Table("history_dataset_association", meta, autoload=True)
|
||||
|
||||
@@ -658,8 +658,7 @@ class GalaxyRBACAgent(RBACAgent):
|
||||
|
||||
def create_user_role(self, user, app):
|
||||
# Create private user role if necessary
|
||||
if not self.get_private_user_role(user):
|
||||
self.create_private_user_role(user)
|
||||
self.get_private_user_role(user, auto_create=True)
|
||||
# Create default user permissions if necessary
|
||||
if not user.default_permissions:
|
||||
if hasattr(app.config, "new_user_dataset_access_role_default_private"):
|
||||
|
||||
@@ -82,7 +82,7 @@ class ObjectStore(object):
|
||||
000/obj.id)
|
||||
"""
|
||||
|
||||
def __init__(self, config, config_dict={}, **kwargs):
|
||||
def __init__(self, config, config_dict=None, **kwargs):
|
||||
"""
|
||||
:type config: object
|
||||
:param config: An object, most likely populated from
|
||||
@@ -95,6 +95,8 @@ class ObjectStore(object):
|
||||
parent directory those directories will be created.
|
||||
* new_file_path -- Used to set the 'temp' extra_dir.
|
||||
"""
|
||||
if config_dict is None:
|
||||
config_dict = {}
|
||||
self.running = True
|
||||
self.config = config
|
||||
self.check_old_style = config.object_store_check_old_style
|
||||
|
||||
@@ -125,7 +125,8 @@ class CloudConfigMixin(object):
|
||||
'cache': {
|
||||
'size': self.cache_size,
|
||||
'path': self.staging_path,
|
||||
}
|
||||
},
|
||||
'enable_cache_monitor': False,
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +139,7 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
store_type = 's3'
|
||||
|
||||
def __init__(self, config, config_dict):
|
||||
super(S3ObjectStore, self).__init__(config)
|
||||
super(S3ObjectStore, self).__init__(config, config_dict)
|
||||
|
||||
self.transfer_progress = 0
|
||||
|
||||
@@ -146,6 +147,7 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
bucket_dict = config_dict['bucket']
|
||||
connection_dict = config_dict.get('connection', {})
|
||||
cache_dict = config_dict['cache']
|
||||
self.enable_cache_monitor = config_dict.get('enable_cache_monitor', True)
|
||||
|
||||
self.access_key = auth_dict.get('access_key')
|
||||
self.secret_key = auth_dict.get('secret_key')
|
||||
@@ -167,9 +169,6 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
(e['type'], e['path']) for e in config_dict.get('extra_dirs', []))
|
||||
self.extra_dirs.update(extra_dirs)
|
||||
|
||||
log.debug("Object cache dir: %s", self.staging_path)
|
||||
log.debug(" job work dir: %s", self.extra_dirs['job_work'])
|
||||
|
||||
self._initialize()
|
||||
|
||||
def _initialize(self):
|
||||
@@ -187,9 +186,17 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
'conn_path': self.conn_path}
|
||||
|
||||
self._configure_connection()
|
||||
self.bucket = self._get_bucket(self.bucket)
|
||||
self._bucket = self._get_bucket(self.bucket)
|
||||
self.start_cache_monitor()
|
||||
# Test if 'axel' is available for parallel download and pull the key into cache
|
||||
if which('axel'):
|
||||
self.use_axel = True
|
||||
else:
|
||||
self.use_axel = False
|
||||
|
||||
def start_cache_monitor(self):
|
||||
# Clean cache only if value is set in galaxy.ini
|
||||
if self.cache_size != -1:
|
||||
if self.cache_size != -1 and self.enable_cache_monitor:
|
||||
# Convert GBs to bytes for comparison
|
||||
self.cache_size = self.cache_size * 1073741824
|
||||
# Helper for interruptable sleep
|
||||
@@ -197,11 +204,6 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
self.cache_monitor_thread = threading.Thread(target=self.__cache_monitor)
|
||||
self.cache_monitor_thread.start()
|
||||
log.info("Cache cleaner manager started")
|
||||
# Test if 'axel' is available for parallel download and pull the key into cache
|
||||
if which('axel'):
|
||||
self.use_axel = True
|
||||
else:
|
||||
self.use_axel = False
|
||||
|
||||
def _configure_connection(self):
|
||||
log.debug("Configuring S3 Connection")
|
||||
@@ -325,7 +327,7 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
# alt_name can contain parent directory references, but S3 will not
|
||||
# follow them, so if they are valid we normalize them out
|
||||
alt_name = os.path.normpath(alt_name)
|
||||
rel_path = os.path.join(*directory_hash_id(obj.id))
|
||||
rel_path = os.path.join(*directory_hash_id(self._get_object_id(obj)))
|
||||
if extra_dir is not None:
|
||||
if extra_dir_at_root:
|
||||
rel_path = os.path.join(extra_dir, rel_path)
|
||||
@@ -334,7 +336,7 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
|
||||
# for JOB_WORK directory
|
||||
if obj_dir:
|
||||
rel_path = os.path.join(rel_path, str(obj.id))
|
||||
rel_path = os.path.join(rel_path, str(self._get_object_id(obj)))
|
||||
if base_dir:
|
||||
base = self.extra_dirs.get(base_dir)
|
||||
return os.path.join(base, rel_path)
|
||||
@@ -343,7 +345,7 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
rel_path = '%s/' % rel_path
|
||||
|
||||
if not dir_only:
|
||||
rel_path = os.path.join(rel_path, alt_name if alt_name else "dataset_%s.dat" % obj.id)
|
||||
rel_path = os.path.join(rel_path, alt_name if alt_name else "dataset_%s.dat" % self._get_object_id(obj))
|
||||
return rel_path
|
||||
|
||||
def _get_cache_path(self, rel_path):
|
||||
@@ -354,7 +356,7 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
|
||||
def _get_size_in_s3(self, rel_path):
|
||||
try:
|
||||
key = self.bucket.get_key(rel_path)
|
||||
key = self._bucket.get_key(rel_path)
|
||||
if key:
|
||||
return key.size
|
||||
except S3ResponseError:
|
||||
@@ -367,19 +369,17 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
# A hackish way of testing if the rel_path is a folder vs a file
|
||||
is_dir = rel_path[-1] == '/'
|
||||
if is_dir:
|
||||
keyresult = self.bucket.get_all_keys(prefix=rel_path)
|
||||
keyresult = self._bucket.get_all_keys(prefix=rel_path)
|
||||
if len(keyresult) > 0:
|
||||
exists = True
|
||||
else:
|
||||
exists = False
|
||||
else:
|
||||
key = Key(self.bucket, rel_path)
|
||||
key = Key(self._bucket, rel_path)
|
||||
exists = key.exists()
|
||||
except S3ResponseError:
|
||||
log.exception("Trouble checking existence of S3 key '%s'", rel_path)
|
||||
return False
|
||||
if rel_path[0] == '/':
|
||||
raise
|
||||
return exists
|
||||
|
||||
def _in_cache(self, rel_path):
|
||||
@@ -427,7 +427,7 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
def _download(self, rel_path):
|
||||
try:
|
||||
log.debug("Pulling key '%s' into cache to %s", rel_path, self._get_cache_path(rel_path))
|
||||
key = self.bucket.get_key(rel_path)
|
||||
key = self._bucket.get_key(rel_path)
|
||||
# Test if cache is large enough to hold the new file
|
||||
if self.cache_size > 0 and key.size > self.cache_size:
|
||||
log.critical("File %s is larger (%s) than the cache size (%s). Cannot download.",
|
||||
@@ -446,7 +446,7 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
key.get_contents_to_filename(self._get_cache_path(rel_path), cb=self._transfer_cb, num_cb=10)
|
||||
return True
|
||||
except S3ResponseError:
|
||||
log.exception("Problem downloading key '%s' from S3 bucket '%s'", rel_path, self.bucket.name)
|
||||
log.exception("Problem downloading key '%s' from S3 bucket '%s'", rel_path, self._bucket.name)
|
||||
return False
|
||||
|
||||
def _push_to_os(self, rel_path, source_file=None, from_string=None):
|
||||
@@ -460,7 +460,7 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
try:
|
||||
source_file = source_file if source_file else self._get_cache_path(rel_path)
|
||||
if os.path.exists(source_file):
|
||||
key = Key(self.bucket, rel_path)
|
||||
key = Key(self._bucket, rel_path)
|
||||
if os.path.getsize(source_file) == 0 and key.exists():
|
||||
log.debug("Wanted to push file '%s' to S3 key '%s' but its size is 0; skipping.", source_file, rel_path)
|
||||
return True
|
||||
@@ -478,7 +478,7 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
cb=self._transfer_cb,
|
||||
num_cb=10)
|
||||
else:
|
||||
multipart_upload(self.s3server, self.bucket, key.name, source_file, mb_size)
|
||||
multipart_upload(self.s3server, self._bucket, key.name, source_file, mb_size)
|
||||
end_time = datetime.now()
|
||||
log.debug("Pushed cache file '%s' to key '%s' (%s bytes transfered in %s sec)",
|
||||
source_file, rel_path, os.path.getsize(source_file), end_time - start_time)
|
||||
@@ -547,7 +547,7 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
alt_name = kwargs.get('alt_name', None)
|
||||
|
||||
# Construct hashed path
|
||||
rel_path = os.path.join(*directory_hash_id(obj.id))
|
||||
rel_path = os.path.join(*directory_hash_id(self._get_object_id(obj)))
|
||||
|
||||
# Optionally append extra_dir
|
||||
if extra_dir is not None:
|
||||
@@ -568,7 +568,7 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
# self._push_to_os(s3_dir, from_string='')
|
||||
# If instructed, create the dataset in cache & in S3
|
||||
if not dir_only:
|
||||
rel_path = os.path.join(rel_path, alt_name if alt_name else "dataset_%s.dat" % obj.id)
|
||||
rel_path = os.path.join(rel_path, alt_name if alt_name else "dataset_%s.dat" % self._get_object_id(obj))
|
||||
open(os.path.join(self.staging_path, rel_path), 'w').close()
|
||||
self._push_to_os(rel_path, from_string='')
|
||||
|
||||
@@ -609,7 +609,7 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
# but requires iterating through each individual key in S3 and deleing it.
|
||||
if entire_dir and extra_dir:
|
||||
shutil.rmtree(self._get_cache_path(rel_path))
|
||||
results = self.bucket.get_all_keys(prefix=rel_path)
|
||||
results = self._bucket.get_all_keys(prefix=rel_path)
|
||||
for key in results:
|
||||
log.debug("Deleting key %s", key.name)
|
||||
key.delete()
|
||||
@@ -619,7 +619,7 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
os.unlink(self._get_cache_path(rel_path))
|
||||
# Delete from S3 as well
|
||||
if self._key_exists(rel_path):
|
||||
key = Key(self.bucket, rel_path)
|
||||
key = Key(self._bucket, rel_path)
|
||||
log.debug("Deleting key %s", key.name)
|
||||
key.delete()
|
||||
return True
|
||||
@@ -707,7 +707,7 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
if self.exists(obj, **kwargs):
|
||||
rel_path = self._construct_path(obj, **kwargs)
|
||||
try:
|
||||
key = Key(self.bucket, rel_path)
|
||||
key = Key(self._bucket, rel_path)
|
||||
return key.generate_url(expires_in=86400) # 24hrs
|
||||
except S3ResponseError:
|
||||
log.exception("Trouble generating URL for dataset '%s'", rel_path)
|
||||
@@ -716,6 +716,14 @@ class S3ObjectStore(ConcreteObjectStore, CloudConfigMixin):
|
||||
def get_store_usage_percent(self):
|
||||
return 0.0
|
||||
|
||||
def shutdown(self):
|
||||
self.running = False
|
||||
thread = getattr(self, 'cache_monitor_thread', None)
|
||||
if thread:
|
||||
log.debug("Shutting down thread")
|
||||
self.sleeper.wake()
|
||||
thread.join(5)
|
||||
|
||||
|
||||
class SwiftObjectStore(S3ObjectStore):
|
||||
"""
|
||||
|
||||
@@ -39,6 +39,13 @@ def validate_email(trans, email, user=None, check_dup=True, allow_empty=False):
|
||||
message = "Email address cannot be more than %d characters in length." % EMAIL_MAX_LEN
|
||||
elif check_dup and trans.sa_session.query(trans.app.model.User).filter(func.lower(trans.app.model.User.table.c.email) == email.lower()).first():
|
||||
message = "User with email '%s' already exists." % email
|
||||
# If the whitelist is not empty filter out any domain not in the list and ignore blacklist.
|
||||
elif trans.app.config.whitelist_content is not None:
|
||||
domain = email.split('@')[1]
|
||||
if len(domain.split('.')) > 2:
|
||||
domain = ('.').join(domain.split('.')[-2:])
|
||||
if domain not in trans.app.config.whitelist_content:
|
||||
message = "Please enter an allowed domain email address for this server."
|
||||
# If the blacklist is not empty filter out the disposable domains.
|
||||
elif trans.app.config.blacklist_content is not None:
|
||||
domain = email.split('@')[1]
|
||||
|
||||
@@ -60,6 +60,7 @@ masthead:
|
||||
# user menu
|
||||
logout: 'Logout'
|
||||
custom_builds: 'Custom Builds'
|
||||
preferences: 'Preferences'
|
||||
histories: 'Histories'
|
||||
pages: 'Pages'
|
||||
|
||||
@@ -67,6 +68,21 @@ masthead:
|
||||
libraries: 'Data Libraries'
|
||||
published_histories: 'Histories'
|
||||
|
||||
preferences:
|
||||
selectors:
|
||||
manage_information: '#edit-preferences-information'
|
||||
current_email: "#user-preferences-current-email"
|
||||
|
||||
change_user_email:
|
||||
selectors:
|
||||
submit: '#submit'
|
||||
|
||||
change_user_address:
|
||||
selectors:
|
||||
address_button:
|
||||
type: xpath
|
||||
selector: '//span[contains(text(), "Insert Address")]'
|
||||
|
||||
history_panel:
|
||||
menu:
|
||||
labels:
|
||||
@@ -294,7 +310,7 @@ workflow_editor:
|
||||
label_input: "[tour_id='__label'] input"
|
||||
annotation_input: "[tour_id='__annotation'] textarea"
|
||||
|
||||
connector_for: "canvas[handle1-id='${source_id}'][handle2-id='${sink_id}']"
|
||||
connector_for: "div[handle1-id='${source_id}'][handle2-id='${sink_id}']"
|
||||
|
||||
connector_destroy_callout: '.delete-terminal'
|
||||
|
||||
|
||||
@@ -215,10 +215,6 @@ class InstallToolDependencyManager(object):
|
||||
if tool_dependency and tool_dependency.status in [self.install_model.ToolDependency.installation_status.INSTALLED,
|
||||
self.install_model.ToolDependency.installation_status.ERROR]:
|
||||
installed_packages.append(tool_dependency)
|
||||
if self.app.config.manage_dependency_relationships:
|
||||
# Add the tool_dependency to the in-memory dictionaries in the installed_repository_manager.
|
||||
self.app.installed_repository_manager.handle_tool_dependency_install(tool_shed_repository,
|
||||
tool_dependency)
|
||||
return installed_packages
|
||||
|
||||
def install_via_fabric(self, tool_shed_repository, tool_dependency, install_dir, package_name=None, custom_fabfile_path=None,
|
||||
@@ -946,10 +942,6 @@ class InstallRepositoryManager(object):
|
||||
basic_util.remove_dir(work_dir)
|
||||
self.update_tool_shed_repository_status(tool_shed_repository,
|
||||
self.install_model.ToolShedRepository.installation_status.INSTALLED)
|
||||
if self.app.config.manage_dependency_relationships:
|
||||
# Add the installed repository and any tool dependencies to the in-memory dictionaries
|
||||
# in the installed_repository_manager.
|
||||
self.app.installed_repository_manager.handle_repository_install(tool_shed_repository)
|
||||
else:
|
||||
# An error occurred while cloning the repository, so reset everything necessary to enable another attempt.
|
||||
repository_util.set_repository_attributes(self.app,
|
||||
|
||||
@@ -79,9 +79,6 @@ class InstalledRepositoryManager(object):
|
||||
# whose values are a list of tuples defining tool_dependency objects (whose status is 'Installed') that require the key
|
||||
# at runtime. The value defines the entire tool dependency tree.
|
||||
self.installed_runtime_dependent_tool_dependencies_of_installed_tool_dependencies = {}
|
||||
if app.config.manage_dependency_relationships:
|
||||
# Load defined dependency relationships for installed tool shed repositories and their contents.
|
||||
self.load_dependency_relationships()
|
||||
|
||||
def activate_repository(self, repository):
|
||||
"""Activate an installed tool shed repository that has been marked as deactivated."""
|
||||
@@ -651,90 +648,6 @@ class InstalledRepositoryManager(object):
|
||||
deleted_tool_dependency_names.append(original_dependency_val_dict['name'])
|
||||
return updated_tool_dependency_names, deleted_tool_dependency_names
|
||||
|
||||
def handle_repository_install(self, repository):
|
||||
"""Load the dependency relationships for a repository that was just installed or reinstalled."""
|
||||
# Populate self.repository_dependencies_of_installed_repositories.
|
||||
self.add_entry_to_repository_dependencies_of_installed_repositories(repository)
|
||||
# Populate self.installed_repository_dependencies_of_installed_repositories.
|
||||
self.add_entry_to_installed_repository_dependencies_of_installed_repositories(repository)
|
||||
# Populate self.tool_dependencies_of_installed_repositories.
|
||||
self.add_entry_to_tool_dependencies_of_installed_repositories(repository)
|
||||
# Populate self.installed_tool_dependencies_of_installed_repositories.
|
||||
self.add_entry_to_installed_tool_dependencies_of_installed_repositories(repository)
|
||||
for tool_dependency in repository.tool_dependencies:
|
||||
# Populate self.runtime_tool_dependencies_of_installed_tool_dependencies.
|
||||
self.add_entry_to_runtime_tool_dependencies_of_installed_tool_dependencies(tool_dependency)
|
||||
# Populate self.installed_runtime_dependent_tool_dependencies_of_installed_tool_dependencies.
|
||||
self.add_entry_to_installed_runtime_dependent_tool_dependencies_of_installed_tool_dependencies(tool_dependency)
|
||||
|
||||
def handle_repository_uninstall(self, repository):
|
||||
"""Remove the dependency relationships for a repository that was just uninstalled."""
|
||||
for tool_dependency in repository.tool_dependencies:
|
||||
tool_dependency_tup = self.get_tool_dependency_tuple_for_installed_repository_manager(tool_dependency)
|
||||
# Remove this tool_dependency from all values in
|
||||
# self.installed_runtime_dependent_tool_dependencies_of_installed_tool_dependencies
|
||||
altered_installed_runtime_dependent_tool_dependencies_of_installed_tool_dependencies = {}
|
||||
for (td_tup, installed_runtime_dependent_tool_dependency_tups) in self.installed_runtime_dependent_tool_dependencies_of_installed_tool_dependencies.items():
|
||||
if tool_dependency_tup in installed_runtime_dependent_tool_dependency_tups:
|
||||
# Remove the tool_dependency from the list.
|
||||
installed_runtime_dependent_tool_dependency_tups.remove(tool_dependency_tup)
|
||||
# Add the possibly altered list to the altered dictionary.
|
||||
altered_installed_runtime_dependent_tool_dependencies_of_installed_tool_dependencies[td_tup] = \
|
||||
installed_runtime_dependent_tool_dependency_tups
|
||||
self.installed_runtime_dependent_tool_dependencies_of_installed_tool_dependencies = \
|
||||
altered_installed_runtime_dependent_tool_dependencies_of_installed_tool_dependencies
|
||||
# Remove the entry for this tool_dependency from self.runtime_tool_dependencies_of_installed_tool_dependencies.
|
||||
self.remove_entry_from_runtime_tool_dependencies_of_installed_tool_dependencies(tool_dependency)
|
||||
# Remove the entry for this tool_dependency from
|
||||
# self.installed_runtime_dependent_tool_dependencies_of_installed_tool_dependencies.
|
||||
self.remove_entry_from_installed_runtime_dependent_tool_dependencies_of_installed_tool_dependencies(tool_dependency)
|
||||
# Remove this repository's entry from self.installed_tool_dependencies_of_installed_repositories.
|
||||
self.remove_entry_from_installed_tool_dependencies_of_installed_repositories(repository)
|
||||
# Remove this repository's entry from self.tool_dependencies_of_installed_repositories
|
||||
self.remove_entry_from_tool_dependencies_of_installed_repositories(repository)
|
||||
# Remove this repository's entry from self.installed_repository_dependencies_of_installed_repositories.
|
||||
self.remove_entry_from_installed_repository_dependencies_of_installed_repositories(repository)
|
||||
# Remove this repository's entry from self.repository_dependencies_of_installed_repositories.
|
||||
self.remove_entry_from_repository_dependencies_of_installed_repositories(repository)
|
||||
|
||||
def handle_tool_dependency_install(self, repository, tool_dependency):
|
||||
"""Load the dependency relationships for a tool dependency that was just installed independently of its containing repository."""
|
||||
# The received repository must have a status of 'Installed'. The value of tool_dependency.status will either be
|
||||
# 'Installed' or 'Error', but we only need to change the in-memory dictionaries if it is 'Installed'.
|
||||
if tool_dependency.is_installed:
|
||||
# Populate self.installed_runtime_dependent_tool_dependencies_of_installed_tool_dependencies.
|
||||
self.add_entry_to_installed_runtime_dependent_tool_dependencies_of_installed_tool_dependencies(tool_dependency)
|
||||
# Populate self.installed_tool_dependencies_of_installed_repositories.
|
||||
repository_tup = self.get_repository_tuple_for_installed_repository_manager(repository)
|
||||
tool_dependency_tup = self.get_tool_dependency_tuple_for_installed_repository_manager(tool_dependency)
|
||||
if repository_tup in self.installed_tool_dependencies_of_installed_repositories:
|
||||
self.installed_tool_dependencies_of_installed_repositories[repository_tup].append(tool_dependency_tup)
|
||||
else:
|
||||
self.installed_tool_dependencies_of_installed_repositories[repository_tup] = [tool_dependency_tup]
|
||||
|
||||
def load_dependency_relationships(self):
|
||||
"""Load relationships for all installed repositories and tool dependencies into in-memnory dictionaries."""
|
||||
# Get the list of installed tool shed repositories.
|
||||
for repository in self.context.query(self.app.install_model.ToolShedRepository) \
|
||||
.filter(self.app.install_model.ToolShedRepository.table.c.status ==
|
||||
self.app.install_model.ToolShedRepository.installation_status.INSTALLED):
|
||||
# Populate self.repository_dependencies_of_installed_repositories.
|
||||
self.add_entry_to_repository_dependencies_of_installed_repositories(repository)
|
||||
# Populate self.installed_repository_dependencies_of_installed_repositories.
|
||||
self.add_entry_to_installed_repository_dependencies_of_installed_repositories(repository)
|
||||
# Populate self.tool_dependencies_of_installed_repositories.
|
||||
self.add_entry_to_tool_dependencies_of_installed_repositories(repository)
|
||||
# Populate self.installed_tool_dependencies_of_installed_repositories.
|
||||
self.add_entry_to_installed_tool_dependencies_of_installed_repositories(repository)
|
||||
# Get the list of installed tool dependencies.
|
||||
for tool_dependency in self.context.query(self.app.install_model.ToolDependency) \
|
||||
.filter(self.app.install_model.ToolDependency.table.c.status ==
|
||||
self.app.install_model.ToolDependency.installation_status.INSTALLED):
|
||||
# Populate self.runtime_tool_dependencies_of_installed_tool_dependencies.
|
||||
self.add_entry_to_runtime_tool_dependencies_of_installed_tool_dependencies(tool_dependency)
|
||||
# Populate self.installed_runtime_dependent_tool_dependencies_of_installed_tool_dependencies.
|
||||
self.add_entry_to_installed_runtime_dependent_tool_dependencies_of_installed_tool_dependencies(tool_dependency)
|
||||
|
||||
def load_proprietary_datatypes(self):
|
||||
cdl = custom_datatype_manager.CustomDatatypeLoader(self.app)
|
||||
for tool_shed_repository in self.context.query(self.install_model.ToolShedRepository) \
|
||||
@@ -814,10 +727,6 @@ class InstalledRepositoryManager(object):
|
||||
if remove_from_disk:
|
||||
repository.status = self.app.install_model.ToolShedRepository.installation_status.UNINSTALLED
|
||||
repository.error_message = None
|
||||
if self.app.config.manage_dependency_relationships:
|
||||
# Remove the uninstalled repository and any tool dependencies from the in-memory dictionaries in the
|
||||
# installed_repository_manager.
|
||||
self.handle_repository_uninstall(repository)
|
||||
else:
|
||||
repository.status = self.app.install_model.ToolShedRepository.installation_status.DEACTIVATED
|
||||
self.app.install_model.context.current.add(repository)
|
||||
|
||||
@@ -1913,7 +1913,7 @@ class DataToolParameter(BaseDataToolParameter):
|
||||
ref = getattr(ref, attribute)
|
||||
if call_attribute:
|
||||
ref = ref()
|
||||
return ref
|
||||
return str(ref)
|
||||
|
||||
def to_dict(self, trans, other_values={}):
|
||||
# create dictionary and fill default parameters
|
||||
|
||||
@@ -1217,9 +1217,10 @@ class BaseGalaxyToolBox(AbstractToolBox):
|
||||
return
|
||||
app_config_dict = self.app.config.config_dict
|
||||
conf_file = app_config_dict.get("dependency_resolvers_config_file")
|
||||
default_tool_dependency_dir = os.path.join(self.app.config.data_dir, self.app.config.appschema['tool_dependency_dir'].get('default'))
|
||||
self.dependency_manager = build_dependency_manager(app_config_dict=app_config_dict, conf_file=conf_file,
|
||||
default_tool_dependency_dir=default_tool_dependency_dir)
|
||||
default_tool_dependency_dir = os.path.join(
|
||||
self.app.config.data_dir, self.app.config.schema.defaults['tool_dependency_dir'])
|
||||
self.dependency_manager = build_dependency_manager(
|
||||
app_config_dict=app_config_dict, conf_file=conf_file, default_tool_dependency_dir=default_tool_dependency_dir)
|
||||
|
||||
def reload_dependency_manager(self):
|
||||
self._init_dependency_manager()
|
||||
|
||||
@@ -10,13 +10,6 @@ from yaml.constructor import ConstructorError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
OPTION_DEFAULTS = {
|
||||
"type": "str",
|
||||
"unknown_option": False,
|
||||
"default": None,
|
||||
"desc": None,
|
||||
}
|
||||
|
||||
|
||||
class OrderedLoader(yaml.SafeLoader):
|
||||
# This class was pulled out of ordered_load() for the sake of
|
||||
|
||||
@@ -537,15 +537,6 @@ mapping:
|
||||
server process should be able to check for repository updates. The
|
||||
setting for hours_between_check should be an integer between 1 and 24.
|
||||
|
||||
manage_dependency_relationships:
|
||||
type: bool
|
||||
default: false
|
||||
required: false
|
||||
desc: |
|
||||
Enable use of an in-memory registry with bi-directional relationships between
|
||||
repositories (i.e., in addition to lists of dependencies for a repository,
|
||||
keep an in-memory registry of dependent items for each repository.
|
||||
|
||||
tool_data_table_config_path:
|
||||
type: str
|
||||
default: config/tool_data_table_conf.xml
|
||||
@@ -911,6 +902,18 @@ mapping:
|
||||
|
||||
Example value 'config/disposable_email_blacklist.conf'
|
||||
|
||||
whitelist_file:
|
||||
type: str
|
||||
default: disposable_email_whitelist.conf
|
||||
path_resolves_to: config_dir
|
||||
required: false
|
||||
desc: |
|
||||
E-mail domains whitelist is used to specify allowed email address domains.
|
||||
If the list is non-empty and a user attempts registration using an email
|
||||
address belonging to a domain that is not on the list, registration will be
|
||||
enied. This is a more restrictive option than <blacklist_file>, and therefore,
|
||||
in case <whitelist_file> is defined, <blacklist_file> will be ignored.
|
||||
|
||||
registration_warning_message:
|
||||
type: str
|
||||
default: >-
|
||||
@@ -2417,6 +2420,15 @@ mapping:
|
||||
Details" option in the history. This option also exposes the command line to
|
||||
non-administrative users. Administrators can always see dataset paths.
|
||||
|
||||
job_metrics_config_file:
|
||||
type: str
|
||||
default: job_metrics_conf.xml
|
||||
required: false
|
||||
desc: |
|
||||
XML config file that contains the job metric collection configuration.
|
||||
|
||||
The value of this option will be resolved with respect to <config_dir>.
|
||||
|
||||
expose_potentially_sensitive_job_metrics:
|
||||
type: bool
|
||||
default: false
|
||||
@@ -2728,7 +2740,7 @@ mapping:
|
||||
default: markdown_export_pages.css
|
||||
required: false
|
||||
desc: |
|
||||
CSS file to apply to "Galaxy Page" exports to PDF. Generally prefer
|
||||
CSS file to apply to "Galaxy Page" exports to PDF. Generally prefer
|
||||
markdown_export_css, but this is here for deployments that
|
||||
would like to tailor different kinds of exports.
|
||||
|
||||
@@ -2737,7 +2749,7 @@ mapping:
|
||||
default: markdown_export_invocation_reports.css
|
||||
required: false
|
||||
desc: |
|
||||
CSS file to apply to invocation report exports to PDF. Generally prefer
|
||||
CSS file to apply to invocation report exports to PDF. Generally prefer
|
||||
markdown_export_css, but this is here for deployments that
|
||||
would like to tailor different kinds of exports.
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
from .framework import (
|
||||
selenium_test,
|
||||
SeleniumTestCase,
|
||||
)
|
||||
|
||||
|
||||
class ManageInformationTestCase(SeleniumTestCase):
|
||||
|
||||
@selenium_test
|
||||
def test_change_email(self):
|
||||
def assert_email(email_to_check):
|
||||
self.assertTrue(email_to_check == self.driver.find_element_by_id("user-preferences-current-email").text)
|
||||
|
||||
email = self._get_random_email()
|
||||
self.register(email)
|
||||
self.navigate_to_user_preferences()
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
|
||||
# rendered email should be equal
|
||||
assert_email(email)
|
||||
|
||||
self.components.preferences.manage_information.wait_for_and_click()
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
|
||||
new_email = self._get_random_email()
|
||||
# new email should be different from initially registered
|
||||
self.assertTrue(email != new_email)
|
||||
email_input_field = self.driver.find_element_by_css_selector("[tour_id='email'] input")
|
||||
|
||||
self.clear_input_field_and_write(email_input_field, new_email)
|
||||
self.components.change_user_email.submit.wait_for_and_click()
|
||||
|
||||
# UX_RENDER time sometimes is not enough
|
||||
self.sleep_for(self.wait_types.UX_TRANSITION)
|
||||
|
||||
# email should be changed
|
||||
assert_email(new_email)
|
||||
|
||||
@selenium_test
|
||||
def test_public_name(self):
|
||||
def get_name_input_field():
|
||||
return self.driver.find_element_by_css_selector("[tour_id='username'] input")
|
||||
|
||||
def assert_public_name(expected_name):
|
||||
self.assertTrue(expected_name == get_name_input_field().get_attribute('value'))
|
||||
|
||||
public_name = "user-public-name"
|
||||
self.register(username=public_name)
|
||||
self.navigate_to_manage_information()
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
|
||||
# make sure that public name is the same as registered
|
||||
assert_public_name(public_name)
|
||||
new_public_name = "new-public-name"
|
||||
# change public name
|
||||
self.clear_input_field_and_write(get_name_input_field(), new_public_name)
|
||||
self.components.change_user_email.submit.wait_for_and_click()
|
||||
|
||||
self.sleep_for(self.wait_types.UX_TRANSITION)
|
||||
|
||||
self.navigate_to_manage_information()
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
# public name field should render new public name
|
||||
assert_public_name(new_public_name)
|
||||
|
||||
@selenium_test
|
||||
def test_user_address(self):
|
||||
def get_address_form():
|
||||
return self.driver.find_element_by_css_selector("div.ui-portlet-section > div.portlet-content")
|
||||
|
||||
self.register(self._get_random_email())
|
||||
self.navigate_to_manage_information()
|
||||
self.components.change_user_address.address_button.wait_for_and_click()
|
||||
|
||||
address_field_labels = ["Short address description", "Name", "Institution", "Address", "City",
|
||||
"State/Province/Region", "Postal Code", "Country", "Phone"]
|
||||
|
||||
address_fields = {}
|
||||
# fill address fields with random data
|
||||
for input_field_label in address_field_labels:
|
||||
input_value = self._get_random_name(prefix=input_field_label)
|
||||
address_fields[input_field_label] = input_value
|
||||
input_field = self.get_address_input_field(get_address_form(), input_field_label)
|
||||
self.clear_input_field_and_write(input_field, input_value)
|
||||
# save new address
|
||||
self.components.change_user_email.submit.wait_for_and_click()
|
||||
self.sleep_for(self.wait_types.UX_TRANSITION)
|
||||
|
||||
self.navigate_to_manage_information()
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
|
||||
# check if address was saved correctly
|
||||
for input_field_label in address_fields.keys():
|
||||
input_field = self.get_address_input_field(get_address_form(), input_field_label)
|
||||
self.assertTrue(input_field.get_attribute('value') == address_fields[input_field_label])
|
||||
|
||||
def navigate_to_user_preferences(self):
|
||||
self.home()
|
||||
self.click_masthead_user()
|
||||
self.components.masthead.preferences.wait_for_and_click()
|
||||
|
||||
def navigate_to_manage_information(self):
|
||||
self.navigate_to_user_preferences()
|
||||
self.components.preferences.manage_information.wait_for_and_click()
|
||||
|
||||
def clear_input_field_and_write(self, element, new_input_text):
|
||||
element.clear()
|
||||
element.send_keys(new_input_text)
|
||||
|
||||
def get_address_input_field(self, address_form, input_field_label):
|
||||
return address_form.find_element_by_css_selector("[data-label='" + input_field_label + "'] > input")
|
||||
@@ -10,6 +10,7 @@ from datetime import timedelta
|
||||
from six.moves import configparser
|
||||
|
||||
from galaxy.config import BaseAppConfiguration
|
||||
from galaxy.config.schema import AppSchema
|
||||
from galaxy.util import string_as_bool
|
||||
from galaxy.version import VERSION, VERSION_MAJOR
|
||||
from galaxy.web.formatting import expand_pretty_datetime_format
|
||||
@@ -19,6 +20,9 @@ log = logging.getLogger(__name__)
|
||||
ts_webapp_path = os.path.abspath(os.path.dirname(__file__))
|
||||
templates_path = os.path.join(ts_webapp_path, "templates")
|
||||
|
||||
TOOLSHED_APP_NAME = 'tool_shed'
|
||||
TOOLSHED_CONFIG_SCHEMA_PATH = 'lib/tool_shed/webapp/config_schema.yml'
|
||||
|
||||
|
||||
def resolve_path(path, root):
|
||||
"""If 'path' is relative make absolute by prepending 'root'"""
|
||||
@@ -34,6 +38,18 @@ class ConfigurationError(Exception):
|
||||
class ToolShedAppConfiguration(BaseAppConfiguration):
|
||||
default_config_file_name = 'tool_shed.yml'
|
||||
|
||||
def _load_schema(self):
|
||||
return AppSchema(TOOLSHED_CONFIG_SCHEMA_PATH, TOOLSHED_APP_NAME)
|
||||
|
||||
def _update_raw_config_from_kwargs(self, kwargs):
|
||||
pass # Intentionally does nothing. To enable, remove + call base __init__().
|
||||
|
||||
def _create_attributes_from_raw_config(self):
|
||||
pass # Intentionally does nothing. To enable, remove + call base __init__().
|
||||
|
||||
def _resolve_paths(self, paths_to_resolve):
|
||||
pass # Intentionally does nothing. To enable, remove + call base __init__().
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.config_dict = kwargs
|
||||
self.root = kwargs.get('root_dir', '.')
|
||||
|
||||
@@ -482,6 +482,18 @@ mapping:
|
||||
disposable email address during the registration. If their address domain
|
||||
matches any domain in the blacklist, they are refused the registration.
|
||||
|
||||
whitelist_file:
|
||||
type: str
|
||||
default: disposable_email_whitelist.conf
|
||||
path_resolves_to: config_dir
|
||||
required: false
|
||||
desc: |
|
||||
E-mail domains whitelist is used to specify allowed email address domains.
|
||||
If the list is non-empty and a user attempts registration using an email
|
||||
address belonging to a domain that is not on the list, registration will be
|
||||
enied. This is a more restrictive option than <blacklist_file>, and therefore,
|
||||
in case <whitelist_file> is defined, <blacklist_file> will be ignored.
|
||||
|
||||
brand:
|
||||
type: str
|
||||
default: null
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,113 @@
|
||||
|
||||
import os
|
||||
import string
|
||||
import subprocess
|
||||
|
||||
from galaxy_test.driver import integration_util
|
||||
|
||||
OBJECT_STORE_HOST = os.environ.get('GALAXY_INTEGRATION_OBJECT_STORE_HOST', '127.0.0.1')
|
||||
OBJECT_STORE_PORT = int(os.environ.get('GALAXY_INTEGRATION_OBJECT_STORE_PORT', 9000))
|
||||
OBJECT_STORE_ACCESS_KEY = os.environ.get('GALAXY_INTEGRATION_OBJECT_STORE_ACCESS_KEY', 'minioadmin')
|
||||
OBJECT_STORE_SECRET_KEY = os.environ.get('GALAXY_INTEGRATION_OBJECT_STORE_SECRET_KEY', 'minioadmin')
|
||||
OBJECT_STORE_CONFIG = string.Template("""
|
||||
<object_store type="hierarchical" id="primary">
|
||||
<backends>
|
||||
<object_store id="swifty" type="swift" weight="1" order="0">
|
||||
<auth access_key="${access_key}" secret_key="${secret_key}" />
|
||||
<bucket name="galaxy" use_reduced_redundancy="False" max_chunk_size="250"/>
|
||||
<connection host="${host}" port="${port}" is_secure="False" conn_path="" multipart="True"/>
|
||||
<cache path="${temp_directory}/object_store_cache" size="1000" />
|
||||
<extra_dir type="job_work" path="${temp_directory}/job_working_directory_swift"/>
|
||||
<extra_dir type="temp" path="${temp_directory}/tmp_swift"/>
|
||||
</object_store>
|
||||
</backends>
|
||||
</object_store>
|
||||
""")
|
||||
TEST_TOOL_IDS = [
|
||||
"multi_output",
|
||||
"multi_output_configured",
|
||||
"multi_output_assign_primary",
|
||||
"multi_output_recurse",
|
||||
"tool_provided_metadata_1",
|
||||
"tool_provided_metadata_2",
|
||||
"tool_provided_metadata_3",
|
||||
"tool_provided_metadata_4",
|
||||
"tool_provided_metadata_5",
|
||||
"tool_provided_metadata_6",
|
||||
"tool_provided_metadata_7",
|
||||
"tool_provided_metadata_8",
|
||||
"tool_provided_metadata_9",
|
||||
"tool_provided_metadata_10",
|
||||
"tool_provided_metadata_11",
|
||||
"tool_provided_metadata_12",
|
||||
"composite_output",
|
||||
"composite_output_tests",
|
||||
"metadata",
|
||||
"metadata_bam",
|
||||
"output_format",
|
||||
"output_auto_format",
|
||||
]
|
||||
|
||||
|
||||
def start_minio(container_name):
|
||||
minio_start_args = [
|
||||
'docker',
|
||||
'run',
|
||||
'-p',
|
||||
'{port}:9000'.format(port=OBJECT_STORE_PORT),
|
||||
'-d',
|
||||
'--name',
|
||||
container_name,
|
||||
# '--rm',
|
||||
'minio/minio:latest',
|
||||
'server',
|
||||
'/tmp/data']
|
||||
subprocess.check_call(minio_start_args)
|
||||
|
||||
|
||||
def stop_minio(container_name):
|
||||
subprocess.check_call(['docker', 'rm', '-f', container_name])
|
||||
|
||||
|
||||
@integration_util.skip_unless_docker()
|
||||
class SwiftObjectStoreIntegrationTestCase(integration_util.IntegrationTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.container_name = "%s_container" % cls.__name__
|
||||
start_minio(cls.container_name)
|
||||
super(SwiftObjectStoreIntegrationTestCase, cls).setUpClass()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
stop_minio(cls.container_name)
|
||||
super(SwiftObjectStoreIntegrationTestCase, cls).tearDownClass()
|
||||
|
||||
@classmethod
|
||||
def handle_galaxy_config_kwds(cls, config):
|
||||
temp_directory = cls._test_driver.mkdtemp()
|
||||
cls.object_stores_parent = temp_directory
|
||||
config_path = os.path.join(temp_directory, "object_store_conf.xml")
|
||||
config["object_store_store_by"] = "uuid"
|
||||
# This doesn't quite work yet, fails with extra_files_path
|
||||
# config["metadata_strategy"] = "extended"
|
||||
config["outpus_to_working_dir"] = True
|
||||
config["retry_metadata_internally"] = False
|
||||
with open(config_path, "w") as f:
|
||||
f.write(
|
||||
OBJECT_STORE_CONFIG.safe_substitute(
|
||||
{
|
||||
"temp_directory": temp_directory,
|
||||
"host": OBJECT_STORE_HOST,
|
||||
"port": OBJECT_STORE_PORT,
|
||||
"access_key": OBJECT_STORE_ACCESS_KEY,
|
||||
"secret_key": OBJECT_STORE_SECRET_KEY,
|
||||
}
|
||||
)
|
||||
)
|
||||
config["object_store_config_file"] = config_path
|
||||
|
||||
|
||||
instance = integration_util.integration_module_instance(SwiftObjectStoreIntegrationTestCase)
|
||||
|
||||
test_tools = integration_util.integration_tool_runner(TEST_TOOL_IDS)
|
||||
@@ -178,6 +178,7 @@ DO_NOT_TEST = [
|
||||
'id_secret', # broken: default overridden
|
||||
'job_config', # no obvious testable defaults
|
||||
'job_config_file', # broken: remove 'config/' prefix from schema
|
||||
'job_metrics_config_file',
|
||||
'job_working_directory', # broken; may or may not be able to test
|
||||
'library_import_dir', # broken: default overridden
|
||||
'logging', # mapping loaded in config/
|
||||
@@ -253,7 +254,7 @@ def get_config_data():
|
||||
|
||||
create_driver() # create + setup DRIVER
|
||||
parent_dirs = load_parent_dirs() # called after DRIVER is setup
|
||||
items = ((k, v) for k, v in DRIVER.app.config.appschema.items() if k not in DO_NOT_TEST)
|
||||
items = ((k, v) for k, v in DRIVER.app.config.schema.app_schema.items() if k not in DO_NOT_TEST)
|
||||
for key, data in items:
|
||||
expected_value = get_expected(key, data, parent_dirs)
|
||||
loaded_value = getattr(DRIVER.app.config, key)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from galaxy_test.driver import integration_util
|
||||
|
||||
|
||||
class ConfigSchemaTestCase(integration_util.IntegrationTestCase):
|
||||
|
||||
def test_schema_path_resolution_graph(self):
|
||||
# Run schema's validation method; throws error if schema invalid
|
||||
schema = self._app.config.schema
|
||||
schema.validate_path_resolution_graph()
|
||||
@@ -1,9 +1,10 @@
|
||||
import pytest
|
||||
|
||||
from galaxy.config import GalaxyAppConfiguration
|
||||
from galaxy.config.schema import AppSchema
|
||||
|
||||
|
||||
MOCK_PROPERTIES = {
|
||||
MOCK_SCHEMA = {
|
||||
'property1': {'default': 'a', 'type': 'str'}, # str
|
||||
'property2': {'default': 1, 'type': 'int'}, # int
|
||||
'property3': {'default': 1.0, 'type': 'float'}, # float
|
||||
@@ -13,17 +14,14 @@ MOCK_PROPERTIES = {
|
||||
}
|
||||
|
||||
|
||||
def get_schema(app_mapping):
|
||||
return {'mapping': {'galaxy': {'mapping': app_mapping}}}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_init(monkeypatch):
|
||||
|
||||
def mock_load_schema(self):
|
||||
self.appschema = MOCK_PROPERTIES
|
||||
|
||||
def mock_process_config(self, kwargs):
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_load_schema', mock_load_schema)
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_process_config', mock_process_config)
|
||||
monkeypatch.setattr(AppSchema, '_read_schema', lambda a, b: get_schema(MOCK_SCHEMA))
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_process_config', lambda a, b: None)
|
||||
|
||||
|
||||
def test_load_config_from_schema(mock_init):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from galaxy.config import GalaxyAppConfiguration
|
||||
from galaxy.config.schema import AppSchema
|
||||
from galaxy.exceptions import ConfigurationError
|
||||
|
||||
|
||||
@@ -15,14 +16,11 @@ from galaxy.exceptions import ConfigurationError
|
||||
# whereas an invalid configuration raises an error.
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config(monkeypatch):
|
||||
def mock_process_config(self, kwargs):
|
||||
pass
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_process_config', mock_process_config)
|
||||
def get_schema(app_mapping):
|
||||
return {'mapping': {'galaxy': {'mapping': app_mapping}}}
|
||||
|
||||
|
||||
def test_basecase(mock_config, monkeypatch):
|
||||
def test_basecase(monkeypatch):
|
||||
# Check that a valid graph is loaded correctly (this graph has 2 components)
|
||||
mock_schema = {
|
||||
'component1_path0': {
|
||||
@@ -48,12 +46,9 @@ def test_basecase(mock_config, monkeypatch):
|
||||
'default': 'value4',
|
||||
'path_resolves_to': 'component2_path0',
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
def mock_load_schema(self):
|
||||
self.appschema = mock_schema
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_load_schema', mock_load_schema)
|
||||
monkeypatch.setattr(AppSchema, '_read_schema', lambda a, b: get_schema(mock_schema))
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_process_config', lambda a, b: None)
|
||||
|
||||
config = GalaxyAppConfiguration()
|
||||
assert config.component1_path0 == 'value0'
|
||||
@@ -63,7 +58,7 @@ def test_basecase(mock_config, monkeypatch):
|
||||
assert config.component2_path1 == 'value3/value4'
|
||||
|
||||
|
||||
def test_resolves_to_invalid_property(mock_config, monkeypatch):
|
||||
def test_resolves_to_invalid_property(monkeypatch):
|
||||
# 'path_resolves_to' should point to an existing property in the schema
|
||||
mock_schema = {
|
||||
'path0': {
|
||||
@@ -76,17 +71,13 @@ def test_resolves_to_invalid_property(mock_config, monkeypatch):
|
||||
'path_resolves_to': 'invalid', # invalid
|
||||
},
|
||||
}
|
||||
|
||||
def mock_load_schema(self):
|
||||
self.appschema = mock_schema
|
||||
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_load_schema', mock_load_schema)
|
||||
monkeypatch.setattr(AppSchema, '_read_schema', lambda a, b: get_schema(mock_schema))
|
||||
|
||||
with pytest.raises(ConfigurationError):
|
||||
GalaxyAppConfiguration()
|
||||
AppSchema(None, 'galaxy').validate_path_resolution_graph()
|
||||
|
||||
|
||||
def test_path_resolution_cycle(mock_config, monkeypatch):
|
||||
def test_path_resolution_cycle(monkeypatch):
|
||||
# Must be a DAG, but this one has a cycle
|
||||
mock_schema = {
|
||||
'path0': {
|
||||
@@ -105,17 +96,13 @@ def test_path_resolution_cycle(mock_config, monkeypatch):
|
||||
'path_resolves_to': 'path1',
|
||||
},
|
||||
}
|
||||
|
||||
def mock_load_schema(self):
|
||||
self.appschema = mock_schema
|
||||
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_load_schema', mock_load_schema)
|
||||
monkeypatch.setattr(AppSchema, '_read_schema', lambda a, b: get_schema(mock_schema))
|
||||
|
||||
with pytest.raises(ConfigurationError):
|
||||
GalaxyAppConfiguration()
|
||||
AppSchema(None, 'galaxy').validate_path_resolution_graph()
|
||||
|
||||
|
||||
def test_path_invalid_type(mock_config, monkeypatch):
|
||||
def test_path_invalid_type(monkeypatch):
|
||||
# Paths should be strings
|
||||
mock_schema = {
|
||||
'path0': {
|
||||
@@ -128,17 +115,13 @@ def test_path_invalid_type(mock_config, monkeypatch):
|
||||
'path_resolves_to': 'path0',
|
||||
},
|
||||
}
|
||||
|
||||
def mock_load_schema(self):
|
||||
self.appschema = mock_schema
|
||||
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_load_schema', mock_load_schema)
|
||||
monkeypatch.setattr(AppSchema, '_read_schema', lambda a, b: get_schema(mock_schema))
|
||||
|
||||
with pytest.raises(ConfigurationError):
|
||||
GalaxyAppConfiguration()
|
||||
AppSchema(None, 'galaxy').validate_path_resolution_graph()
|
||||
|
||||
|
||||
def test_resolves_to_invalid_type(mock_config, monkeypatch):
|
||||
def test_resolves_to_invalid_type(monkeypatch):
|
||||
# Paths should be strings
|
||||
mock_schema = {
|
||||
'path0': {
|
||||
@@ -151,17 +134,13 @@ def test_resolves_to_invalid_type(mock_config, monkeypatch):
|
||||
'path_resolves_to': 'path0',
|
||||
},
|
||||
}
|
||||
|
||||
def mock_load_schema(self):
|
||||
self.appschema = mock_schema
|
||||
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_load_schema', mock_load_schema)
|
||||
monkeypatch.setattr(AppSchema, '_read_schema', lambda a, b: get_schema(mock_schema))
|
||||
|
||||
with pytest.raises(ConfigurationError):
|
||||
GalaxyAppConfiguration()
|
||||
AppSchema(None, 'galaxy').validate_path_resolution_graph()
|
||||
|
||||
|
||||
def test_resolves_with_empty_component(mock_config, monkeypatch):
|
||||
def test_resolves_with_empty_component(monkeypatch):
|
||||
# A path can be None (root path is never None; may be asigned elsewhere)
|
||||
mock_schema = {
|
||||
'path0': {
|
||||
@@ -178,11 +157,8 @@ def test_resolves_with_empty_component(mock_config, monkeypatch):
|
||||
'path_resolves_to': 'path1',
|
||||
},
|
||||
}
|
||||
|
||||
def mock_load_schema(self):
|
||||
self.appschema = mock_schema
|
||||
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_load_schema', mock_load_schema)
|
||||
monkeypatch.setattr(AppSchema, '_read_schema', lambda a, b: get_schema(mock_schema))
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_process_config', lambda a, b: None)
|
||||
|
||||
config = GalaxyAppConfiguration()
|
||||
assert config.path0 == 'value0'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from galaxy.config import GalaxyAppConfiguration
|
||||
from galaxy.config.schema import AppSchema
|
||||
|
||||
|
||||
MOCK_DEPRECATED_DIRS = {
|
||||
@@ -35,34 +36,23 @@ MOCK_SCHEMA = {
|
||||
}
|
||||
|
||||
|
||||
def get_schema(app_mapping):
|
||||
return {'mapping': {'galaxy': {'mapping': app_mapping}}}
|
||||
|
||||
|
||||
def test_deprecated_prefixes_set_correctly(monkeypatch):
|
||||
# Before we mock them, check that correct values are assigned
|
||||
def mock_load_schema(self):
|
||||
self.appschema = {}
|
||||
|
||||
def mock_process_config(self, kwargs):
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_load_schema', mock_load_schema)
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_process_config', mock_process_config)
|
||||
monkeypatch.setattr(AppSchema, '_read_schema', lambda a, b: get_schema(MOCK_SCHEMA))
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_process_config', lambda a, b: None)
|
||||
|
||||
config = GalaxyAppConfiguration()
|
||||
expected_deprecated_dirs = {'config_dir': 'config', 'data_dir': 'database'}
|
||||
|
||||
assert config.deprecated_dirs == expected_deprecated_dirs
|
||||
assert config.deprecated_dirs == {'config_dir': 'config', 'data_dir': 'database'}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_init(monkeypatch):
|
||||
|
||||
def mock_load_schema(self):
|
||||
self.appschema = MOCK_SCHEMA
|
||||
|
||||
def mock_process_config(self, kwargs):
|
||||
pass # because we're done before this is called
|
||||
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_load_schema', mock_load_schema)
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_process_config', mock_process_config)
|
||||
monkeypatch.setattr(AppSchema, '_read_schema', lambda a, b: get_schema(MOCK_SCHEMA))
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_process_config', lambda a, b: None)
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, 'deprecated_dirs', MOCK_DEPRECATED_DIRS)
|
||||
|
||||
|
||||
|
||||
@@ -1,33 +1,41 @@
|
||||
import pytest
|
||||
|
||||
from galaxy import config
|
||||
from galaxy.config import GalaxyAppConfiguration
|
||||
from galaxy.config import reload_config_options
|
||||
from galaxy.config.schema import AppSchema
|
||||
|
||||
|
||||
R1, R2, N1, N2 = 'reloadable1', 'reloadable2', 'nonrelodable1', 'nonreloadable2' # config options
|
||||
|
||||
|
||||
class MockGalaxyAppConfiguration():
|
||||
def __init__(self, properties):
|
||||
self.config_file = None
|
||||
self.reloadable_options = {R1, R2}
|
||||
self._raw_config = {}
|
||||
for key, value in properties.items():
|
||||
setattr(self, key, value)
|
||||
self._raw_config[key] = value
|
||||
|
||||
def update_reloadable_property(self, key, value):
|
||||
setattr(self, key, value)
|
||||
MOCK_SCHEMA = {
|
||||
R1: {'reloadable': True, 'default': 1},
|
||||
R2: {'reloadable': True, 'default': 2},
|
||||
N1: {'default': 3},
|
||||
N2: {'default': 4},
|
||||
}
|
||||
|
||||
|
||||
def test_update_property(monkeypatch):
|
||||
def get_schema(app_mapping):
|
||||
return {'mapping': {'galaxy': {'mapping': app_mapping}}}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_init(monkeypatch):
|
||||
|
||||
monkeypatch.setattr(AppSchema, '_read_schema', lambda a, b: get_schema(MOCK_SCHEMA))
|
||||
monkeypatch.setattr(GalaxyAppConfiguration, '_process_config', lambda a, b: None)
|
||||
|
||||
|
||||
def test_update_property(mock_init, monkeypatch):
|
||||
# This also covers adding a property. When a config file does not set a property,
|
||||
# that property is set to its default value. Thus, if we add a reloadable property
|
||||
# to the config file, it's the same as modifying that property's value.
|
||||
appconfig = MockGalaxyAppConfiguration({R1: 1, R2: 2, N1: 3})
|
||||
|
||||
def mock_read_properties_from_file(values):
|
||||
return {R1: 1, R2: 42, N1: 99} # edits: R2, N1 modified
|
||||
# edits to config file: R2, N1 modified
|
||||
monkeypatch.setattr(config, 'read_properties_from_file', lambda _: {R1: 1, R2: 42, N1: 99})
|
||||
|
||||
monkeypatch.setattr(config, 'read_properties_from_file', mock_read_properties_from_file)
|
||||
appconfig = GalaxyAppConfiguration()
|
||||
|
||||
assert getattr(appconfig, R1) == 1
|
||||
assert getattr(appconfig, R2) == 2
|
||||
@@ -40,7 +48,7 @@ def test_update_property(monkeypatch):
|
||||
assert getattr(appconfig, N1) == 3 # no change: option modified but is non-relodable
|
||||
|
||||
|
||||
def test_overwrite_reloadable_attribute(monkeypatch):
|
||||
def test_overwrite_reloadable_attribute(mock_init, monkeypatch):
|
||||
# This is similar to test_update_property, but here we overwrite the attribute before reloading.
|
||||
# This can happen if a config property is modified AFTER it has been loaded from schema or kwargs.
|
||||
# For example: load `foo` (from schema or kwargs), but then, in a # subsequent step while initializing
|
||||
@@ -48,12 +56,11 @@ def test_overwrite_reloadable_attribute(monkeypatch):
|
||||
# is not what was initially loaded, and if `foo` is reloadable, it will be reset to its default as soon
|
||||
# as the config file is modified. To prevent this, we compare the values read from the modified file
|
||||
# to the `_raw_config` dict. This test ensures this works correctly.
|
||||
appconfig = MockGalaxyAppConfiguration({R1: 1, R2: 2, N1: 3})
|
||||
|
||||
def mock_read_properties_from_file(values):
|
||||
return {R1: 1, R2: 42} # edits: R2 modified
|
||||
# edits to config file: R2 modified
|
||||
monkeypatch.setattr(config, 'read_properties_from_file', lambda _: {R1: 1, R2: 42})
|
||||
|
||||
monkeypatch.setattr(config, 'read_properties_from_file', mock_read_properties_from_file)
|
||||
appconfig = GalaxyAppConfiguration()
|
||||
|
||||
assert getattr(appconfig, R1) == 1
|
||||
assert getattr(appconfig, R2) == 2
|
||||
@@ -67,16 +74,15 @@ def test_overwrite_reloadable_attribute(monkeypatch):
|
||||
assert getattr(appconfig, R2) == 42 # change: reloadable option modified
|
||||
|
||||
|
||||
def test_cant_delete_property(monkeypatch):
|
||||
def test_cant_delete_property(mock_init, monkeypatch):
|
||||
# A property should not be deleted: we don't know whether it was initially
|
||||
# set to a default, loaded from a config file, env var, etc. Therefore, if a property
|
||||
# is removed from the config file, it will not be modified or deleted.
|
||||
appconfig = MockGalaxyAppConfiguration({R1: 1, R2: 2, N1: 3, N2: 4})
|
||||
|
||||
def mock_read_properties_from_file(values):
|
||||
return {R1: 1, N1: 3} # edits: R2, N2 deleted
|
||||
# edits to config file: R2, N2 deleted
|
||||
monkeypatch.setattr(config, 'read_properties_from_file', lambda _: {R1: 1, N1: 3})
|
||||
|
||||
monkeypatch.setattr(config, 'read_properties_from_file', mock_read_properties_from_file)
|
||||
appconfig = GalaxyAppConfiguration()
|
||||
|
||||
assert getattr(appconfig, R1) == 1
|
||||
assert getattr(appconfig, R2) == 2
|
||||
|
||||
@@ -12,9 +12,27 @@ MOCK_YAML = '''
|
||||
mockgalaxy:
|
||||
type: map
|
||||
mapping:
|
||||
option:
|
||||
attr1: a
|
||||
attr2: b
|
||||
property1:
|
||||
default: a
|
||||
type: str
|
||||
path_resolves_to: foo
|
||||
property2:
|
||||
default: 1
|
||||
type: int
|
||||
reloadable: true
|
||||
property3:
|
||||
default: 1.0
|
||||
type: float
|
||||
reloadable: true
|
||||
property4:
|
||||
default: true
|
||||
type: bool
|
||||
path_resolves_to: foo
|
||||
property5:
|
||||
something_else: b
|
||||
type: invalid
|
||||
property6:
|
||||
something_else: b
|
||||
'''
|
||||
|
||||
|
||||
@@ -29,10 +47,29 @@ def test_schema_is_loaded(monkeypatch):
|
||||
monkeypatch.setattr(AppSchema, '_read_schema', mock_read_schema)
|
||||
monkeypatch.setattr(OrderedLoader, '__init__', mock_init)
|
||||
|
||||
loaded_schema = AppSchema('no path', 'mockgalaxy')
|
||||
schema = AppSchema('no path', 'mockgalaxy')
|
||||
data = ordered_load(MOCK_YAML)
|
||||
|
||||
assert loaded_schema.description == data['desc']
|
||||
assert loaded_schema.raw_schema['foo'] == 'bar'
|
||||
assert loaded_schema.app_schema['option']['attr1'] == 'a'
|
||||
assert loaded_schema.get_app_option('option')['attr2'] == 'b'
|
||||
assert schema.description == data['desc']
|
||||
assert schema.raw_schema['foo'] == 'bar'
|
||||
|
||||
assert len(schema.defaults) == 6
|
||||
assert schema.defaults['property1'] == 'a'
|
||||
assert schema.defaults['property2'] == 1
|
||||
assert schema.defaults['property3'] == 1.0
|
||||
assert schema.defaults['property4'] is True
|
||||
assert schema.defaults['property5'] is None
|
||||
assert schema.defaults['property6'] is None
|
||||
|
||||
assert type(schema.defaults['property1']) is str
|
||||
assert type(schema.defaults['property2']) is int
|
||||
assert type(schema.defaults['property3']) is float
|
||||
assert type(schema.defaults['property4']) is bool
|
||||
|
||||
assert len(schema.reloadable_options) == 2
|
||||
assert 'property2' in schema.reloadable_options
|
||||
assert 'property3' in schema.reloadable_options
|
||||
|
||||
assert len(schema.paths_to_resolve) == 2
|
||||
assert 'property1' in schema.paths_to_resolve
|
||||
assert 'property4' in schema.paths_to_resolve
|
||||
|
||||
@@ -564,11 +564,7 @@ class SimplifiedToolBox(ToolBox):
|
||||
app.tool_cache = ToolCache() if not hasattr(app, 'tool_cache') else app.tool_cache
|
||||
app.job_config.get_tool_resource_parameters = lambda tool_id: None
|
||||
app.config.update_integrated_tool_panel = True
|
||||
app.config.appschema = {
|
||||
'tool_dependency_dir': {
|
||||
'default': 'dependencies',
|
||||
}
|
||||
}
|
||||
app.config.schema.defaults = {'tool_dependency_dir': 'dependencies'}
|
||||
config_files = test_case.config_files
|
||||
tool_root_dir = test_case.test_directory
|
||||
super(SimplifiedToolBox, self).__init__(
|
||||
|
||||
@@ -111,6 +111,9 @@ class MockLock(object):
|
||||
|
||||
class MockAppConfig(Bunch):
|
||||
|
||||
class MockSchema(Bunch):
|
||||
pass
|
||||
|
||||
def __init__(self, root=None, **kwargs):
|
||||
Bunch.__init__(self, **kwargs)
|
||||
if not root:
|
||||
@@ -118,6 +121,7 @@ class MockAppConfig(Bunch):
|
||||
self._remove_root = True
|
||||
else:
|
||||
self._remove_root = False
|
||||
self.schema = self.MockSchema()
|
||||
self.security = idencoding.IdEncodingHelper(id_secret='6e46ed6483a833c100e68cc3f1d0dd76')
|
||||
self.database_connection = kwargs.get('database_connection', "sqlite:///:memory:")
|
||||
self.use_remote_user = kwargs.get('use_remote_user', False)
|
||||
|
||||
Reference in New Issue
Block a user