mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge branch 'dev' of https://github.com/galaxyproject/galaxy into realtimetools
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd `dirname $0`/..
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
xsd_path="lib/galaxy/tools/xsd/galaxy.xsd"
|
||||
# Lint the XSD
|
||||
xmllint --noout "$xsd_path"
|
||||
|
||||
test_tools_path='test/functional/tools'
|
||||
tool_files_list=$(ls "$test_tools_path"/*xml | grep -v '_conf.xml$')
|
||||
tool_files_list=$(ls "$test_tools_path"/*.xml | grep -v '_conf.xml$')
|
||||
sh scripts/validate_tools.sh $tool_files_list
|
||||
|
||||
+18
-8
@@ -25,7 +25,7 @@ variables:
|
||||
jobs:
|
||||
get_code:
|
||||
docker:
|
||||
- image: circleci/python:2.7.15
|
||||
- image: circleci/python:3.5
|
||||
<<: *set_workdir
|
||||
steps:
|
||||
# Replace standard code checkout with shallow clone to speed things up.
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
- ~/repo
|
||||
py27_lint:
|
||||
docker:
|
||||
- image: circleci/python:2.7.15
|
||||
- image: circleci/python:2.7
|
||||
<<: *set_workdir
|
||||
steps:
|
||||
- *restore_repo_cache
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
- run: tox -e py27-lint
|
||||
py27_unit:
|
||||
docker:
|
||||
- image: circleci/python:2.7.15
|
||||
- image: circleci/python:2.7
|
||||
<<: *set_workdir
|
||||
steps:
|
||||
- *restore_repo_cache
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
- run: tox -e py27-unit
|
||||
py27_docstring:
|
||||
docker:
|
||||
- image: circleci/python:2.7.15
|
||||
- image: circleci/python:2.7
|
||||
<<: *set_workdir
|
||||
steps:
|
||||
- *restore_repo_cache
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
- run: tox -e py27-lint_docstring_include_list
|
||||
py27_first_startup:
|
||||
docker:
|
||||
- image: circleci/python:2.7.15
|
||||
- image: circleci/python:2.7
|
||||
<<: *set_workdir
|
||||
steps:
|
||||
- *restore_repo_cache
|
||||
@@ -139,14 +139,14 @@ jobs:
|
||||
<<: *set_workdir
|
||||
steps:
|
||||
- *restore_repo_cache
|
||||
- *install_tox
|
||||
- run: sudo apt-get update
|
||||
# For uwsgi
|
||||
- run: sudo apt-get install -y libpython3.5-dev
|
||||
- *install_tox
|
||||
- run: tox -e py35-first_startup
|
||||
validate_test_tools:
|
||||
docker:
|
||||
- image: circleci/python:2.7.15
|
||||
- image: circleci/python:3.5
|
||||
<<: *set_workdir
|
||||
steps:
|
||||
- *restore_repo_cache
|
||||
@@ -154,9 +154,17 @@ jobs:
|
||||
- run: sudo apt-get install -y libxml2-utils
|
||||
- *install_tox
|
||||
- run: tox -e validate_test_tools
|
||||
test_galaxy_packages:
|
||||
docker:
|
||||
- image: circleci/python:3.5
|
||||
<<: *set_workdir
|
||||
steps:
|
||||
- *restore_repo_cache
|
||||
- *install_tox
|
||||
- run: tox -e test_galaxy_packages
|
||||
check_py3_compatibility:
|
||||
docker:
|
||||
- image: circleci/python:2.7.15
|
||||
- image: circleci/python:3.5
|
||||
<<: *set_workdir
|
||||
steps:
|
||||
- *restore_repo_cache
|
||||
@@ -202,6 +210,8 @@ workflows:
|
||||
<<: *requires_get_code
|
||||
- py35_first_startup:
|
||||
<<: *requires_get_code
|
||||
- test_galaxy_packages:
|
||||
<<: *requires_get_code
|
||||
- validate_test_tools:
|
||||
<<: *requires_get_code
|
||||
- check_py3_compatibility:
|
||||
|
||||
@@ -79,6 +79,7 @@ export function multiHistory(options) {
|
||||
export { default as panels } from "layout/panel";
|
||||
export { show_in_overlay, hide_modal, show_message, show_modal, Modal } from "layout/modal";
|
||||
export { make_popupmenu, make_popup_menus } from "ui/popupmenu";
|
||||
export { render_embedded_items } from "mvc/embedded-objects";
|
||||
export { default as async_save_text } from "utils/async-save-text";
|
||||
|
||||
// Previously "chart"
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<span>
|
||||
<div class="unified-panel-header" unselectable="on">
|
||||
<div class="unified-panel-header-inner">
|
||||
Page Editor: {{ title }}
|
||||
<a id="save-button" class="btn btn-secondary fa fa-save float-right" @click="saveContent"></a>
|
||||
</div>
|
||||
</div>
|
||||
<page-editor-html :page-id="pageId" :content="content" ref="contentEditor" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageEditorHtml from "./PageEditorHtml";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
pageId: {
|
||||
required: true,
|
||||
type: String
|
||||
},
|
||||
content: {
|
||||
type: String
|
||||
},
|
||||
title: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
saveContent: function() {
|
||||
this.$refs.contentEditor.saveContent();
|
||||
}
|
||||
},
|
||||
components: {
|
||||
PageEditorHtml
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,682 @@
|
||||
<template>
|
||||
<div class="unified-panel-body">
|
||||
<textarea name="page_content" :val="content"></textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import $ from "jquery";
|
||||
import _l from "utils/localization";
|
||||
|
||||
import GridView from "mvc/grid/grid-view";
|
||||
import { show_modal, hide_modal } from "layout/modal";
|
||||
import { make_popupmenu } from "ui/popupmenu";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import { buildConfig } from "utils/genericConfig";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { save } from "./util";
|
||||
|
||||
// Built a generic config container for the properties that
|
||||
// are passed in from python then used (formerly) globally
|
||||
const configs = buildConfig();
|
||||
|
||||
// Turn these window variables into config props list_objects_url,
|
||||
// set_accessible_url, get_name_and_link_url, editor_base_path,
|
||||
// iframe_base_path, save_url, page_id, page_list_url
|
||||
function setPageConfigs(incoming = {}) {
|
||||
return Object.assign(configs, incoming);
|
||||
}
|
||||
|
||||
var WYMeditor = window.WYMeditor;
|
||||
|
||||
var editor = null;
|
||||
|
||||
var CONTROLS = {
|
||||
// Item types.
|
||||
ITEM_HISTORY: "item_history",
|
||||
ITEM_DATASET: "item_dataset",
|
||||
ITEM_WORKFLOW: "item_workflow",
|
||||
ITEM_PAGE: "item_page",
|
||||
ITEM_VISUALIZATION: "item_visualization",
|
||||
|
||||
// Link dialogs.
|
||||
DIALOG_HISTORY_LINK: "link_history",
|
||||
DIALOG_DATASET_LINK: "link_dataset",
|
||||
DIALOG_WORKFLOW_LINK: "link_workflow",
|
||||
DIALOG_PAGE_LINK: "link_page",
|
||||
DIALOG_VISUALIZATION_LINK: "link_visualization",
|
||||
|
||||
// Embed dialogs.
|
||||
DIALOG_EMBED_HISTORY: "embed_history",
|
||||
DIALOG_EMBED_DATASET: "embed_dataset",
|
||||
DIALOG_EMBED_WORKFLOW: "embed_workflow",
|
||||
DIALOG_EMBED_PAGE: "embed_page",
|
||||
DIALOG_EMBED_VISUALIZATION: "embed_visualization"
|
||||
};
|
||||
|
||||
// Based on the dialog type, return a dictionary of information about an item
|
||||
function get_item_info(dialog_type) {
|
||||
var item_singular;
|
||||
var item_plural;
|
||||
var item_controller;
|
||||
var item_class;
|
||||
switch (dialog_type) {
|
||||
case CONTROLS.ITEM_HISTORY:
|
||||
item_singular = "History";
|
||||
item_plural = "Histories";
|
||||
item_controller = "history";
|
||||
item_class = "History";
|
||||
break;
|
||||
case CONTROLS.ITEM_DATASET:
|
||||
item_singular = "Dataset";
|
||||
item_plural = "Datasets";
|
||||
item_controller = "dataset";
|
||||
item_class = "HistoryDatasetAssociation";
|
||||
break;
|
||||
case CONTROLS.ITEM_WORKFLOW:
|
||||
item_singular = "Workflow";
|
||||
item_plural = "Workflows";
|
||||
item_controller = "workflow";
|
||||
item_class = "StoredWorkflow";
|
||||
break;
|
||||
case CONTROLS.ITEM_PAGE:
|
||||
item_singular = "Page";
|
||||
item_plural = "Pages";
|
||||
item_controller = "page";
|
||||
item_class = "Page";
|
||||
break;
|
||||
case CONTROLS.ITEM_VISUALIZATION:
|
||||
item_singular = "Visualization";
|
||||
item_plural = "Visualizations";
|
||||
item_controller = "visualization";
|
||||
item_class = "Visualization";
|
||||
break;
|
||||
}
|
||||
|
||||
// Build ajax URL that lists items for selection.
|
||||
var item_list_action = `list_${item_plural.toLowerCase()}_for_selection`;
|
||||
var ajax_url = configs.list_objects_url.replace("LIST_ACTION", item_list_action);
|
||||
|
||||
// Set up and return dict.
|
||||
return {
|
||||
singular: item_singular,
|
||||
plural: item_plural,
|
||||
controller: item_controller,
|
||||
iclass: item_class,
|
||||
list_ajax_url: ajax_url
|
||||
};
|
||||
}
|
||||
|
||||
// Make an item importable.
|
||||
function make_item_importable(item_controller, item_id, item_type) {
|
||||
var ajax_url = configs.set_accessible_url.replace("ITEM_CONTROLLER", item_controller);
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: ajax_url,
|
||||
data: { id: item_id, accessible: "True" },
|
||||
error: function() {
|
||||
alert(`Making ${item_type} accessible failed`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Completely replace WYM's dialog handling
|
||||
WYMeditor.editor.prototype.dialog = function(dialogType, dialogFeatures, bodyHtml) {
|
||||
var wym = this;
|
||||
var sStamp = wym.uniqueStamp();
|
||||
var selected = wym.selected();
|
||||
|
||||
// Swap out URL attribute for id/name attribute in link creation to enable anchor creation in page.
|
||||
function set_link_id() {
|
||||
// When "set link id" link clicked, update UI.
|
||||
$("#set_link_id").click(function() {
|
||||
// Set label.
|
||||
$("#link_attribute_label").text("ID/Name");
|
||||
|
||||
// Set input elt class, value.
|
||||
var attribute_input = $(".wym_href");
|
||||
attribute_input.addClass("wym_id").removeClass("wym_href");
|
||||
if (selected) attribute_input.val($(selected).attr("id"));
|
||||
|
||||
// Remove link.
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
|
||||
// LINK DIALOG
|
||||
if (dialogType == WYMeditor.DIALOG_LINK) {
|
||||
if (selected) {
|
||||
$(wym._options.hrefSelector).val($(selected).attr(WYMeditor.HREF));
|
||||
$(wym._options.srcSelector).val($(selected).attr(WYMeditor.SRC));
|
||||
$(wym._options.titleSelector).val($(selected).attr(WYMeditor.TITLE));
|
||||
$(wym._options.altSelector).val($(selected).attr(WYMeditor.ALT));
|
||||
}
|
||||
|
||||
// Get current URL, title.
|
||||
var curURL;
|
||||
|
||||
var curTitle;
|
||||
if (selected) {
|
||||
curURL = $(selected).attr("href");
|
||||
if (curURL == undefined) curURL = "";
|
||||
curTitle = $(selected).attr("title");
|
||||
if (curTitle == undefined) curTitle = "";
|
||||
}
|
||||
show_modal(
|
||||
"Create Link",
|
||||
`<div><div><label id='link_attribute_label'>URL <span style='float: right; font-size: 90%'><a href='#' id='set_link_id'>Create in-page anchor</a></span></label><br><input type='text' class='wym_href' value='${curURL}' size='40' /></div><div><label>Title</label><br><input type='text' class='wym_title' value='${curTitle}' size='40' /></div><div>`,
|
||||
{
|
||||
"Make link": function() {
|
||||
// Get URL, name/title.
|
||||
var sUrl = $(wym._options.hrefSelector).val() || "";
|
||||
|
||||
var sId = $(".wym_id").val() || "";
|
||||
var sName = $(wym._options.titleSelector).val() || "";
|
||||
|
||||
if (sUrl || sId) {
|
||||
// Create link.
|
||||
wym._exec(WYMeditor.CREATE_LINK, sStamp);
|
||||
|
||||
// Set link attributes.
|
||||
var link = $(`a[href=${sStamp}]`, wym._doc.body);
|
||||
link.attr(WYMeditor.HREF, sUrl)
|
||||
.attr(WYMeditor.TITLE, sName)
|
||||
.attr("id", sId);
|
||||
|
||||
// If link's text is default (wym-...), change it to the title.
|
||||
if (link.text().indexOf("wym-") === 0) {
|
||||
link.text(sName);
|
||||
}
|
||||
}
|
||||
hide_modal();
|
||||
},
|
||||
Cancel: function() {
|
||||
hide_modal();
|
||||
}
|
||||
},
|
||||
{},
|
||||
set_link_id
|
||||
);
|
||||
}
|
||||
|
||||
// IMAGE DIALOG
|
||||
if (dialogType == WYMeditor.DIALOG_IMAGE) {
|
||||
if (wym._selected_image) {
|
||||
$(`${wym._options.dialogImageSelector} ${wym._options.srcSelector}`).val(
|
||||
$(wym._selected_image).attr(WYMeditor.SRC)
|
||||
);
|
||||
$(`${wym._options.dialogImageSelector} ${wym._options.titleSelector}`).val(
|
||||
$(wym._selected_image).attr(WYMeditor.TITLE)
|
||||
);
|
||||
$(`${wym._options.dialogImageSelector} ${wym._options.altSelector}`).val(
|
||||
$(wym._selected_image).attr(WYMeditor.ALT)
|
||||
);
|
||||
}
|
||||
show_modal(
|
||||
"Image",
|
||||
"<div class='row'>" +
|
||||
"<label>URL</label><br>" +
|
||||
"<input type='text' class='wym_src' value='' size='40' />" +
|
||||
"</div>" +
|
||||
"<div class='row'>" +
|
||||
"<label>Alt text</label><br>" +
|
||||
"<input type='text' class='wym_alt' value='' size='40' />" +
|
||||
"</div>" +
|
||||
"<div class='row'>" +
|
||||
"<label>Title</label><br>" +
|
||||
"<input type='text' class='wym_title' value='' size='40' />" +
|
||||
"</div>",
|
||||
{
|
||||
Insert: function() {
|
||||
var sUrl = $(wym._options.srcSelector).val();
|
||||
if (sUrl.length > 0) {
|
||||
wym._exec(WYMeditor.INSERT_IMAGE, sStamp);
|
||||
$(`img[src$=${sStamp}]`, wym._doc.body)
|
||||
.attr(WYMeditor.SRC, sUrl)
|
||||
.attr(WYMeditor.TITLE, $(wym._options.titleSelector).val())
|
||||
.attr(WYMeditor.ALT, $(wym._options.altSelector).val());
|
||||
}
|
||||
hide_modal();
|
||||
},
|
||||
Cancel: function() {
|
||||
hide_modal();
|
||||
}
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// TABLE DIALOG
|
||||
if (dialogType == WYMeditor.DIALOG_TABLE) {
|
||||
show_modal(
|
||||
"Table",
|
||||
"<div class='row'>" +
|
||||
"<label>Caption</label><br>" +
|
||||
"<input type='text' class='wym_caption' value='' size='40' />" +
|
||||
"</div>" +
|
||||
"<div class='row'>" +
|
||||
"<label>Summary</label><br>" +
|
||||
"<input type='text' class='wym_summary' value='' size='40' />" +
|
||||
"</div>" +
|
||||
"<div class='row'>" +
|
||||
"<label>Number Of Rows<br></label>" +
|
||||
"<input type='text' class='wym_rows' value='3' size='3' />" +
|
||||
"</div>" +
|
||||
"<div class='row'>" +
|
||||
"<label>Number Of Cols<br></label>" +
|
||||
"<input type='text' class='wym_cols' value='2' size='3' />" +
|
||||
"</div>",
|
||||
{
|
||||
Insert: function() {
|
||||
var iRows = $(wym._options.rowsSelector).val();
|
||||
var iCols = $(wym._options.colsSelector).val();
|
||||
|
||||
if (iRows > 0 && iCols > 0) {
|
||||
var table = wym._doc.createElement(WYMeditor.TABLE);
|
||||
var newRow = null;
|
||||
|
||||
var sCaption = $(wym._options.captionSelector).val();
|
||||
|
||||
//we create the caption
|
||||
var newCaption = table.createCaption();
|
||||
newCaption.innerHTML = sCaption;
|
||||
|
||||
//we create the rows and cells
|
||||
for (var x = 0; x < iRows; x++) {
|
||||
newRow = table.insertRow(x);
|
||||
for (var y = 0; y < iCols; y++) {
|
||||
newRow.insertCell(y);
|
||||
}
|
||||
}
|
||||
|
||||
//set the summary attr
|
||||
$(table).attr("summary", $(wym._options.summarySelector).val());
|
||||
|
||||
//append the table after the selected container
|
||||
var node = $(wym.findUp(wym.container(), WYMeditor.MAIN_CONTAINERS)).get(0);
|
||||
if (!node || !node.parentNode) $(wym._doc.body).append(table);
|
||||
else $(node).after(table);
|
||||
}
|
||||
hide_modal();
|
||||
},
|
||||
Cancel: function() {
|
||||
hide_modal();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// INSERT "GALAXY ITEM" LINK DIALOG
|
||||
if (
|
||||
dialogType == CONTROLS.DIALOG_HISTORY_LINK ||
|
||||
dialogType == CONTROLS.DIALOG_DATASET_LINK ||
|
||||
dialogType == CONTROLS.DIALOG_WORKFLOW_LINK ||
|
||||
dialogType == CONTROLS.DIALOG_PAGE_LINK ||
|
||||
dialogType == CONTROLS.DIALOG_VISUALIZATION_LINK
|
||||
) {
|
||||
// Based on item type, set useful vars.
|
||||
var item_info;
|
||||
switch (dialogType) {
|
||||
case CONTROLS.DIALOG_HISTORY_LINK:
|
||||
item_info = get_item_info(CONTROLS.ITEM_HISTORY);
|
||||
break;
|
||||
case CONTROLS.DIALOG_DATASET_LINK:
|
||||
item_info = get_item_info(CONTROLS.ITEM_DATASET);
|
||||
break;
|
||||
case CONTROLS.DIALOG_WORKFLOW_LINK:
|
||||
item_info = get_item_info(CONTROLS.ITEM_WORKFLOW);
|
||||
break;
|
||||
case CONTROLS.DIALOG_PAGE_LINK:
|
||||
item_info = get_item_info(CONTROLS.ITEM_PAGE);
|
||||
break;
|
||||
case CONTROLS.DIALOG_VISUALIZATION_LINK:
|
||||
item_info = get_item_info(CONTROLS.ITEM_VISUALIZATION);
|
||||
break;
|
||||
}
|
||||
|
||||
var grid = new GridView({
|
||||
url_base: item_info.list_ajax_url,
|
||||
embedded: true
|
||||
});
|
||||
const Galaxy = getGalaxyInstance();
|
||||
Galaxy.modal.show({
|
||||
title: `Insert Link to ${item_info.singular}`,
|
||||
body: $("<div/>")
|
||||
.append(grid.$el)
|
||||
.append(
|
||||
$("<div/>")
|
||||
.append('<input id="make-importable" type="checkbox" checked/>')
|
||||
.append(
|
||||
`Make the selected ${item_info.plural.toLowerCase()} accessible so that they can viewed by everyone.`
|
||||
)
|
||||
),
|
||||
closing_events: true,
|
||||
buttons: {
|
||||
Insert: function() {
|
||||
// Make selected items accessible (importable) ?
|
||||
var make_importable = false;
|
||||
if ($("#make-importable:checked").val() != null) make_importable = true;
|
||||
|
||||
// Insert links to history for each checked item.
|
||||
grid.$("input[name=id]:checked").each(function() {
|
||||
var item_id = $(this).val();
|
||||
|
||||
// Make item importable?
|
||||
if (make_importable) make_item_importable(item_info.controller, item_id, item_info.singular);
|
||||
|
||||
// Insert link(s) to item(s). This is done by getting item info and then manipulating wym.
|
||||
var url_template = configs.get_name_and_link_url + item_id;
|
||||
var ajax_url = url_template.replace("ITEM_CONTROLLER", item_info.controller);
|
||||
$.getJSON(ajax_url, returned_item_info => {
|
||||
// Get link text.
|
||||
wym._exec(WYMeditor.CREATE_LINK, sStamp);
|
||||
var link_text = $(`a[href=${sStamp}]`, wym._doc.body).text();
|
||||
|
||||
// Insert link: need to do different actions depending on link text.
|
||||
if (
|
||||
link_text == "" || // Firefox.
|
||||
link_text == sStamp // Safari
|
||||
) {
|
||||
// User selected no text; create link from scratch and use default text.
|
||||
wym.insert(
|
||||
`<a href='${returned_item_info.link}'>${item_info.singular} '${
|
||||
returned_item_info.name
|
||||
}'</a>`
|
||||
);
|
||||
} else {
|
||||
// Link created from selected text; add href and title.
|
||||
$(`a[href=${sStamp}]`, wym._doc.body)
|
||||
.attr(WYMeditor.HREF, returned_item_info.link)
|
||||
.attr(WYMeditor.TITLE, item_info.singular + item_id);
|
||||
}
|
||||
});
|
||||
});
|
||||
Galaxy.modal.hide();
|
||||
},
|
||||
Close: function() {
|
||||
Galaxy.modal.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// EMBED GALAXY OBJECT DIALOGS
|
||||
if (
|
||||
dialogType == CONTROLS.DIALOG_EMBED_HISTORY ||
|
||||
dialogType == CONTROLS.DIALOG_EMBED_DATASET ||
|
||||
dialogType == CONTROLS.DIALOG_EMBED_WORKFLOW ||
|
||||
dialogType == CONTROLS.DIALOG_EMBED_PAGE ||
|
||||
dialogType == CONTROLS.DIALOG_EMBED_VISUALIZATION
|
||||
) {
|
||||
// Based on item type, set useful vars.
|
||||
let item_info;
|
||||
switch (dialogType) {
|
||||
case CONTROLS.DIALOG_EMBED_HISTORY:
|
||||
item_info = get_item_info(CONTROLS.ITEM_HISTORY);
|
||||
break;
|
||||
case CONTROLS.DIALOG_EMBED_DATASET:
|
||||
item_info = get_item_info(CONTROLS.ITEM_DATASET);
|
||||
break;
|
||||
case CONTROLS.DIALOG_EMBED_WORKFLOW:
|
||||
item_info = get_item_info(CONTROLS.ITEM_WORKFLOW);
|
||||
break;
|
||||
case CONTROLS.DIALOG_EMBED_PAGE:
|
||||
item_info = get_item_info(CONTROLS.ITEM_PAGE);
|
||||
break;
|
||||
case CONTROLS.DIALOG_EMBED_VISUALIZATION:
|
||||
item_info = get_item_info(CONTROLS.ITEM_VISUALIZATION);
|
||||
break;
|
||||
}
|
||||
|
||||
const grid = new GridView({
|
||||
url_base: item_info.list_ajax_url,
|
||||
embedded: true
|
||||
});
|
||||
const Galaxy = getGalaxyInstance();
|
||||
Galaxy.modal.show({
|
||||
title: `Insert Link to ${item_info.singular}`,
|
||||
body: $("<div/>")
|
||||
.append(grid.$el)
|
||||
.append(
|
||||
$("<div/>")
|
||||
.append('<input id="make-importable" type="checkbox" checked/>')
|
||||
.append(
|
||||
`Make the selected ${item_info.plural.toLowerCase()} accessible so that they can viewed by everyone.`
|
||||
)
|
||||
),
|
||||
closing_events: true,
|
||||
extra_class: "pages-embed",
|
||||
buttons: {
|
||||
Embed: function() {
|
||||
// Make selected items accessible (importable) ?
|
||||
var make_importable = false;
|
||||
if ($("#make-importable:checked").val() != null) make_importable = true;
|
||||
|
||||
grid.$("input[name=id]:checked").each(function() {
|
||||
var elt = $(this);
|
||||
// Get item ID and name.
|
||||
var item_id = elt.val();
|
||||
// Use ':first' because there are many labels in table; the first one is the item name.
|
||||
var item_name = elt
|
||||
.closest("td")
|
||||
.next("td")
|
||||
.find("label")
|
||||
.text();
|
||||
|
||||
if (make_importable) make_item_importable(item_info.controller, item_id, item_info.singular);
|
||||
|
||||
// Embedded item HTML; item class is embedded in div container classes; this is necessary because the editor strips
|
||||
// all non-standard attributes when it returns its content (e.g. it will not return an element attribute of the form
|
||||
// item_class='History').
|
||||
var item_elt_id = `${item_info.iclass}-${item_id}`;
|
||||
var item_embed_html = [
|
||||
"<div id='",
|
||||
item_elt_id,
|
||||
"' class='embedded-item ",
|
||||
item_info.singular.toLowerCase(),
|
||||
" placeholder'>",
|
||||
"<p class='title'>",
|
||||
"Embedded Galaxy ",
|
||||
item_info.singular,
|
||||
" '",
|
||||
item_name,
|
||||
"'",
|
||||
"</p>",
|
||||
"<p class='content'>",
|
||||
"[Do not edit this block; Galaxy will fill it in with the annotated ",
|
||||
item_info.singular.toLowerCase(),
|
||||
" when it is displayed.]",
|
||||
"</p>",
|
||||
"</div>"
|
||||
].join("");
|
||||
|
||||
// Insert embedded item into document.
|
||||
wym.insert(item_embed_html);
|
||||
});
|
||||
Galaxy.modal.hide();
|
||||
},
|
||||
Close: function() {
|
||||
Galaxy.modal.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function renderEditorWithContent(pageId, content) {
|
||||
const appRoot = getAppRoot();
|
||||
const pageConfigs = {
|
||||
page_id: pageId,
|
||||
page_list_url: `${appRoot}pages/list`,
|
||||
list_objects_url: `${appRoot}page/LIST_ACTION`,
|
||||
set_accessible_url: `${appRoot}ITEM_CONTROLLER/set_accessible_async`,
|
||||
get_name_and_link_url: `${appRoot}ITEM_CONTROLLER/get_name_and_link_async?id=`,
|
||||
editor_base_path: `${appRoot}static/wymeditor/`,
|
||||
iframe_base_path: `${appRoot}static/wymeditor/iframe/galaxy/`
|
||||
};
|
||||
setPageConfigs(pageConfigs);
|
||||
|
||||
// Generic error handling
|
||||
$(document).ajaxError((e, x) => {
|
||||
// console.log( e, x );
|
||||
var message = x.responseText || x.statusText || "Could not connect to server";
|
||||
show_modal("Server error", message, { "Ignore error": hide_modal });
|
||||
return false;
|
||||
});
|
||||
// Create editor
|
||||
$("[name=page_content]")
|
||||
.val(content)
|
||||
.wymeditor({
|
||||
skin: "galaxy",
|
||||
basePath: configs.editor_base_path,
|
||||
iframeBasePath: configs.iframe_base_path,
|
||||
// + WYMeditor.HTML
|
||||
boxHtml: `<table class='wym_box' width='100%' height='100%'><tr><td><div class='wym_area_top'>${
|
||||
WYMeditor.TOOLS
|
||||
}</div></td></tr><tr height='100%'><td><div class='wym_area_main' style='height: 100%;'>${
|
||||
// + WYMeditor.HTML
|
||||
WYMeditor.IFRAME
|
||||
}${WYMeditor.STATUS}</div></div></td></tr></table>`,
|
||||
toolsItems: [
|
||||
{ name: "Bold", title: "Strong", css: "wym_tools_strong" },
|
||||
{ name: "Italic", title: "Emphasis", css: "wym_tools_emphasis" },
|
||||
{
|
||||
name: "Superscript",
|
||||
title: _l("Superscript"),
|
||||
css: "wym_tools_superscript"
|
||||
},
|
||||
{
|
||||
name: "Subscript",
|
||||
title: _l("Subscript"),
|
||||
css: "wym_tools_subscript"
|
||||
},
|
||||
{
|
||||
name: "InsertOrderedList",
|
||||
title: "Ordered_List",
|
||||
css: "wym_tools_ordered_list"
|
||||
},
|
||||
{
|
||||
name: "InsertUnorderedList",
|
||||
title: "Unordered_List",
|
||||
css: "wym_tools_unordered_list"
|
||||
},
|
||||
{ name: "Indent", title: "Indent", css: "wym_tools_indent" },
|
||||
{ name: "Outdent", title: "Outdent", css: "wym_tools_outdent" },
|
||||
{ name: "Undo", title: "Undo", css: "wym_tools_undo" },
|
||||
{ name: "Redo", title: "Redo", css: "wym_tools_redo" },
|
||||
{ name: "CreateLink", title: "Link", css: "wym_tools_link" },
|
||||
{ name: "Unlink", title: "Unlink", css: "wym_tools_unlink" },
|
||||
{ name: "InsertImage", title: "Image", css: "wym_tools_image" },
|
||||
{ name: "InsertTable", title: "Table", css: "wym_tools_table" }
|
||||
]
|
||||
});
|
||||
// Get the editor object
|
||||
editor = $.wymeditors(0);
|
||||
|
||||
//
|
||||
// Containers, Galaxy style
|
||||
//
|
||||
var containers_menu = $(
|
||||
"<div class='galaxy-page-editor-button'><a id='insert-galaxy-link' class='action-button popup' href='#'>Paragraph type</a></div>"
|
||||
);
|
||||
$(".wym_area_top").append(containers_menu);
|
||||
|
||||
// Add menu options.
|
||||
var items = {};
|
||||
$.each(editor._options.containersItems, (k, v) => {
|
||||
var tagname = v.name;
|
||||
items[v.title.replace("_", " ")] = () => {
|
||||
editor.container(tagname);
|
||||
};
|
||||
});
|
||||
make_popupmenu(containers_menu, items);
|
||||
|
||||
//
|
||||
// Create 'Insert Link to Galaxy Object' menu.
|
||||
//
|
||||
|
||||
// Add menu button.
|
||||
var insert_link_menu_button = $(
|
||||
"<div><a id='insert-galaxy-link' class='action-button popup' href='#'>Insert Link to Galaxy Object</a></div>"
|
||||
).addClass("galaxy-page-editor-button");
|
||||
$(".wym_area_top").append(insert_link_menu_button);
|
||||
|
||||
// Add menu options.
|
||||
make_popupmenu(insert_link_menu_button, {
|
||||
"Insert History Link": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_HISTORY_LINK);
|
||||
},
|
||||
"Insert Dataset Link": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_DATASET_LINK);
|
||||
},
|
||||
"Insert Workflow Link": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_WORKFLOW_LINK);
|
||||
},
|
||||
"Insert Page Link": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_PAGE_LINK);
|
||||
},
|
||||
"Insert Visualization Link": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_VISUALIZATION_LINK);
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Create 'Embed Galaxy Object' menu.
|
||||
//
|
||||
|
||||
// Add menu button.
|
||||
var embed_object_button = $(
|
||||
"<div><a id='embed-galaxy-object' class='action-button popup' href='#'>Embed Galaxy Object</a></div>"
|
||||
).addClass("galaxy-page-editor-button");
|
||||
$(".wym_area_top").append(embed_object_button);
|
||||
|
||||
// Add menu options.
|
||||
make_popupmenu(embed_object_button, {
|
||||
"Embed History": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_EMBED_HISTORY);
|
||||
},
|
||||
"Embed Dataset": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_EMBED_DATASET);
|
||||
},
|
||||
"Embed Workflow": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_EMBED_WORKFLOW);
|
||||
},
|
||||
"Embed Visualization": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_EMBED_VISUALIZATION);
|
||||
}
|
||||
//"Embed Page": function() {
|
||||
// editor.dialog(CONTROLS.DIALOG_EMBED_PAGE);
|
||||
//}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
pageId: {
|
||||
required: true,
|
||||
type: String
|
||||
},
|
||||
content: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
this.$nextTick(() => {
|
||||
renderEditorWithContent(this.pageId, this.content);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
saveContent: function() {
|
||||
save(this.pageId, editor.xhtml());
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "embed_item";
|
||||
.galaxy-page-editor-button {
|
||||
position: relative;
|
||||
float: left;
|
||||
padding: 0.2em;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as PageEditor } from "./PageEditor.vue";
|
||||
@@ -0,0 +1,24 @@
|
||||
import $ from "jquery";
|
||||
import _l from "utils/localization";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { show_modal, hide_modal } from "layout/modal";
|
||||
|
||||
export const save = (pageId, content) => {
|
||||
console.log(`content is ${content}`);
|
||||
|
||||
show_modal(_l("Saving page"), _l("progress"));
|
||||
|
||||
// Do save.
|
||||
$.ajax({
|
||||
url: `${getAppRoot()}page/save`,
|
||||
type: "POST",
|
||||
data: {
|
||||
id: pageId,
|
||||
content: content,
|
||||
_: "true"
|
||||
},
|
||||
success: function() {
|
||||
hide_modal();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,681 +1,31 @@
|
||||
import $ from "jquery";
|
||||
import _l from "utils/localization";
|
||||
import axios from "axios";
|
||||
import { Toast } from "ui/toast";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { PageEditor } from "components/PageEditor";
|
||||
import Vue from "vue";
|
||||
|
||||
import GridView from "mvc/grid/grid-view";
|
||||
import { show_modal, hide_modal } from "layout/modal";
|
||||
import { make_popupmenu } from "ui/popupmenu";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import { buildConfig } from "utils/genericConfig";
|
||||
|
||||
// Built a generic config container for the properties that
|
||||
// are passed in from python then used (formerly) globally
|
||||
const configs = buildConfig();
|
||||
|
||||
// Turn these window variables into config props list_objects_url,
|
||||
// set_accessible_url, get_name_and_link_url, editor_base_path,
|
||||
// iframe_base_path, save_url, page_id, page_list_url
|
||||
function setPageConfigs(incoming = {}) {
|
||||
return Object.assign(configs, incoming);
|
||||
}
|
||||
|
||||
var WYMeditor = window.WYMeditor;
|
||||
|
||||
var CONTROLS = {
|
||||
// Item types.
|
||||
ITEM_HISTORY: "item_history",
|
||||
ITEM_DATASET: "item_dataset",
|
||||
ITEM_WORKFLOW: "item_workflow",
|
||||
ITEM_PAGE: "item_page",
|
||||
ITEM_VISUALIZATION: "item_visualization",
|
||||
|
||||
// Link dialogs.
|
||||
DIALOG_HISTORY_LINK: "link_history",
|
||||
DIALOG_DATASET_LINK: "link_dataset",
|
||||
DIALOG_WORKFLOW_LINK: "link_workflow",
|
||||
DIALOG_PAGE_LINK: "link_page",
|
||||
DIALOG_VISUALIZATION_LINK: "link_visualization",
|
||||
|
||||
// Embed dialogs.
|
||||
DIALOG_EMBED_HISTORY: "embed_history",
|
||||
DIALOG_EMBED_DATASET: "embed_dataset",
|
||||
DIALOG_EMBED_WORKFLOW: "embed_workflow",
|
||||
DIALOG_EMBED_PAGE: "embed_page",
|
||||
DIALOG_EMBED_VISUALIZATION: "embed_visualization"
|
||||
};
|
||||
|
||||
// Based on the dialog type, return a dictionary of information about an item
|
||||
function get_item_info(dialog_type) {
|
||||
var item_singular;
|
||||
var item_plural;
|
||||
var item_controller;
|
||||
var item_class;
|
||||
switch (dialog_type) {
|
||||
case CONTROLS.ITEM_HISTORY:
|
||||
item_singular = "History";
|
||||
item_plural = "Histories";
|
||||
item_controller = "history";
|
||||
item_class = "History";
|
||||
break;
|
||||
case CONTROLS.ITEM_DATASET:
|
||||
item_singular = "Dataset";
|
||||
item_plural = "Datasets";
|
||||
item_controller = "dataset";
|
||||
item_class = "HistoryDatasetAssociation";
|
||||
break;
|
||||
case CONTROLS.ITEM_WORKFLOW:
|
||||
item_singular = "Workflow";
|
||||
item_plural = "Workflows";
|
||||
item_controller = "workflow";
|
||||
item_class = "StoredWorkflow";
|
||||
break;
|
||||
case CONTROLS.ITEM_PAGE:
|
||||
item_singular = "Page";
|
||||
item_plural = "Pages";
|
||||
item_controller = "page";
|
||||
item_class = "Page";
|
||||
break;
|
||||
case CONTROLS.ITEM_VISUALIZATION:
|
||||
item_singular = "Visualization";
|
||||
item_plural = "Visualizations";
|
||||
item_controller = "visualization";
|
||||
item_class = "Visualization";
|
||||
break;
|
||||
}
|
||||
|
||||
// Build ajax URL that lists items for selection.
|
||||
var item_list_action = `list_${item_plural.toLowerCase()}_for_selection`;
|
||||
var ajax_url = configs.list_objects_url.replace("LIST_ACTION", item_list_action);
|
||||
|
||||
// Set up and return dict.
|
||||
return {
|
||||
singular: item_singular,
|
||||
plural: item_plural,
|
||||
controller: item_controller,
|
||||
iclass: item_class,
|
||||
list_ajax_url: ajax_url
|
||||
};
|
||||
}
|
||||
|
||||
// Make an item importable.
|
||||
function make_item_importable(item_controller, item_id, item_type) {
|
||||
var ajax_url = configs.set_accessible_url.replace("ITEM_CONTROLLER", item_controller);
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: ajax_url,
|
||||
data: { id: item_id, accessible: "True" },
|
||||
error: function() {
|
||||
alert(`Making ${item_type} accessible failed`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Completely replace WYM's dialog handling
|
||||
WYMeditor.editor.prototype.dialog = function(dialogType, dialogFeatures, bodyHtml) {
|
||||
var wym = this;
|
||||
var sStamp = wym.uniqueStamp();
|
||||
var selected = wym.selected();
|
||||
|
||||
// Swap out URL attribute for id/name attribute in link creation to enable anchor creation in page.
|
||||
function set_link_id() {
|
||||
// When "set link id" link clicked, update UI.
|
||||
$("#set_link_id").click(function() {
|
||||
// Set label.
|
||||
$("#link_attribute_label").text("ID/Name");
|
||||
|
||||
// Set input elt class, value.
|
||||
var attribute_input = $(".wym_href");
|
||||
attribute_input.addClass("wym_id").removeClass("wym_href");
|
||||
if (selected) attribute_input.val($(selected).attr("id"));
|
||||
|
||||
// Remove link.
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
|
||||
// LINK DIALOG
|
||||
if (dialogType == WYMeditor.DIALOG_LINK) {
|
||||
if (selected) {
|
||||
$(wym._options.hrefSelector).val($(selected).attr(WYMeditor.HREF));
|
||||
$(wym._options.srcSelector).val($(selected).attr(WYMeditor.SRC));
|
||||
$(wym._options.titleSelector).val($(selected).attr(WYMeditor.TITLE));
|
||||
$(wym._options.altSelector).val($(selected).attr(WYMeditor.ALT));
|
||||
}
|
||||
|
||||
// Get current URL, title.
|
||||
var curURL;
|
||||
|
||||
var curTitle;
|
||||
if (selected) {
|
||||
curURL = $(selected).attr("href");
|
||||
if (curURL == undefined) curURL = "";
|
||||
curTitle = $(selected).attr("title");
|
||||
if (curTitle == undefined) curTitle = "";
|
||||
}
|
||||
show_modal(
|
||||
"Create Link",
|
||||
`<div><div><label id='link_attribute_label'>URL <span style='float: right; font-size: 90%'><a href='#' id='set_link_id'>Create in-page anchor</a></span></label><br><input type='text' class='wym_href' value='${curURL}' size='40' /></div><div><label>Title</label><br><input type='text' class='wym_title' value='${curTitle}' size='40' /></div><div>`,
|
||||
{
|
||||
"Make link": function() {
|
||||
// Get URL, name/title.
|
||||
var sUrl = $(wym._options.hrefSelector).val() || "";
|
||||
|
||||
var sId = $(".wym_id").val() || "";
|
||||
var sName = $(wym._options.titleSelector).val() || "";
|
||||
|
||||
if (sUrl || sId) {
|
||||
// Create link.
|
||||
wym._exec(WYMeditor.CREATE_LINK, sStamp);
|
||||
|
||||
// Set link attributes.
|
||||
var link = $(`a[href=${sStamp}]`, wym._doc.body);
|
||||
link.attr(WYMeditor.HREF, sUrl)
|
||||
.attr(WYMeditor.TITLE, sName)
|
||||
.attr("id", sId);
|
||||
|
||||
// If link's text is default (wym-...), change it to the title.
|
||||
if (link.text().indexOf("wym-") === 0) {
|
||||
link.text(sName);
|
||||
}
|
||||
}
|
||||
hide_modal();
|
||||
},
|
||||
Cancel: function() {
|
||||
hide_modal();
|
||||
}
|
||||
},
|
||||
{},
|
||||
set_link_id
|
||||
);
|
||||
}
|
||||
|
||||
// IMAGE DIALOG
|
||||
if (dialogType == WYMeditor.DIALOG_IMAGE) {
|
||||
if (wym._selected_image) {
|
||||
$(`${wym._options.dialogImageSelector} ${wym._options.srcSelector}`).val(
|
||||
$(wym._selected_image).attr(WYMeditor.SRC)
|
||||
);
|
||||
$(`${wym._options.dialogImageSelector} ${wym._options.titleSelector}`).val(
|
||||
$(wym._selected_image).attr(WYMeditor.TITLE)
|
||||
);
|
||||
$(`${wym._options.dialogImageSelector} ${wym._options.altSelector}`).val(
|
||||
$(wym._selected_image).attr(WYMeditor.ALT)
|
||||
);
|
||||
}
|
||||
show_modal(
|
||||
"Image",
|
||||
"<div class='row'>" +
|
||||
"<label>URL</label><br>" +
|
||||
"<input type='text' class='wym_src' value='' size='40' />" +
|
||||
"</div>" +
|
||||
"<div class='row'>" +
|
||||
"<label>Alt text</label><br>" +
|
||||
"<input type='text' class='wym_alt' value='' size='40' />" +
|
||||
"</div>" +
|
||||
"<div class='row'>" +
|
||||
"<label>Title</label><br>" +
|
||||
"<input type='text' class='wym_title' value='' size='40' />" +
|
||||
"</div>",
|
||||
{
|
||||
Insert: function() {
|
||||
var sUrl = $(wym._options.srcSelector).val();
|
||||
if (sUrl.length > 0) {
|
||||
wym._exec(WYMeditor.INSERT_IMAGE, sStamp);
|
||||
$(`img[src$=${sStamp}]`, wym._doc.body)
|
||||
.attr(WYMeditor.SRC, sUrl)
|
||||
.attr(WYMeditor.TITLE, $(wym._options.titleSelector).val())
|
||||
.attr(WYMeditor.ALT, $(wym._options.altSelector).val());
|
||||
}
|
||||
hide_modal();
|
||||
},
|
||||
Cancel: function() {
|
||||
hide_modal();
|
||||
}
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// TABLE DIALOG
|
||||
if (dialogType == WYMeditor.DIALOG_TABLE) {
|
||||
show_modal(
|
||||
"Table",
|
||||
"<div class='row'>" +
|
||||
"<label>Caption</label><br>" +
|
||||
"<input type='text' class='wym_caption' value='' size='40' />" +
|
||||
"</div>" +
|
||||
"<div class='row'>" +
|
||||
"<label>Summary</label><br>" +
|
||||
"<input type='text' class='wym_summary' value='' size='40' />" +
|
||||
"</div>" +
|
||||
"<div class='row'>" +
|
||||
"<label>Number Of Rows<br></label>" +
|
||||
"<input type='text' class='wym_rows' value='3' size='3' />" +
|
||||
"</div>" +
|
||||
"<div class='row'>" +
|
||||
"<label>Number Of Cols<br></label>" +
|
||||
"<input type='text' class='wym_cols' value='2' size='3' />" +
|
||||
"</div>",
|
||||
{
|
||||
Insert: function() {
|
||||
var iRows = $(wym._options.rowsSelector).val();
|
||||
var iCols = $(wym._options.colsSelector).val();
|
||||
|
||||
if (iRows > 0 && iCols > 0) {
|
||||
var table = wym._doc.createElement(WYMeditor.TABLE);
|
||||
var newRow = null;
|
||||
|
||||
var sCaption = $(wym._options.captionSelector).val();
|
||||
|
||||
//we create the caption
|
||||
var newCaption = table.createCaption();
|
||||
newCaption.innerHTML = sCaption;
|
||||
|
||||
//we create the rows and cells
|
||||
for (var x = 0; x < iRows; x++) {
|
||||
newRow = table.insertRow(x);
|
||||
for (var y = 0; y < iCols; y++) {
|
||||
newRow.insertCell(y);
|
||||
}
|
||||
}
|
||||
|
||||
//set the summary attr
|
||||
$(table).attr("summary", $(wym._options.summarySelector).val());
|
||||
|
||||
//append the table after the selected container
|
||||
var node = $(wym.findUp(wym.container(), WYMeditor.MAIN_CONTAINERS)).get(0);
|
||||
if (!node || !node.parentNode) $(wym._doc.body).append(table);
|
||||
else $(node).after(table);
|
||||
}
|
||||
hide_modal();
|
||||
export default function pagesEditorOnload() {
|
||||
const pageId = $("#page-editor-content").attr("page_id");
|
||||
axios
|
||||
.get(`${getAppRoot()}api/pages/${pageId}`)
|
||||
.then(response => {
|
||||
const pageEditorInstance = Vue.extend(PageEditor);
|
||||
new pageEditorInstance({
|
||||
propsData: {
|
||||
pageId: pageId,
|
||||
content: response.data.content,
|
||||
title: response.data.title
|
||||
},
|
||||
Cancel: function() {
|
||||
hide_modal();
|
||||
}
|
||||
el: "#page-editor-content"
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
const response = e.response;
|
||||
if (typeof response.responseJSON !== "undefined") {
|
||||
Toast.error(response.responseJSON.err_msg);
|
||||
} else {
|
||||
Toast.error("An error occurred.");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// INSERT "GALAXY ITEM" LINK DIALOG
|
||||
if (
|
||||
dialogType == CONTROLS.DIALOG_HISTORY_LINK ||
|
||||
dialogType == CONTROLS.DIALOG_DATASET_LINK ||
|
||||
dialogType == CONTROLS.DIALOG_WORKFLOW_LINK ||
|
||||
dialogType == CONTROLS.DIALOG_PAGE_LINK ||
|
||||
dialogType == CONTROLS.DIALOG_VISUALIZATION_LINK
|
||||
) {
|
||||
// Based on item type, set useful vars.
|
||||
var item_info;
|
||||
switch (dialogType) {
|
||||
case CONTROLS.DIALOG_HISTORY_LINK:
|
||||
item_info = get_item_info(CONTROLS.ITEM_HISTORY);
|
||||
break;
|
||||
case CONTROLS.DIALOG_DATASET_LINK:
|
||||
item_info = get_item_info(CONTROLS.ITEM_DATASET);
|
||||
break;
|
||||
case CONTROLS.DIALOG_WORKFLOW_LINK:
|
||||
item_info = get_item_info(CONTROLS.ITEM_WORKFLOW);
|
||||
break;
|
||||
case CONTROLS.DIALOG_PAGE_LINK:
|
||||
item_info = get_item_info(CONTROLS.ITEM_PAGE);
|
||||
break;
|
||||
case CONTROLS.DIALOG_VISUALIZATION_LINK:
|
||||
item_info = get_item_info(CONTROLS.ITEM_VISUALIZATION);
|
||||
break;
|
||||
}
|
||||
|
||||
var grid = new GridView({
|
||||
url_base: item_info.list_ajax_url,
|
||||
embedded: true
|
||||
});
|
||||
const Galaxy = getGalaxyInstance();
|
||||
Galaxy.modal.show({
|
||||
title: `Insert Link to ${item_info.singular}`,
|
||||
body: $("<div/>")
|
||||
.append(grid.$el)
|
||||
.append(
|
||||
$("<div/>")
|
||||
.append('<input id="make-importable" type="checkbox" checked/>')
|
||||
.append(
|
||||
`Make the selected ${item_info.plural.toLowerCase()} accessible so that they can viewed by everyone.`
|
||||
)
|
||||
),
|
||||
closing_events: true,
|
||||
buttons: {
|
||||
Insert: function() {
|
||||
// Make selected items accessible (importable) ?
|
||||
var make_importable = false;
|
||||
if ($("#make-importable:checked").val() != null) make_importable = true;
|
||||
|
||||
// Insert links to history for each checked item.
|
||||
grid.$("input[name=id]:checked").each(function() {
|
||||
var item_id = $(this).val();
|
||||
|
||||
// Make item importable?
|
||||
if (make_importable) make_item_importable(item_info.controller, item_id, item_info.singular);
|
||||
|
||||
// Insert link(s) to item(s). This is done by getting item info and then manipulating wym.
|
||||
var url_template = configs.get_name_and_link_url + item_id;
|
||||
var ajax_url = url_template.replace("ITEM_CONTROLLER", item_info.controller);
|
||||
$.getJSON(ajax_url, returned_item_info => {
|
||||
// Get link text.
|
||||
wym._exec(WYMeditor.CREATE_LINK, sStamp);
|
||||
var link_text = $(`a[href=${sStamp}]`, wym._doc.body).text();
|
||||
|
||||
// Insert link: need to do different actions depending on link text.
|
||||
if (
|
||||
link_text == "" || // Firefox.
|
||||
link_text == sStamp // Safari
|
||||
) {
|
||||
// User selected no text; create link from scratch and use default text.
|
||||
wym.insert(
|
||||
`<a href='${returned_item_info.link}'>${item_info.singular} '${
|
||||
returned_item_info.name
|
||||
}'</a>`
|
||||
);
|
||||
} else {
|
||||
// Link created from selected text; add href and title.
|
||||
$(`a[href=${sStamp}]`, wym._doc.body)
|
||||
.attr(WYMeditor.HREF, returned_item_info.link)
|
||||
.attr(WYMeditor.TITLE, item_info.singular + item_id);
|
||||
}
|
||||
});
|
||||
});
|
||||
Galaxy.modal.hide();
|
||||
},
|
||||
Close: function() {
|
||||
Galaxy.modal.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// EMBED GALAXY OBJECT DIALOGS
|
||||
if (
|
||||
dialogType == CONTROLS.DIALOG_EMBED_HISTORY ||
|
||||
dialogType == CONTROLS.DIALOG_EMBED_DATASET ||
|
||||
dialogType == CONTROLS.DIALOG_EMBED_WORKFLOW ||
|
||||
dialogType == CONTROLS.DIALOG_EMBED_PAGE ||
|
||||
dialogType == CONTROLS.DIALOG_EMBED_VISUALIZATION
|
||||
) {
|
||||
// Based on item type, set useful vars.
|
||||
let item_info;
|
||||
switch (dialogType) {
|
||||
case CONTROLS.DIALOG_EMBED_HISTORY:
|
||||
item_info = get_item_info(CONTROLS.ITEM_HISTORY);
|
||||
break;
|
||||
case CONTROLS.DIALOG_EMBED_DATASET:
|
||||
item_info = get_item_info(CONTROLS.ITEM_DATASET);
|
||||
break;
|
||||
case CONTROLS.DIALOG_EMBED_WORKFLOW:
|
||||
item_info = get_item_info(CONTROLS.ITEM_WORKFLOW);
|
||||
break;
|
||||
case CONTROLS.DIALOG_EMBED_PAGE:
|
||||
item_info = get_item_info(CONTROLS.ITEM_PAGE);
|
||||
break;
|
||||
case CONTROLS.DIALOG_EMBED_VISUALIZATION:
|
||||
item_info = get_item_info(CONTROLS.ITEM_VISUALIZATION);
|
||||
break;
|
||||
}
|
||||
|
||||
const grid = new GridView({
|
||||
url_base: item_info.list_ajax_url,
|
||||
embedded: true
|
||||
});
|
||||
const Galaxy = getGalaxyInstance();
|
||||
Galaxy.modal.show({
|
||||
title: `Insert Link to ${item_info.singular}`,
|
||||
body: $("<div/>")
|
||||
.append(grid.$el)
|
||||
.append(
|
||||
$("<div/>")
|
||||
.append('<input id="make-importable" type="checkbox" checked/>')
|
||||
.append(
|
||||
`Make the selected ${item_info.plural.toLowerCase()} accessible so that they can viewed by everyone.`
|
||||
)
|
||||
),
|
||||
closing_events: true,
|
||||
buttons: {
|
||||
Embed: function() {
|
||||
// Make selected items accessible (importable) ?
|
||||
var make_importable = false;
|
||||
if ($("#make-importable:checked").val() != null) make_importable = true;
|
||||
|
||||
grid.$("input[name=id]:checked").each(function() {
|
||||
var elt = $(this);
|
||||
// Get item ID and name.
|
||||
var item_id = elt.val();
|
||||
// Use ':first' because there are many labels in table; the first one is the item name.
|
||||
var item_name = elt
|
||||
.closest("td")
|
||||
.next("td")
|
||||
.find("label")
|
||||
.text();
|
||||
|
||||
if (make_importable) make_item_importable(item_info.controller, item_id, item_info.singular);
|
||||
|
||||
// Embedded item HTML; item class is embedded in div container classes; this is necessary because the editor strips
|
||||
// all non-standard attributes when it returns its content (e.g. it will not return an element attribute of the form
|
||||
// item_class='History').
|
||||
var item_elt_id = `${item_info.iclass}-${item_id}`;
|
||||
var item_embed_html = [
|
||||
"<div id='",
|
||||
item_elt_id,
|
||||
"' class='embedded-item ",
|
||||
item_info.singular.toLowerCase(),
|
||||
" placeholder'>",
|
||||
"<p class='title'>",
|
||||
"Embedded Galaxy ",
|
||||
item_info.singular,
|
||||
" '",
|
||||
item_name,
|
||||
"'",
|
||||
"</p>",
|
||||
"<p class='content'>",
|
||||
"[Do not edit this block; Galaxy will fill it in with the annotated ",
|
||||
item_info.singular.toLowerCase(),
|
||||
" when it is displayed.]",
|
||||
"</p>",
|
||||
"</div>"
|
||||
].join("");
|
||||
|
||||
// Insert embedded item into document.
|
||||
wym.insert(item_embed_html);
|
||||
});
|
||||
Galaxy.modal.hide();
|
||||
},
|
||||
Close: function() {
|
||||
Galaxy.modal.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default function pagesEditorOnload(renderedConfigs = {}) {
|
||||
setPageConfigs(renderedConfigs);
|
||||
|
||||
// Generic error handling
|
||||
$(document).ajaxError((e, x) => {
|
||||
// console.log( e, x );
|
||||
var message = x.responseText || x.statusText || "Could not connect to server";
|
||||
show_modal("Server error", message, { "Ignore error": hide_modal });
|
||||
return false;
|
||||
});
|
||||
// Create editor
|
||||
$("[name=page_content]").wymeditor({
|
||||
skin: "galaxy",
|
||||
basePath: configs.editor_base_path,
|
||||
iframeBasePath: configs.iframe_base_path,
|
||||
// + WYMeditor.HTML
|
||||
boxHtml: `<table class='wym_box' width='100%' height='100%'><tr><td><div class='wym_area_top'>${
|
||||
WYMeditor.TOOLS
|
||||
}</div></td></tr><tr height='100%'><td><div class='wym_area_main' style='height: 100%;'>${
|
||||
// + WYMeditor.HTML
|
||||
WYMeditor.IFRAME
|
||||
}${WYMeditor.STATUS}</div></div></td></tr></table>`,
|
||||
toolsItems: [
|
||||
{ name: "Bold", title: "Strong", css: "wym_tools_strong" },
|
||||
{ name: "Italic", title: "Emphasis", css: "wym_tools_emphasis" },
|
||||
{
|
||||
name: "Superscript",
|
||||
title: _l("Superscript"),
|
||||
css: "wym_tools_superscript"
|
||||
},
|
||||
{
|
||||
name: "Subscript",
|
||||
title: _l("Subscript"),
|
||||
css: "wym_tools_subscript"
|
||||
},
|
||||
{
|
||||
name: "InsertOrderedList",
|
||||
title: "Ordered_List",
|
||||
css: "wym_tools_ordered_list"
|
||||
},
|
||||
{
|
||||
name: "InsertUnorderedList",
|
||||
title: "Unordered_List",
|
||||
css: "wym_tools_unordered_list"
|
||||
},
|
||||
{ name: "Indent", title: "Indent", css: "wym_tools_indent" },
|
||||
{ name: "Outdent", title: "Outdent", css: "wym_tools_outdent" },
|
||||
{ name: "Undo", title: "Undo", css: "wym_tools_undo" },
|
||||
{ name: "Redo", title: "Redo", css: "wym_tools_redo" },
|
||||
{ name: "CreateLink", title: "Link", css: "wym_tools_link" },
|
||||
{ name: "Unlink", title: "Unlink", css: "wym_tools_unlink" },
|
||||
{ name: "InsertImage", title: "Image", css: "wym_tools_image" },
|
||||
{ name: "InsertTable", title: "Table", css: "wym_tools_table" }
|
||||
]
|
||||
});
|
||||
// Get the editor object
|
||||
var editor = $.wymeditors(0);
|
||||
var save = callback => {
|
||||
show_modal("Saving page", "progress");
|
||||
|
||||
// Do save.
|
||||
$.ajax({
|
||||
url: configs.save_url,
|
||||
type: "POST",
|
||||
data: {
|
||||
id: configs.page_id,
|
||||
content: editor.xhtml(),
|
||||
annotations: JSON.stringify({}),
|
||||
// annotations: JSON.stringify(annotations),
|
||||
_: "true"
|
||||
},
|
||||
success: function() {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
};
|
||||
// Save button
|
||||
$("#save-button").click(() => {
|
||||
save(() => {
|
||||
hide_modal();
|
||||
});
|
||||
});
|
||||
// Close button
|
||||
$("#close-button").click(() => {
|
||||
// var new_content = editor.xhtml();
|
||||
// var changed = ( initial_content != new_content );
|
||||
var changed = false;
|
||||
if (changed) {
|
||||
var do_close = () => {
|
||||
window.onbeforeunload = undefined;
|
||||
window.document.location = configs.page_list_url;
|
||||
};
|
||||
show_modal(
|
||||
"Close editor",
|
||||
"There are unsaved changes to your page which will be lost.",
|
||||
{
|
||||
Cancel: hide_modal,
|
||||
"Save Changes": function() {
|
||||
save(do_close);
|
||||
}
|
||||
},
|
||||
{
|
||||
"Don't Save": do_close
|
||||
}
|
||||
);
|
||||
} else {
|
||||
window.document.location = configs.page_list_url;
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize galaxy elements.
|
||||
//init_galaxy_elts(editor);
|
||||
|
||||
//
|
||||
// Containers, Galaxy style
|
||||
//
|
||||
var containers_menu = $(
|
||||
"<div class='galaxy-page-editor-button'><a id='insert-galaxy-link' class='action-button popup' href='#'>Paragraph type</a></div>"
|
||||
);
|
||||
$(".wym_area_top").append(containers_menu);
|
||||
|
||||
// Add menu options.
|
||||
var items = {};
|
||||
$.each(editor._options.containersItems, (k, v) => {
|
||||
var tagname = v.name;
|
||||
items[v.title.replace("_", " ")] = () => {
|
||||
editor.container(tagname);
|
||||
};
|
||||
});
|
||||
make_popupmenu(containers_menu, items);
|
||||
|
||||
//
|
||||
// Create 'Insert Link to Galaxy Object' menu.
|
||||
//
|
||||
|
||||
// Add menu button.
|
||||
var insert_link_menu_button = $(
|
||||
"<div><a id='insert-galaxy-link' class='action-button popup' href='#'>Insert Link to Galaxy Object</a></div>"
|
||||
).addClass("galaxy-page-editor-button");
|
||||
$(".wym_area_top").append(insert_link_menu_button);
|
||||
|
||||
// Add menu options.
|
||||
make_popupmenu(insert_link_menu_button, {
|
||||
"Insert History Link": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_HISTORY_LINK);
|
||||
},
|
||||
"Insert Dataset Link": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_DATASET_LINK);
|
||||
},
|
||||
"Insert Workflow Link": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_WORKFLOW_LINK);
|
||||
},
|
||||
"Insert Page Link": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_PAGE_LINK);
|
||||
},
|
||||
"Insert Visualization Link": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_VISUALIZATION_LINK);
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Create 'Embed Galaxy Object' menu.
|
||||
//
|
||||
|
||||
// Add menu button.
|
||||
var embed_object_button = $(
|
||||
"<div><a id='embed-galaxy-object' class='action-button popup' href='#'>Embed Galaxy Object</a></div>"
|
||||
).addClass("galaxy-page-editor-button");
|
||||
$(".wym_area_top").append(embed_object_button);
|
||||
|
||||
// Add menu options.
|
||||
make_popupmenu(embed_object_button, {
|
||||
"Embed History": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_EMBED_HISTORY);
|
||||
},
|
||||
"Embed Dataset": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_EMBED_DATASET);
|
||||
},
|
||||
"Embed Workflow": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_EMBED_WORKFLOW);
|
||||
},
|
||||
"Embed Visualization": function() {
|
||||
editor.dialog(CONTROLS.DIALOG_EMBED_VISUALIZATION);
|
||||
}
|
||||
//"Embed Page": function() {
|
||||
// editor.dialog(CONTROLS.DIALOG_EMBED_PAGE);
|
||||
//}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import $ from "jquery";
|
||||
import { make_popup_menus } from "ui/popupmenu";
|
||||
|
||||
export function render_embedded_items() {
|
||||
$(".embedded-item").each(function() {
|
||||
var container = $(this);
|
||||
if (container.hasClass("history")) {
|
||||
return;
|
||||
}
|
||||
//note: we can't do the same override for visualizations
|
||||
// bc builtins (like trackster) need the handlers/ajax below to work.
|
||||
// instead: (for registry visualizations) we'll clear the handlers below
|
||||
// and add new ones (in embed_in_frame.mako) ...ugh.
|
||||
|
||||
// Show embedded item.
|
||||
var show_embedded_item = function() {
|
||||
var ajax_url = container.find("input[type=hidden]").val();
|
||||
|
||||
// Only get item content if it's not already there.
|
||||
var item_content = $.trim(container.find(".item-content").text());
|
||||
if (!item_content) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: ajax_url,
|
||||
error: function() {
|
||||
alert("Getting item content failed.");
|
||||
},
|
||||
success: function(item_content) {
|
||||
container.find(".summary-content").hide("fast");
|
||||
container.find(".item-content").html(item_content);
|
||||
container.find(".expanded-content").show("fast");
|
||||
container.find(".toggle-expand").hide();
|
||||
container.find(".toggle").show();
|
||||
|
||||
make_popup_menus();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
container.find(".summary-content").hide("fast");
|
||||
container.find(".expanded-content").show("fast");
|
||||
container.find(".toggle-expand").hide();
|
||||
container.find(".toggle").show();
|
||||
}
|
||||
};
|
||||
|
||||
// Hide embedded item.
|
||||
var hide_embedded_item = function() {
|
||||
container.find(".expanded-content").hide("fast");
|
||||
container.find(".summary-content").show("fast");
|
||||
container.find(".toggle").hide();
|
||||
container.find(".toggle-expand").show();
|
||||
};
|
||||
|
||||
// Setup toggle expand.
|
||||
var toggle_expand = $(this).find(".toggle-expand");
|
||||
toggle_expand.click(function() {
|
||||
show_embedded_item();
|
||||
return false;
|
||||
});
|
||||
|
||||
// Setup toggle contract.
|
||||
var toggle_contract = $(this).find(".toggle");
|
||||
toggle_contract.click(function() {
|
||||
hide_embedded_item();
|
||||
return false;
|
||||
});
|
||||
|
||||
// Setup toggle embed.
|
||||
var toggle_embed = $(this).find(".toggle-embed");
|
||||
toggle_embed.click(function() {
|
||||
if (container.find(".expanded-content").is(":visible")) {
|
||||
hide_embedded_item();
|
||||
} else {
|
||||
show_embedded_item();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -81,6 +81,9 @@ export var View = Backbone.View.extend({
|
||||
// link elements
|
||||
this.$header = this.$(".modal-header");
|
||||
this.$dialog = this.$(".modal-dialog");
|
||||
if (this.options.extra_class) {
|
||||
this.$dialog.addClass(this.options.extra_class);
|
||||
}
|
||||
this.$body = this.$(".modal-body");
|
||||
this.$footer = this.$(".modal-footer");
|
||||
this.$backdrop = this.$(".modal-backdrop");
|
||||
|
||||
@@ -85,3 +85,18 @@ pre.code {
|
||||
color: white;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
// increase visibility of links within alert boxes
|
||||
.alert-info, .alert-danger, .alert-warning, .alert-success {
|
||||
a {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
// increase visibility of dropdown menu section headers
|
||||
.dropdown-header {
|
||||
font-size: 1rem;
|
||||
color: $brand-dark;
|
||||
padding: .5rem 1.5rem .5rem 1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -256,6 +256,8 @@ $ui-margin-horizontal-large: $margin-v * 2;
|
||||
.popover {
|
||||
max-width: 700px;
|
||||
.popover-header {
|
||||
color: $brand-white;
|
||||
background-color: $brand-primary;
|
||||
i {
|
||||
@extend .float-right;
|
||||
@extend .pl-4;
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd `dirname $0`
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
. ./scripts/common_startup_functions.sh
|
||||
|
||||
setup_python
|
||||
|
||||
python ./scripts/create_db.py $@
|
||||
python ./scripts/create_db.py "$@"
|
||||
|
||||
+2
-2
@@ -42,8 +42,8 @@ help:
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
@echo " updaterst to update Sphinx RST files for lib/ to reflect code structure changes"
|
||||
|
||||
source/dev/schema.md: parse_gx_xsd.py schema_template.md ../lib/galaxy/tools/xsd/galaxy.xsd ## Build Github-flavored Markdown from Galaxy Tool XSD (expects lxml in environment)
|
||||
python parse_gx_xsd.py schema_template.md ../lib/galaxy/tools/xsd/galaxy.xsd > $@
|
||||
source/dev/schema.md: parse_gx_xsd.py schema_template.md ../lib/galaxy/tool_util/xsd/galaxy.xsd ## Build Github-flavored Markdown from Galaxy Tool XSD (expects lxml in environment)
|
||||
python parse_gx_xsd.py schema_template.md ../lib/galaxy/tool_util/xsd/galaxy.xsd > $@
|
||||
|
||||
source/api/api.rst: source/lib/galaxy.webapps.galaxy.api.rst
|
||||
printf "Galaxy API\n==========\n" > $@
|
||||
|
||||
@@ -393,6 +393,11 @@ Enhancements
|
||||
Fixes
|
||||
-------------------------------
|
||||
|
||||
* Explicitly specify system user under which Kubernetes jobs should run.
|
||||
`Pull Request 8103`_
|
||||
* Ensure Kubernetes job querries filter the same namespace as defined in the
|
||||
job destination.
|
||||
`Pull Request 8092`_
|
||||
* Fix client styleguide asset handling.
|
||||
`Pull Request 7664`_
|
||||
* Handle subworkflows in view workflow mako
|
||||
|
||||
@@ -17,7 +17,7 @@ Highlights
|
||||
user release notes instead. A huge thanks to `@erasche <https://github.com/erasche>`__
|
||||
for getting the ball rolling on this initiative.
|
||||
|
||||
Check out the `19.05 user release notes<https://docs.galaxyproject.org/en/release_19.05/releases/19.05_announce_user.html>`__.
|
||||
Check out the `19.05 user release notes <https://docs.galaxyproject.org/en/release_19.05/releases/19.05_announce_user.html>`__.
|
||||
|
||||
**Login and Registration Rewrite**
|
||||
The march toward replacing templated backend generated HTML with modern, reactive
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
|
||||
.. github_links
|
||||
.. _Pull Request 8103: https://github.com/galaxyproject/galaxy/pull/8103
|
||||
.. _Pull Request 8092: https://github.com/galaxyproject/galaxy/pull/8092
|
||||
.. _Pull Request 4659: https://github.com/galaxyproject/galaxy/pull/4659
|
||||
.. _Pull Request 5495: https://github.com/galaxyproject/galaxy/pull/5495
|
||||
.. _Pull Request 6321: https://github.com/galaxyproject/galaxy/pull/6321
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd `dirname $0`
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
. ./scripts/common_startup_functions.sh
|
||||
|
||||
@@ -9,5 +9,5 @@ setup_python
|
||||
for file in $1/split_info*.json
|
||||
do
|
||||
# echo processing $file
|
||||
python ./scripts/extract_dataset_part.py $file
|
||||
python ./scripts/extract_dataset_part.py "$file"
|
||||
done
|
||||
|
||||
@@ -133,7 +133,7 @@ class AuthnzManager(object):
|
||||
def _unify_provider_name(self, provider):
|
||||
if provider.lower() in self.oidc_backends_config:
|
||||
return provider.lower()
|
||||
for k, v in BACKENDS_NAME.iteritems():
|
||||
for k, v in BACKENDS_NAME.items():
|
||||
if v == provider:
|
||||
return k.lower()
|
||||
return None
|
||||
|
||||
@@ -86,7 +86,7 @@ DISCONNECT_PIPELINE = (
|
||||
class PSAAuthnz(IdentityProvider):
|
||||
def __init__(self, provider, oidc_config, oidc_backend_config):
|
||||
self.config = {'provider': provider.lower()}
|
||||
for key, value in oidc_config.iteritems():
|
||||
for key, value in oidc_config.items():
|
||||
self.config[setting_name(key)] = value
|
||||
|
||||
self.config[setting_name('USER_MODEL')] = 'models.User'
|
||||
|
||||
@@ -50,7 +50,7 @@ PATH_DEFAULTS = dict(
|
||||
error_report_file=['config/error_report.yml', 'config/error_report.yml.sample'],
|
||||
oidc_config_file=['config/oidc_config.yml', 'config/oidc_config.yml.sample'],
|
||||
oidc_backends_config_file=['config/oidc_backends_config.yml', 'config/oidc_backends_config.yml.sample'],
|
||||
dependency_resolvers_config_file=['config/dependency_resolvers_conf.xml', 'dependency_resolvers_conf.xml'],
|
||||
dependency_resolvers_config_file=['config/dependency_resolvers_conf.xml', 'dependency_resolvers_conf.xml', None],
|
||||
job_resource_params_file=['config/job_resource_params_conf.xml', 'job_resource_params_conf.xml'],
|
||||
workflow_resource_params_file=['config/workflow_resource_params_conf.xml', 'workflow_resource_params_conf.xml'],
|
||||
migrated_tools_config=['migrated_tools_conf.xml', 'config/migrated_tools_conf.xml'],
|
||||
@@ -142,6 +142,9 @@ def find_path(kwargs, var, root):
|
||||
path = kwargs.get(var)
|
||||
else:
|
||||
for default in defaults:
|
||||
if default is None:
|
||||
# if None is the final default - just return that.
|
||||
return None
|
||||
if os.path.exists(resolve_path(default, root)):
|
||||
path = default
|
||||
break
|
||||
@@ -506,6 +509,7 @@ class Configuration(object):
|
||||
self.use_cached_dependency_manager = use_cached_dependency_manager
|
||||
self.tool_dependency_cache_dir = tool_dependency_cache_dir
|
||||
self.precache_dependencies = precache_dependencies
|
||||
self.dependency_resolvers = kwargs.get("dependency_resolvers")
|
||||
# Deployers may either specify a complete list of mapping files or get the default for free and just
|
||||
# specify a local mapping file to adapt and extend the default one.
|
||||
if "conda_mapping_files" in kwargs:
|
||||
@@ -938,7 +942,10 @@ def parse_dependency_options(kwargs, root, dependency_resolvers_config_file):
|
||||
precache_dependencies = string_as_bool(kwargs.get("precache_dependencies", 'True'))
|
||||
else:
|
||||
tool_dependency_dir = None
|
||||
use_tool_dependencies = os.path.exists(dependency_resolvers_config_file)
|
||||
if dependency_resolvers_config_file is None:
|
||||
use_tool_dependencies = bool(kwargs.get("dependency_resolvers", None))
|
||||
else:
|
||||
use_tool_dependencies = os.path.exists(dependency_resolvers_config_file)
|
||||
tool_dependency_cache_dir = None
|
||||
precache_dependencies = False
|
||||
use_cached_dependency_manager = False
|
||||
|
||||
@@ -76,6 +76,8 @@ def stream_to_open_named_file(stream, fd, filename, source_encoding=None, source
|
||||
else:
|
||||
# Compressed files must be encoded after they are uncompressed in the upload utility,
|
||||
# while binary files should not be encoded at all.
|
||||
if isinstance(chunk, text_type):
|
||||
chunk = chunk.encode(target_encoding, target_error)
|
||||
os.write(fd, chunk)
|
||||
os.close(fd)
|
||||
return filename
|
||||
|
||||
@@ -69,7 +69,8 @@ class ConditionalDependencies(object):
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
job_conf_dict = yaml.safe_load(job_conf_path)
|
||||
with open("job_conf_path", "r") as f:
|
||||
job_conf_dict = yaml.safe_load(f)
|
||||
load_job_config_dict(job_conf_dict)
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
|
||||
@@ -15,6 +15,7 @@ mock = "*"
|
||||
NoseHTML = "*"
|
||||
pygithub3 = {version = "*", markers = "python_version < '3'"}
|
||||
pytest = "*"
|
||||
pytest-cov = "*"
|
||||
pytest-html = "*"
|
||||
pytest-pythonpath = "*"
|
||||
pytest-postgresql = "*"
|
||||
@@ -75,8 +76,8 @@ pyparsing = "*"
|
||||
"Fabric3" = "*"
|
||||
paramiko = "*"
|
||||
python-genomespaceclient = "<2.0"
|
||||
social_auth_core = {version = "==3.1.0", extras = ['openidconnect']}
|
||||
cloudauthz = "<=0.2.0"
|
||||
social_auth_core = {version = "==3.1.0+gx0", extras = ['openidconnect']}
|
||||
cloudauthz = "<=0.4.0"
|
||||
gxformat2 = "*"
|
||||
|
||||
[requires]
|
||||
|
||||
@@ -4,45 +4,49 @@ alabaster==0.7.12
|
||||
argh==0.26.2
|
||||
atomicwrites==1.3.0
|
||||
attrs==19.1.0
|
||||
babel==2.6.0
|
||||
certifi==2019.3.9
|
||||
babel==2.7.0
|
||||
certifi==2019.6.16
|
||||
chardet==3.0.4
|
||||
commonmark==0.8.1
|
||||
commonmark==0.9.0
|
||||
configparser==3.7.4 ; python_version < '3.2'
|
||||
contextlib2==0.5.5 ; python_version < '3.5'
|
||||
coverage==4.5.3
|
||||
docutils==0.14
|
||||
funcsigs==1.0.2 ; python_version < '3.3'
|
||||
future==0.17.1
|
||||
gunicorn==19.9.0
|
||||
idna==2.8
|
||||
imagesize==1.1.0
|
||||
importlib-metadata==0.18
|
||||
jinja2==2.10.1
|
||||
lxml==4.3.3
|
||||
lxml==4.3.4
|
||||
markdown==2.6.11
|
||||
markupsafe==1.1.1
|
||||
mirakuru==1.1.0
|
||||
mock==2.0.0
|
||||
mock==3.0.5
|
||||
more-itertools==5.0.0
|
||||
nose==1.3.7
|
||||
nosehtml==0.4.5
|
||||
packaging==19.0
|
||||
pathlib2==2.3.2 ; python_version < '3'
|
||||
pathtools==0.1.2
|
||||
pbr==5.1.3
|
||||
pluggy==0.9.0
|
||||
pluggy==0.12.0
|
||||
port-for==0.4
|
||||
psutil==5.6.1
|
||||
psutil==5.6.3
|
||||
py==1.8.0
|
||||
pygithub3==0.5.1 ; python_version < '3'
|
||||
pygments==2.3.1
|
||||
pygments==2.4.2
|
||||
pyparsing==2.4.0
|
||||
pytest-cov==2.7.1
|
||||
pytest-html==1.20.0
|
||||
pytest-metadata==1.8.0
|
||||
pytest-postgresql==1.4.0
|
||||
pytest-postgresql==1.4.1
|
||||
pytest-pythonpath==0.7.3
|
||||
pytest==4.4.1
|
||||
pytest==4.6.3
|
||||
pytz==2019.1
|
||||
pyyaml==5.1
|
||||
pyyaml==5.1.1
|
||||
recommonmark==0.5.0
|
||||
requests==2.21.0
|
||||
requests==2.22.0
|
||||
scandir==1.10.0 ; python_version < '3.5'
|
||||
selenium==3.141.0
|
||||
six==1.11.0
|
||||
@@ -50,9 +54,11 @@ snowballstemmer==1.2.1
|
||||
sphinx-markdown-tables==0.0.9
|
||||
sphinx-rtd-theme==0.4.3
|
||||
sphinx==1.8.5
|
||||
sphinxcontrib-websupport==1.1.0
|
||||
testfixtures==6.7.0
|
||||
sphinxcontrib-websupport==1.1.2
|
||||
testfixtures==6.9.0
|
||||
twill==0.9.1 ; python_version < '3'
|
||||
typing==3.6.6 ; python_version < '3.5'
|
||||
urllib3==1.24.2 ; python_version == '2.7'
|
||||
urllib3==1.25.3 ; python_version == '2.7'
|
||||
watchdog==0.9.0
|
||||
wcwidth==0.1.7 ; sys_platform != 'win32'
|
||||
zipp==0.5.1
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
-i https://wheels.galaxyproject.org/simple
|
||||
--extra-index-url https://pypi.python.org/simple
|
||||
adal==1.2.1
|
||||
amqp==2.4.2
|
||||
amqp==2.5.0
|
||||
appdirs==1.4.3
|
||||
asn1crypto==0.24.0
|
||||
attrs==19.1.0
|
||||
avro==1.8.1 ; python_version < '3'
|
||||
azure-common==1.1.14
|
||||
azure-cosmosdb-nspkg==2.0.2
|
||||
@@ -16,9 +17,9 @@ azure-mgmt-resource==2.0.0
|
||||
azure-mgmt-storage==2.0.0
|
||||
azure-nspkg==3.0.2
|
||||
azure-storage-blob==1.3.1
|
||||
azure-storage-common==1.4.0
|
||||
azure-storage-common==1.4.2
|
||||
azure-storage-nspkg==3.1.0
|
||||
babel==2.6.0
|
||||
babel==2.7.0
|
||||
bagit==1.6.4
|
||||
bcrypt==3.1.6
|
||||
bdbag==1.4.1
|
||||
@@ -28,21 +29,21 @@ bleach==3.1.0
|
||||
boltons==19.1.0
|
||||
boto3==1.9.114
|
||||
boto==2.49.0
|
||||
botocore==1.12.133
|
||||
botocore==1.12.169
|
||||
bx-python==0.8.2
|
||||
bz2file==0.98 ; python_version < '3.3'
|
||||
cachecontrol==0.11.7
|
||||
cachetools==3.1.0
|
||||
certifi==2019.3.9
|
||||
cachetools==3.1.1
|
||||
certifi==2019.6.16
|
||||
cffi==1.12.3
|
||||
chardet==3.0.4
|
||||
cheetah3==3.2.2
|
||||
cliff==2.14.1
|
||||
cloudauthz==0.2.0
|
||||
cheetah3==3.2.3
|
||||
cliff==2.15.0
|
||||
cloudauthz==0.4.0
|
||||
cloudbridge==2.0.0
|
||||
cmd2==0.8.9
|
||||
contextlib2==0.5.5 ; python_version < '3.5'
|
||||
cryptography==2.6.1
|
||||
cryptography==2.7
|
||||
cwltool==1.0.20180721142728
|
||||
debtcollector==1.21.0
|
||||
decorator==4.4.0
|
||||
@@ -65,7 +66,7 @@ google-auth-httplib2==0.0.3
|
||||
google-auth==1.6.3
|
||||
gxformat2==0.8.3
|
||||
h5py==2.9.0
|
||||
httplib2==0.12.1
|
||||
httplib2==0.13.0
|
||||
idna==2.8
|
||||
ipaddress==1.0.22 ; python_version < '3.3'
|
||||
isa-rwval==0.10.7
|
||||
@@ -74,12 +75,12 @@ isodate==0.6.0
|
||||
jmespath==0.9.4
|
||||
jsonpatch==1.23
|
||||
jsonpointer==2.0
|
||||
jsonschema==2.6.0
|
||||
jsonschema==3.0.1
|
||||
keystoneauth1==3.14.0
|
||||
kombu==4.5.0
|
||||
kombu==4.6.3
|
||||
lockfile==0.12.2
|
||||
lxml==4.3.3
|
||||
mako==1.0.9
|
||||
lxml==4.3.4
|
||||
mako==1.0.12
|
||||
markupsafe==1.1.1
|
||||
mercurial==3.7.3 ; python_version < '3'
|
||||
mistune==0.8.4
|
||||
@@ -94,35 +95,35 @@ netifaces==0.10.9
|
||||
networkx==1.11
|
||||
nodeenv==1.3.3
|
||||
nose==1.3.7
|
||||
numpy==1.16.2
|
||||
numpy==1.16.4
|
||||
oauth2client==4.1.3
|
||||
oauthlib==3.0.1
|
||||
openstacksdk==0.17.0
|
||||
os-client-config==1.32.0
|
||||
os-service-types==1.6.0
|
||||
os-service-types==1.7.0
|
||||
osc-lib==1.12.1
|
||||
oslo.config==6.8.1
|
||||
oslo.config==6.10.0
|
||||
oslo.context==2.22.1
|
||||
oslo.i18n==3.23.1
|
||||
oslo.log==3.42.3
|
||||
oslo.serialization==2.28.2
|
||||
oslo.utils==3.40.3
|
||||
oslo.log==3.44.0
|
||||
oslo.serialization==2.29.1
|
||||
oslo.utils==3.41.0
|
||||
packaging==19.0
|
||||
paramiko==2.4.2
|
||||
paramiko==2.5.0
|
||||
parsley==1.3
|
||||
paste==3.0.8
|
||||
pastedeploy==2.0.1
|
||||
pastescript==3.1.0
|
||||
pathlib2==2.3.2 ; python_version < '3'
|
||||
pbr==5.1.3
|
||||
pbr==5.3.0
|
||||
prettytable==0.7.2
|
||||
prov==1.5.1
|
||||
psutil==5.6.1
|
||||
pulsar-galaxy-lib==0.11.0
|
||||
pyasn1-modules==0.2.4
|
||||
psutil==5.6.3
|
||||
pulsar-galaxy-lib==0.12.1
|
||||
pyasn1-modules==0.2.5
|
||||
pyasn1==0.4.5
|
||||
pycparser==2.19
|
||||
pycryptodome==3.8.1
|
||||
pycryptodome==3.8.2
|
||||
pyeventsystem==0.1.0
|
||||
pyinotify==0.9.6 ; sys_platform != 'win32' and sys_platform != 'darwin' and sys_platform != 'sunos5'
|
||||
pyjwt==1.7.1
|
||||
@@ -131,6 +132,7 @@ pynacl==1.3.0
|
||||
pyopenssl==19.0.0
|
||||
pyparsing==2.4.0
|
||||
pyperclip==1.7.0
|
||||
pyrsistent==0.15.2
|
||||
pysam==0.15.2
|
||||
pysftp==0.2.9
|
||||
python-cinderclient==4.0.0
|
||||
@@ -144,20 +146,20 @@ python-novaclient==11.0.0
|
||||
python-openid==2.2.5 ; python_version < '3.0'
|
||||
python-swiftclient==3.6.0
|
||||
pytz==2019.1
|
||||
pyyaml==5.1
|
||||
pyyaml==5.1.1
|
||||
rdflib-jsonld==0.4.0
|
||||
rdflib==4.2.2
|
||||
repoze.lru==0.7
|
||||
requests-oauthlib==1.2.0
|
||||
requests-toolbelt==0.9.1
|
||||
requests==2.21.0
|
||||
requests==2.22.0
|
||||
requestsexceptions==1.4.0
|
||||
rfc3986==1.2.0
|
||||
rfc3986==1.3.2
|
||||
routes==2.4.1
|
||||
rsa==4.0
|
||||
ruamel.ordereddict==0.4.13 ; platform_python_implementation == 'CPython' and python_version <= '2.7'
|
||||
ruamel.yaml==0.15.92
|
||||
s3transfer==0.2.0
|
||||
ruamel.yaml==0.15.97
|
||||
s3transfer==0.2.1
|
||||
scandir==1.10.0 ; python_version < '3.5'
|
||||
schema-salad==2.7.20181126142424
|
||||
shellescape==3.4.1
|
||||
@@ -165,11 +167,11 @@ simplejson==3.16.0
|
||||
six==1.11.0
|
||||
social-auth-core[openidconnect]==3.1.0+gx0
|
||||
sqlalchemy-migrate==0.12.0
|
||||
sqlalchemy-utils==0.33.11
|
||||
sqlalchemy==1.2.18
|
||||
sqlalchemy-utils==0.34.0
|
||||
sqlalchemy==1.3.4
|
||||
sqlparse==0.3.0
|
||||
stevedore==1.30.1
|
||||
subprocess32==3.5.3 ; python_version < '3.0'
|
||||
subprocess32==3.5.4 ; python_version < '3.0'
|
||||
svgwrite==1.2.1
|
||||
tempita==0.5.2
|
||||
tenacity==4.12.0
|
||||
@@ -178,10 +180,10 @@ typing==3.6.6 ; python_version < '3.5'
|
||||
tzlocal==1.5.1
|
||||
unicodecsv==0.14.1 ; python_version < '3.0'
|
||||
uritemplate==3.0.0
|
||||
urllib3==1.24.2 ; python_version == '2.7'
|
||||
urllib3==1.25.3 ; python_version == '2.7'
|
||||
uwsgi==2.0.18
|
||||
vine==1.3.0
|
||||
warlock==1.3.0
|
||||
warlock==1.3.3
|
||||
wcwidth==0.1.7 ; sys_platform != 'win32'
|
||||
webencodings==0.5.1
|
||||
webob==1.8.5
|
||||
|
||||
@@ -65,6 +65,7 @@ for env in $ENVS; do
|
||||
-e "s/^futures==\([^ ;]\{1,\}\).*$/futures==\1 ; python_version == '2.6' or python_version == '2.7'/" \
|
||||
-e "s/^monotonic==\([^ ;]\{1,\}\).*$/monotonic==\1/" \
|
||||
-e "s/^more-itertools==\([^ ;]\{1,\}\).*$/more-itertools==\1/" \
|
||||
-e "s/^paste==\([^ ;]\{1,\}\).*$/paste==\1/" \
|
||||
-e "s/^py2-ipaddress==\([^ ;]\{1,\}\).*$/py2-ipaddress==\1 ; python_version < '3'/" \
|
||||
-e "s/^pyinotify==\([^ ;]\{1,\}\).*$/pyinotify==\1 ; sys_platform != 'win32' and sys_platform != 'darwin' and sys_platform != 'sunos5'/" \
|
||||
-e "s/^python-dateutil==\([^ ;]\{1,\}\).*$/python-dateutil==\1/" \
|
||||
|
||||
@@ -11,8 +11,8 @@ class JobMetricFormatter(object):
|
||||
def seconds_to_str(value):
|
||||
"""Convert seconds to a simple simple string describing the amount of time."""
|
||||
if value < 60:
|
||||
return "%s seconds" % value
|
||||
return "%s seconds" % round(value, 2)
|
||||
elif value < 3600:
|
||||
return "%s minutes" % (value / 60)
|
||||
return "%s minutes" % round(value / 60, 2)
|
||||
else:
|
||||
return "%s hours and %s minutes" % (value / 3600, (value % 3600) / 60)
|
||||
return "%s hours and %s minutes" % (round(value / 3600, 2), round((value % 3600) / 60, 2))
|
||||
|
||||
@@ -1456,7 +1456,8 @@ class JobWrapper(HasResourceParameters):
|
||||
if not os.path.exists(version_filename):
|
||||
version_filename = self.get_version_string_path_legacy()
|
||||
if os.path.exists(version_filename):
|
||||
self.version_string = open(version_filename).read()
|
||||
with open(version_filename, 'rb') as fh:
|
||||
self.version_string = galaxy.util.shrink_and_unicodify(fh.read())
|
||||
os.unlink(version_filename)
|
||||
|
||||
outputs_to_working_directory = util.asbool(self.get_destination_configuration("outputs_to_working_directory", False))
|
||||
|
||||
@@ -434,7 +434,7 @@ class JobHandlerQueue(Monitors):
|
||||
model.Dataset.deleted,
|
||||
model.Dataset.purged,
|
||||
model.Dataset.state,
|
||||
).join(job_to_input) \
|
||||
).join(job_to_input.job) \
|
||||
.join(input_association) \
|
||||
.join(model.Dataset) \
|
||||
.filter(model.Job.id.in_(job_ids_to_check)) \
|
||||
|
||||
@@ -54,6 +54,8 @@ class KubernetesJobRunner(AsynchronousJobRunner):
|
||||
k8s_job_api_version=dict(map=str, default="batch/v1"),
|
||||
k8s_supplemental_group_id=dict(map=str),
|
||||
k8s_pull_policy=dict(map=str, default="Default"),
|
||||
k8s_run_as_user_id=dict(map=str, valid=lambda s: s == "$uid" or s.isdigit()),
|
||||
k8s_run_as_group_id=dict(map=str, valid=lambda s: s == "$gid" or s.isdigit()),
|
||||
k8s_fs_group_id=dict(map=int),
|
||||
k8s_default_requests_cpu=dict(map=str, default=None),
|
||||
k8s_default_requests_memory=dict(map=str, default=None),
|
||||
@@ -76,6 +78,8 @@ class KubernetesJobRunner(AsynchronousJobRunner):
|
||||
|
||||
self._galaxy_instance_id = self.__get_galaxy_instance_id()
|
||||
|
||||
self._run_as_user_id = self.__get_run_as_user_id()
|
||||
self._run_as_group_id = self.__get_run_as_group_id()
|
||||
self._supplemental_group = self.__get_supplemental_group()
|
||||
self._fs_group = self.__get_fs_group()
|
||||
self._default_pull_policy = self.__get_pull_policy()
|
||||
@@ -169,6 +173,24 @@ class KubernetesJobRunner(AsynchronousJobRunner):
|
||||
return self.runner_params['k8s_pull_policy']
|
||||
return None
|
||||
|
||||
def __get_run_as_user_id(self):
|
||||
if "k8s_run_as_user_id" in self.runner_params:
|
||||
run_as_user = self.runner_params["k8s_run_as_user_id"]
|
||||
if run_as_user == "$uid":
|
||||
return os.getuid()
|
||||
else:
|
||||
return int(self.runner_params["k8s_run_as_user_id"])
|
||||
return None
|
||||
|
||||
def __get_run_as_group_id(self):
|
||||
if "k8s_run_as_group_id" in self.runner_params:
|
||||
run_as_group = self.runner_params["k8s_run_as_group_id"]
|
||||
if run_as_group == "$gid":
|
||||
return self.app.config.gid
|
||||
else:
|
||||
return int(self.runner_params["k8s_run_as_group_id"])
|
||||
return None
|
||||
|
||||
def __get_supplemental_group(self):
|
||||
if "k8s_supplemental_group_id" in self.runner_params:
|
||||
try:
|
||||
@@ -238,17 +260,21 @@ class KubernetesJobRunner(AsynchronousJobRunner):
|
||||
}
|
||||
# TODO include other relevant elements that people might want to use from
|
||||
# TODO http://kubernetes.io/docs/api-reference/v1/definitions/#_v1_podspec
|
||||
|
||||
if self._supplemental_group and self._supplemental_group > 0:
|
||||
k8s_spec_template["spec"]["securityContext"] = dict(supplementalGroups=[self._supplemental_group])
|
||||
if self._fs_group and self._fs_group > 0:
|
||||
if "securityContext" in k8s_spec_template["spec"]:
|
||||
k8s_spec_template["spec"]["securityContext"]["fsGroup"] = self._fs_group
|
||||
else:
|
||||
k8s_spec_template["spec"]["securityContext"] = dict(fsGroup=self._fs_group)
|
||||
|
||||
k8s_spec_template["spec"]["securityContext"] = self.__get_k8s_security_context()
|
||||
return k8s_spec_template
|
||||
|
||||
def __get_k8s_security_context(self):
|
||||
security_context = {}
|
||||
if self._run_as_user_id:
|
||||
security_context["runAsUser"] = self._run_as_user_id
|
||||
if self._run_as_group_id:
|
||||
security_context["runAsGroup"] = self._run_as_group_id
|
||||
if self._supplemental_group and self._supplemental_group > 0:
|
||||
security_context["supplementalGroups"] = [self._supplemental_group]
|
||||
if self._fs_group and self._fs_group > 0:
|
||||
security_context["fsGroup"] = self._fs_group
|
||||
return security_context
|
||||
|
||||
def __get_k8s_restart_policy(self, job_wrapper):
|
||||
"""The default Kubernetes restart policy for Jobs"""
|
||||
return "Never"
|
||||
@@ -475,7 +501,8 @@ class KubernetesJobRunner(AsynchronousJobRunner):
|
||||
marks the job for resubmission (resubmit logic is part of destinations).
|
||||
"""
|
||||
|
||||
pods = Pod.objects(self._pykube_api).filter(selector="app=%s" % job_state.job_id)
|
||||
pods = Pod.objects(self._pykube_api).filter(selector="app=%s" % job_state.job_id,
|
||||
namespace=self.runner_params['k8s_namespace'])
|
||||
pod = Pod(self._pykube_api, pods.response['items'][0])
|
||||
|
||||
if pod.obj['status']['phase'] == "Failed" and \
|
||||
@@ -488,8 +515,9 @@ class KubernetesJobRunner(AsynchronousJobRunner):
|
||||
"""Attempts to delete a dispatched job to the k8s cluster"""
|
||||
job = job_wrapper.get_job()
|
||||
try:
|
||||
jobs = Job.objects(self._pykube_api).filter(selector="app=" +
|
||||
self.__produce_unique_k8s_job_name(job.get_id_tag()))
|
||||
jobs = Job.objects(self._pykube_api).filter(
|
||||
selector="app=" + self.__produce_unique_k8s_job_name(job.get_id_tag()),
|
||||
namespace=self.runner_params['k8s_namespace'])
|
||||
if len(jobs.response['items']) >= 0:
|
||||
job_to_delete = Job(self._pykube_api, jobs.response['items'][0])
|
||||
job_to_delete.scale(replicas=0)
|
||||
|
||||
@@ -10,6 +10,7 @@ import time
|
||||
|
||||
from galaxy import model
|
||||
from galaxy.jobs.runners.drmaa import DRMAAJobRunner
|
||||
from galaxy.util import unicodify
|
||||
from galaxy.util.logging import get_logger
|
||||
|
||||
log = get_logger(__name__)
|
||||
@@ -51,7 +52,7 @@ class SlurmJobRunner(DRMAAJobRunner):
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
stderr = stderr.strip()
|
||||
stderr = unicodify(stderr).strip()
|
||||
if stderr == 'SLURM accounting storage is disabled':
|
||||
log.warning('SLURM accounting storage is not properly configured, unable to run sacct')
|
||||
return
|
||||
@@ -59,7 +60,7 @@ class SlurmJobRunner(DRMAAJobRunner):
|
||||
# First line is for 'job_id'
|
||||
# Second line is for 'job_id.batch' (only available after the batch job is complete)
|
||||
# Following lines are for the steps 'job_id.0', 'job_id.1', ... (but Galaxy does not use steps)
|
||||
first_line = stdout.splitlines()[0]
|
||||
first_line = unicodify(stdout).splitlines()[0]
|
||||
# Strip whitespaces and the final '+' (if present), only return the first word
|
||||
return first_line.strip().rstrip('+').split()[0]
|
||||
|
||||
@@ -77,6 +78,7 @@ class SlurmJobRunner(DRMAAJobRunner):
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
# Will need to be more clever here if this message is not consistent
|
||||
stderr = unicodify(stderr)
|
||||
if stderr == 'slurm_load_jobs error: Invalid job id specified\n':
|
||||
# The job may be old, try to get its state with sacct
|
||||
job_state = _get_slurm_state_with_sacct(job_id, cluster)
|
||||
@@ -84,7 +86,7 @@ class SlurmJobRunner(DRMAAJobRunner):
|
||||
return job_state
|
||||
return 'NOT_FOUND'
|
||||
raise Exception('`%s` returned %s, stderr: %s' % (' '.join(cmd), p.returncode, stderr))
|
||||
stdout = stdout.strip()
|
||||
stdout = unicodify(stdout).strip()
|
||||
# stdout is a single line in format "key1=value1 key2=value2 ..."
|
||||
job_info_keys = []
|
||||
job_info_values = []
|
||||
|
||||
@@ -1128,3 +1128,10 @@ class ModelFilterParser(HasAModelManager):
|
||||
|
||||
def raise_filter_err(self, attr, op, val, msg):
|
||||
raise exceptions.RequestParameterInvalidException(msg, column=attr, operation=op, val=val)
|
||||
|
||||
|
||||
def is_valid_slug(slug):
|
||||
"""Returns true iff slug is valid."""
|
||||
|
||||
VALID_SLUG_RE = re.compile(r"^[a-z0-9\-]+$")
|
||||
return VALID_SLUG_RE.match(slug)
|
||||
|
||||
@@ -6,14 +6,53 @@ Pages are markup created and saved by users that can contain Galaxy objects
|
||||
from within Galaxy.
|
||||
"""
|
||||
import logging
|
||||
import re
|
||||
|
||||
from galaxy import model
|
||||
from galaxy.managers import sharable
|
||||
from six.moves.html_entities import name2codepoint
|
||||
from six.moves.html_parser import HTMLParser
|
||||
|
||||
from galaxy import exceptions, model
|
||||
from galaxy.managers import base, sharable
|
||||
from galaxy.managers.hdas import HDAManager
|
||||
from galaxy.model.item_attrs import UsesAnnotations
|
||||
from galaxy.util import unicodify
|
||||
from galaxy.util.sanitize_html import sanitize_html
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Copied from https://github.com/kurtmckee/feedparser
|
||||
_cp1252 = {
|
||||
128: u'\u20ac', # euro sign
|
||||
130: u'\u201a', # single low-9 quotation mark
|
||||
131: u'\u0192', # latin small letter f with hook
|
||||
132: u'\u201e', # double low-9 quotation mark
|
||||
133: u'\u2026', # horizontal ellipsis
|
||||
134: u'\u2020', # dagger
|
||||
135: u'\u2021', # double dagger
|
||||
136: u'\u02c6', # modifier letter circumflex accent
|
||||
137: u'\u2030', # per mille sign
|
||||
138: u'\u0160', # latin capital letter s with caron
|
||||
139: u'\u2039', # single left-pointing angle quotation mark
|
||||
140: u'\u0152', # latin capital ligature oe
|
||||
142: u'\u017d', # latin capital letter z with caron
|
||||
145: u'\u2018', # left single quotation mark
|
||||
146: u'\u2019', # right single quotation mark
|
||||
147: u'\u201c', # left double quotation mark
|
||||
148: u'\u201d', # right double quotation mark
|
||||
149: u'\u2022', # bullet
|
||||
150: u'\u2013', # en dash
|
||||
151: u'\u2014', # em dash
|
||||
152: u'\u02dc', # small tilde
|
||||
153: u'\u2122', # trade mark sign
|
||||
154: u'\u0161', # latin small letter s with caron
|
||||
155: u'\u203a', # single right-pointing angle quotation mark
|
||||
156: u'\u0153', # latin small ligature oe
|
||||
158: u'\u017e', # latin small letter z with caron
|
||||
159: u'\u0178', # latin capital letter y with diaeresis
|
||||
}
|
||||
|
||||
class PageManager(sharable.SharableModelManager):
|
||||
|
||||
class PageManager(sharable.SharableModelManager, UsesAnnotations):
|
||||
"""
|
||||
"""
|
||||
|
||||
@@ -35,6 +74,88 @@ class PageManager(sharable.SharableModelManager):
|
||||
"""
|
||||
pass
|
||||
|
||||
def create(self, trans, payload):
|
||||
user = trans.get_user()
|
||||
|
||||
if not payload.get("title", None):
|
||||
raise exceptions.ObjectAttributeMissingException("Page name is required")
|
||||
elif not payload.get("slug", None):
|
||||
raise exceptions.ObjectAttributeMissingException("Page id is required")
|
||||
elif not base.is_valid_slug(payload["slug"]):
|
||||
raise exceptions.ObjectAttributeInvalidException("Page identifier must consist of only lowercase letters, numbers, and the '-' character")
|
||||
elif trans.sa_session.query(trans.app.model.Page).filter_by(user=user, slug=payload["slug"], deleted=False).first():
|
||||
raise exceptions.DuplicatedSlugException("Page identifier must be unique")
|
||||
|
||||
content = payload.get("content", "")
|
||||
content = self.rewrite_content_for_import(trans, content)
|
||||
|
||||
# Create the new stored page
|
||||
page = trans.app.model.Page()
|
||||
page.title = payload['title']
|
||||
page.slug = payload['slug']
|
||||
page_annotation = payload.get("annotation", None)
|
||||
if page_annotation is not None:
|
||||
page_annotation = sanitize_html(page_annotation)
|
||||
self.add_item_annotation(trans.sa_session, trans.get_user(), page, page_annotation)
|
||||
|
||||
page.user = user
|
||||
# And the first (empty) page revision
|
||||
page_revision = trans.app.model.PageRevision()
|
||||
page_revision.title = payload['title']
|
||||
page_revision.page = page
|
||||
page.latest_revision = page_revision
|
||||
page_revision.content = content
|
||||
# Persist
|
||||
session = trans.sa_session
|
||||
session.add(page)
|
||||
session.flush()
|
||||
return page
|
||||
|
||||
def save_new_revision(self, trans, page, payload):
|
||||
# Assumes security has already been checked by caller.
|
||||
content = payload.get("content", None)
|
||||
if not content:
|
||||
raise exceptions.ObjectAttributeMissingException("content undefined or empty")
|
||||
|
||||
if 'title' in payload:
|
||||
title = payload['title']
|
||||
else:
|
||||
title = page.title
|
||||
|
||||
content = self.rewrite_content_for_import(trans, content)
|
||||
|
||||
page_revision = trans.app.model.PageRevision()
|
||||
page_revision.title = title
|
||||
page_revision.page = page
|
||||
page.latest_revision = page_revision
|
||||
page_revision.content = content
|
||||
|
||||
# Persist
|
||||
session = trans.sa_session
|
||||
session.flush()
|
||||
return page_revision
|
||||
|
||||
def rewrite_content_for_import(self, trans, content):
|
||||
try:
|
||||
content = sanitize_html(content)
|
||||
processor = PageContentProcessor(trans, placeholderRenderForSave)
|
||||
processor.feed(content)
|
||||
# Output is string, so convert to unicode for saving.
|
||||
content = unicodify(processor.output(), 'utf-8')
|
||||
except exceptions.MessageException:
|
||||
raise
|
||||
except Exception:
|
||||
raise exceptions.RequestParameterInvalidException("problem with embedded HTML content [%s]" % content)
|
||||
return content
|
||||
|
||||
def rewrite_content_for_export(self, trans, as_dict):
|
||||
content = as_dict["content"]
|
||||
processor = PageContentProcessor(trans, placeholderRenderForEdit)
|
||||
processor.feed(content)
|
||||
content = unicodify(processor.output(), 'utf-8')
|
||||
as_dict["content"] = content
|
||||
return as_dict
|
||||
|
||||
|
||||
class PageSerializer(sharable.SharableModelSerializer):
|
||||
"""
|
||||
@@ -73,3 +194,230 @@ class PageDeserializer(sharable.SharableModelDeserializer):
|
||||
self.deserializers.update({
|
||||
})
|
||||
self.deserializable_keyset.update(self.deserializers.keys())
|
||||
|
||||
|
||||
class PageContentProcessor(HTMLParser, object):
|
||||
"""
|
||||
Processes page content to produce HTML that is suitable for display.
|
||||
For now, processor renders embedded objects.
|
||||
"""
|
||||
bare_ampersand = re.compile(r"&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)")
|
||||
elements_no_end_tag = set([
|
||||
'area', 'base', 'basefont', 'br', 'col', 'command', 'embed', 'frame',
|
||||
'hr', 'img', 'input', 'isindex', 'keygen', 'link', 'meta', 'param',
|
||||
'source', 'track', 'wbr'
|
||||
])
|
||||
|
||||
def __init__(self, trans, render_embed_html_fn):
|
||||
HTMLParser.__init__(self)
|
||||
self.trans = trans
|
||||
self.ignore_content = False
|
||||
self.num_open_tags_for_ignore = 0
|
||||
self.render_embed_html_fn = render_embed_html_fn
|
||||
|
||||
def reset(self):
|
||||
self.pieces = []
|
||||
HTMLParser.reset(self)
|
||||
|
||||
def _shorttag_replace(self, match):
|
||||
tag = match.group(1)
|
||||
if tag in self.elements_no_end_tag:
|
||||
return '<' + tag + ' />'
|
||||
else:
|
||||
return '<' + tag + '></' + tag + '>'
|
||||
|
||||
def feed(self, data):
|
||||
data = re.compile(r'<!((?!DOCTYPE|--|\[))', re.IGNORECASE).sub(r'<!\1', data)
|
||||
data = re.sub(r'<([^<>\s]+?)\s*/>', self._shorttag_replace, data)
|
||||
data = data.replace(''', "'")
|
||||
data = data.replace('"', '"')
|
||||
HTMLParser.feed(self, data)
|
||||
HTMLParser.close(self)
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
"""
|
||||
Called for each start tag
|
||||
|
||||
attrs is a list of (attr, value) tuples, e.g. for <pre class='screen'>,
|
||||
tag='pre', attrs=[('class', 'screen')]
|
||||
"""
|
||||
|
||||
# If ignoring content, just increment tag count and ignore.
|
||||
if self.ignore_content:
|
||||
self.num_open_tags_for_ignore += 1
|
||||
return
|
||||
|
||||
# Not ignoring tag; look for embedded content.
|
||||
embedded_item = False
|
||||
for attribute in attrs:
|
||||
if (attribute[0] == "class") and ("embedded-item" in attribute[1].split(" ")):
|
||||
embedded_item = True
|
||||
break
|
||||
# For embedded content, set ignore flag to ignore current content and add new content for embedded item.
|
||||
if embedded_item:
|
||||
# Set processing attributes to ignore content.
|
||||
self.ignore_content = True
|
||||
self.num_open_tags_for_ignore = 1
|
||||
|
||||
# Insert content for embedded element.
|
||||
for attribute in attrs:
|
||||
name = attribute[0]
|
||||
if name == "id":
|
||||
# ID has form '<class_name>-<encoded_item_id>'
|
||||
item_class, item_id = attribute[1].split("-")
|
||||
embed_html = self.render_embed_html_fn(self.trans, item_class, item_id)
|
||||
self.pieces.append(embed_html)
|
||||
return
|
||||
|
||||
# Default behavior: not ignoring and no embedded content.
|
||||
uattrs = []
|
||||
strattrs = ''
|
||||
if attrs:
|
||||
for key, value in attrs:
|
||||
value = value.replace('>', '>').replace('<', '<').replace('"', '"')
|
||||
value = self.bare_ampersand.sub("&", value)
|
||||
uattrs.append((key, value))
|
||||
strattrs = ''.join(' %s="%s"' % (k, v) for k, v in uattrs)
|
||||
if tag in self.elements_no_end_tag:
|
||||
self.pieces.append('<%s%s />' % (tag, strattrs))
|
||||
else:
|
||||
self.pieces.append('<%s%s>' % (tag, strattrs))
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
"""
|
||||
Called for each end tag
|
||||
|
||||
E.g. for </pre>, tag will be 'pre'
|
||||
"""
|
||||
# If ignoring content, see if current tag is the end of content to ignore.
|
||||
if self.ignore_content:
|
||||
self.num_open_tags_for_ignore -= 1
|
||||
if self.num_open_tags_for_ignore == 0:
|
||||
# Done ignoring content.
|
||||
self.ignore_content = False
|
||||
return
|
||||
|
||||
# Default behavior: reconstruct the original end tag.
|
||||
if tag not in self.elements_no_end_tag:
|
||||
self.pieces.append("</%s>" % tag)
|
||||
|
||||
def handle_charref(self, ref):
|
||||
# called for each character reference, e.g. for ' ', ref will be '160'
|
||||
# Reconstruct the original character reference.
|
||||
ref = ref.lower()
|
||||
if ref.startswith('x'):
|
||||
value = int(ref[1:], 16)
|
||||
else:
|
||||
value = int(ref)
|
||||
|
||||
if value in _cp1252:
|
||||
self.pieces.append('&#%s;' % hex(ord(_cp1252[value]))[1:])
|
||||
else:
|
||||
self.pieces.append('&#%s;' % ref)
|
||||
|
||||
def handle_entityref(self, ref):
|
||||
# called for each entity reference, e.g. for '©', ref will be 'copy'
|
||||
# Reconstruct the original entity reference.
|
||||
if ref in name2codepoint or ref == 'apos':
|
||||
self.pieces.append('&%s;' % ref)
|
||||
else:
|
||||
self.pieces.append('&%s' % ref)
|
||||
|
||||
def handle_data(self, text):
|
||||
"""
|
||||
Called for each block of plain text
|
||||
|
||||
Called outside of any tag and not containing any character or entity
|
||||
references. Store the original text verbatim.
|
||||
"""
|
||||
if self.ignore_content:
|
||||
return
|
||||
self.pieces.append(text)
|
||||
|
||||
def handle_comment(self, text):
|
||||
# called for each HTML comment, e.g. <!-- insert Javascript code here -->
|
||||
# Reconstruct the original comment.
|
||||
self.pieces.append('<!--%s-->' % text)
|
||||
|
||||
def handle_decl(self, text):
|
||||
# called for the DOCTYPE, if present, e.g.
|
||||
# <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
# "http://www.w3.org/TR/html4/loose.dtd">
|
||||
# Reconstruct original DOCTYPE
|
||||
self.pieces.append('<!%s>' % text)
|
||||
|
||||
def handle_pi(self, text):
|
||||
# called for each processing instruction, e.g. <?instruction>
|
||||
# Reconstruct original processing instruction.
|
||||
self.pieces.append('<?%s>' % text)
|
||||
|
||||
def output(self):
|
||||
'''Return processed HTML as a single string'''
|
||||
return ''.join(self.pieces)
|
||||
|
||||
|
||||
PAGE_MAXRAW = 10**15
|
||||
|
||||
|
||||
def get_page_identifiers(item_id, app):
|
||||
# Assume if item id is integer and less than 10**15, it's unencoded.
|
||||
try:
|
||||
decoded_id = int(item_id)
|
||||
if decoded_id >= PAGE_MAXRAW:
|
||||
raise ValueError("Identifier larger than maximum expected raw int, must be already encoded.")
|
||||
encoded_id = app.security.encode_id(item_id)
|
||||
except ValueError:
|
||||
# It's an encoded id.
|
||||
encoded_id = item_id
|
||||
decoded_id = base.decode_id(app, item_id)
|
||||
return (encoded_id, decoded_id)
|
||||
|
||||
|
||||
# Utilities for encoding/decoding HTML content.
|
||||
PLACEHOLDER_TEMPLATE = '''<div class="embedded-item {class_shorthand_lower} placeholder" id="{item_class}-{item_id}"><p class="title">Embedded Galaxy {class_shorthand} - '{item_name}'</p><p class="content">[Do not edit this block; Galaxy will fill it in with the annotated {class_shorthand} when it is displayed]</p></div>'''
|
||||
|
||||
# This is a mapping of the id portion of page contents to the cssclass/shortname.
|
||||
PAGE_CLASS_MAPPING = {
|
||||
'History': 'History',
|
||||
'HistoryDatasetAssociation': 'Dataset',
|
||||
'StoredWorkflow': 'Workflow',
|
||||
'Visualization': 'Visualization'
|
||||
}
|
||||
|
||||
|
||||
def placeholderRenderForEdit(trans, item_class, item_id):
|
||||
return placeholderRenderForSave(trans, item_class, item_id, encode=True)
|
||||
|
||||
|
||||
def placeholderRenderForSave(trans, item_class, item_id, encode=False):
|
||||
encoded_item_id, decoded_item_id = get_page_identifiers(item_id, trans.app)
|
||||
item_name = ''
|
||||
if item_class == 'History':
|
||||
history = trans.sa_session.query(trans.model.History).get(decoded_item_id)
|
||||
history = base.security_check(trans, history, False, True)
|
||||
item_name = history.name
|
||||
elif item_class == 'HistoryDatasetAssociation':
|
||||
hda = trans.sa_session.query(trans.model.HistoryDatasetAssociation).get(decoded_item_id)
|
||||
hda_manager = HDAManager(trans.app)
|
||||
hda = hda_manager.get_accessible(decoded_item_id, trans.user)
|
||||
item_name = hda.name
|
||||
elif item_class == 'StoredWorkflow':
|
||||
wf = trans.sa_session.query(trans.model.StoredWorkflow).get(decoded_item_id)
|
||||
wf = base.security_check(trans, wf, False, True)
|
||||
item_name = wf.name
|
||||
elif item_class == 'Visualization':
|
||||
visualization = trans.sa_session.query(trans.model.Visualization).get(decoded_item_id)
|
||||
visualization = base.security_check(trans, visualization, False, True)
|
||||
item_name = visualization.title
|
||||
class_shorthand = PAGE_CLASS_MAPPING[item_class]
|
||||
if encode:
|
||||
item_id = encoded_item_id
|
||||
else:
|
||||
item_id = decoded_item_id
|
||||
return PLACEHOLDER_TEMPLATE.format(
|
||||
item_class=item_class,
|
||||
class_shorthand=class_shorthand,
|
||||
class_shorthand_lower=class_shorthand.lower(),
|
||||
item_id=item_id,
|
||||
item_name=item_name
|
||||
)
|
||||
|
||||
@@ -289,11 +289,13 @@ class JobLike(object):
|
||||
|
||||
def set_streams(self, tool_stdout, tool_stderr, job_stdout=None, job_stderr=None, job_messages=None):
|
||||
def shrink_and_unicodify(what, stream):
|
||||
stream = galaxy.util.unicodify(stream) or u''
|
||||
if (len(stream) > galaxy.util.DATABASE_MAX_STRING_SIZE):
|
||||
stream = galaxy.util.shrink_string_by_size(tool_stdout, galaxy.util.DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True)
|
||||
log.info("%s for %s %d is greater than %s, only a portion will be logged to database", what, type(self), self.id, galaxy.util.DATABASE_MAX_STRING_SIZE_PRETTY)
|
||||
return stream
|
||||
if len(stream) > galaxy.util.DATABASE_MAX_STRING_SIZE:
|
||||
log.info("%s for %s %d is greater than %s, only a portion will be logged to database",
|
||||
what,
|
||||
type(self),
|
||||
self.id,
|
||||
galaxy.util.DATABASE_MAX_STRING_SIZE_PRETTY)
|
||||
return galaxy.util.shrink_and_unicodify(stream)
|
||||
|
||||
self.tool_stdout = shrink_and_unicodify('tool_stdout', tool_stdout)
|
||||
self.tool_stderr = shrink_and_unicodify('tool_stderr', tool_stderr)
|
||||
@@ -1678,13 +1680,10 @@ class History(HasTags, Dictifiable, UsesAnnotations, HasName, RepresentById):
|
||||
set_genome = genome_build not in [None, '?']
|
||||
for i, dataset in enumerate(datasets):
|
||||
dataset.hid = base_hid + i
|
||||
# Don't let SA manage this.
|
||||
delattr(dataset, "history")
|
||||
dataset.history = self
|
||||
dataset.history_id = cached_id(self)
|
||||
if set_genome:
|
||||
self.genome_build = genome_build
|
||||
for dataset in datasets:
|
||||
dataset.history_id = cached_id(self)
|
||||
return datasets
|
||||
|
||||
def add_dataset_collection(self, history_dataset_collection, set_hid=True):
|
||||
@@ -2444,6 +2443,14 @@ class DatasetInstance(object):
|
||||
self.parent_id = parent_id
|
||||
self.validation_errors = validation_errors
|
||||
|
||||
@property
|
||||
def peek(self):
|
||||
return self._peek
|
||||
|
||||
@peek.setter
|
||||
def peek(self, peek):
|
||||
self._peek = unicodify(peek, strip_null=True)
|
||||
|
||||
def update(self):
|
||||
self.update_time = galaxy.model.orm.now.now()
|
||||
|
||||
|
||||
@@ -74,13 +74,10 @@ class UsesItemRatings(object):
|
||||
item_rating_assoc_class = '%sRatingAssociation' % item.__class__.__name__
|
||||
return getattr(webapp_model, item_rating_assoc_class, None)
|
||||
|
||||
def _get_item_id_filter_str(self, item, item_rating_assoc_class, webapp_model=None):
|
||||
def _get_item_id_filter_str(self, item, item_rating_assoc_class):
|
||||
# Get foreign key in item-rating association table that references item table.
|
||||
if webapp_model is None:
|
||||
webapp_model = galaxy.model
|
||||
item_fk = get_foreign_key(item_rating_assoc_class, item)
|
||||
# TODO: can we provide a better filter than a raw string?
|
||||
return "%s=%i" % (item_fk.parent.name, item.id)
|
||||
return item_fk.parent == item.id
|
||||
|
||||
|
||||
class UsesAnnotations(object):
|
||||
@@ -122,7 +119,6 @@ def get_item_annotation_obj(db_session, user, item):
|
||||
# Get annotation association object.
|
||||
annotation_assoc = db_session.query(annotation_assoc_class).filter_by(user=user)
|
||||
|
||||
# TODO: use filtering like that in _get_item_id_filter_str()
|
||||
if item.__class__ == galaxy.model.History:
|
||||
annotation_assoc = annotation_assoc.filter_by(history=item)
|
||||
elif item.__class__ == galaxy.model.HistoryDatasetAssociation:
|
||||
|
||||
@@ -222,7 +222,7 @@ model.HistoryDatasetAssociation.table = Table(
|
||||
Column("name", TrimmedString(255)),
|
||||
Column("info", TrimmedString(255)),
|
||||
Column("blurb", TrimmedString(255)),
|
||||
Column("peek", TEXT),
|
||||
Column("peek", TEXT, key="_peek"),
|
||||
Column("tool_version", TEXT),
|
||||
Column("extension", TrimmedString(64)),
|
||||
Column("metadata", MetadataType(), key="_metadata"),
|
||||
@@ -506,7 +506,7 @@ model.LibraryDatasetDatasetAssociation.table = Table(
|
||||
Column("name", TrimmedString(255), index=True),
|
||||
Column("info", TrimmedString(255)),
|
||||
Column("blurb", TrimmedString(255)),
|
||||
Column("peek", TEXT),
|
||||
Column("peek", TEXT, key="_peek"),
|
||||
Column("tool_version", TEXT),
|
||||
Column("extension", TrimmedString(64)),
|
||||
Column("metadata", MetadataType(), key="_metadata"),
|
||||
@@ -2763,7 +2763,7 @@ def db_next_hid(self, n=1):
|
||||
trans = session.begin()
|
||||
try:
|
||||
if "postgres" not in session.bind.dialect.name:
|
||||
next_hid = select([table.c.hid_counter], table.c.id == model.cached_id(self), for_update=True).scalar()
|
||||
next_hid = select([table.c.hid_counter], table.c.id == model.cached_id(self)).with_for_update().scalar()
|
||||
table.update(table.c.id == self.id).execute(hid_counter=(next_hid + n))
|
||||
else:
|
||||
stmt = table.update().where(table.c.id == model.cached_id(self)).values(hid_counter=(table.c.hid_counter + n)).returning(table.c.hid_counter)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Objects with No values
|
||||
"""
|
||||
|
||||
from galaxy.datatypes.registry import Registry
|
||||
from galaxy.model.metadata import MetadataCollection
|
||||
|
||||
|
||||
@@ -27,10 +26,7 @@ class NoneDataset(RecursiveNone):
|
||||
def __init__(self, datatypes_registry=None, ext='data', dbkey='?'):
|
||||
self.ext = self.extension = ext
|
||||
self.dbkey = dbkey
|
||||
if datatypes_registry is None:
|
||||
# Default Value Required for unit tests
|
||||
datatypes_registry = Registry()
|
||||
datatypes_registry.load_datatypes()
|
||||
assert datatypes_registry is not None
|
||||
self.datatype = datatypes_registry.get_datatype_by_extension(ext)
|
||||
self._metadata = None
|
||||
self.metadata = MetadataCollection(self)
|
||||
@@ -24,8 +24,6 @@ from .resolvers import (
|
||||
ContainerDependency,
|
||||
NullDependency,
|
||||
)
|
||||
from .resolvers.conda import CondaDependencyResolver
|
||||
from .resolvers.galaxy_packages import GalaxyPackageDependencyResolver
|
||||
from .resolvers.tool_shed_packages import ToolShedPackageDependencyResolver
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -34,13 +32,26 @@ CONFIG_VAL_NOT_FOUND = object()
|
||||
|
||||
|
||||
def build_dependency_manager(config):
|
||||
if getattr(config, "use_tool_dependencies", False):
|
||||
config_dict = {
|
||||
"use_tool_dependencies": getattr(config, "use_tool_dependencies", False),
|
||||
"default_base_path": getattr(config, "tool_dependency_dir", None),
|
||||
"conf_file": getattr(config, "dependency_resolvers_config_file", None),
|
||||
"cache": getattr(config, "use_cached_dependency_manager", False),
|
||||
"app_config": config,
|
||||
"dependency_resolvers": getattr(config, "dependency_resolvers", None),
|
||||
}
|
||||
return build_dependency_manager_from_dict(config_dict)
|
||||
|
||||
|
||||
def build_dependency_manager_from_dict(config_dict):
|
||||
if config_dict.get("use_tool_dependencies", False):
|
||||
dependency_manager_kwds = {
|
||||
'default_base_path': config.tool_dependency_dir,
|
||||
'conf_file': config.dependency_resolvers_config_file,
|
||||
'app_config': config,
|
||||
'default_base_path': config_dict.get("default_base_path"),
|
||||
'conf_file': config_dict.get("conf_file"),
|
||||
'app_config': config_dict.get("app_config"),
|
||||
"dependency_resolver_dicts": config_dict.get("dependency_resolvers"),
|
||||
}
|
||||
if getattr(config, "use_cached_dependency_manager", False):
|
||||
if config_dict.get("cache"):
|
||||
dependency_manager = CachedDependencyManager(**dependency_manager_kwds)
|
||||
else:
|
||||
dependency_manager = DependencyManager(**dependency_manager_kwds)
|
||||
@@ -61,8 +72,9 @@ class DependencyManager(object):
|
||||
and should each contain a file 'env.sh' which can be sourced to make the
|
||||
dependency available in the current shell environment.
|
||||
"""
|
||||
cached = False
|
||||
|
||||
def __init__(self, default_base_path, conf_file=None, app_config={}):
|
||||
def __init__(self, default_base_path, conf_file=None, app_config={}, dependency_resolver_dicts=None):
|
||||
"""
|
||||
Create a new dependency manager looking for packages under the paths listed
|
||||
in `base_paths`. The default base path is app.config.tool_dependency_dir.
|
||||
@@ -74,7 +86,13 @@ class DependencyManager(object):
|
||||
self.__app_config = app_config
|
||||
self.default_base_path = os.path.abspath(default_base_path)
|
||||
self.resolver_classes = self.__resolvers_dict()
|
||||
self.dependency_resolvers = self.__build_dependency_resolvers(conf_file)
|
||||
|
||||
plugin_source = None
|
||||
if dependency_resolver_dicts is not None:
|
||||
plugin_source = ('dict', dependency_resolver_dicts)
|
||||
else:
|
||||
plugin_source = self.__build_dependency_resolvers_plugin_source(conf_file)
|
||||
self.dependency_resolvers = self.__parse_resolver_conf_plugins(plugin_source)
|
||||
self._enabled_container_types = []
|
||||
self._destination_for_container_type = {}
|
||||
|
||||
@@ -232,38 +250,50 @@ class DependencyManager(object):
|
||||
else:
|
||||
return NullDependency(name=name, version=version)
|
||||
|
||||
def __build_dependency_resolvers(self, conf_file):
|
||||
def __build_dependency_resolvers_plugin_source(self, conf_file):
|
||||
if not conf_file:
|
||||
return self.__default_dependency_resolvers()
|
||||
return self.__default_dependency_resolvers_source()
|
||||
if not os.path.exists(conf_file):
|
||||
log.debug("Unable to find config file '%s'", conf_file)
|
||||
return self.__default_dependency_resolvers()
|
||||
return self.__default_dependency_resolvers_source()
|
||||
plugin_source = plugin_config.plugin_source_from_path(conf_file)
|
||||
return self.__parse_resolver_conf_xml(plugin_source)
|
||||
return plugin_source
|
||||
|
||||
def __default_dependency_resolvers(self):
|
||||
return [
|
||||
ToolShedPackageDependencyResolver(self),
|
||||
GalaxyPackageDependencyResolver(self),
|
||||
CondaDependencyResolver(self),
|
||||
GalaxyPackageDependencyResolver(self, versionless=True),
|
||||
CondaDependencyResolver(self, versionless=True),
|
||||
]
|
||||
def __default_dependency_resolvers_source(self):
|
||||
return ('dict', [
|
||||
{"type": "tool_shed_packages"},
|
||||
{"type": "galaxy_packages"},
|
||||
{"type": "conda"},
|
||||
{"type": "galaxy_packages", "versionless": True},
|
||||
{"type": "conda", "versionless": True},
|
||||
])
|
||||
|
||||
def __parse_resolver_conf_xml(self, plugin_source):
|
||||
def __parse_resolver_conf_plugins(self, plugin_source):
|
||||
"""
|
||||
"""
|
||||
extra_kwds = dict(dependency_manager=self)
|
||||
return plugin_config.load_plugins(self.resolver_classes, plugin_source, extra_kwds)
|
||||
# Use either 'type' from YAML definition or 'resolver_type' from to_dict definition.
|
||||
return plugin_config.load_plugins(self.resolver_classes, plugin_source, extra_kwds, plugin_type_keys=['type', 'resolver_type'])
|
||||
|
||||
def __resolvers_dict(self):
|
||||
import galaxy.tool_util.deps.resolvers
|
||||
return plugin_config.plugins_dict(galaxy.tool_util.deps.resolvers, 'resolver_type')
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"cache": self.cached,
|
||||
"use_tool_dependencies": True,
|
||||
"default_base_path": self.default_base_path,
|
||||
"dependency_resolvers": [m.to_dict() for m in self.dependency_resolvers],
|
||||
"tool_dependency_cache_dir": getattr(self, "tool_dependency_cache_dir", None),
|
||||
}
|
||||
|
||||
|
||||
class CachedDependencyManager(DependencyManager):
|
||||
def __init__(self, default_base_path, conf_file=None, app_config={}, tool_dependency_cache_dir=None):
|
||||
super(CachedDependencyManager, self).__init__(default_base_path=default_base_path, conf_file=conf_file, app_config=app_config)
|
||||
cached = True
|
||||
|
||||
def __init__(self, default_base_path, **kwd):
|
||||
super(CachedDependencyManager, self).__init__(default_base_path=default_base_path, **kwd)
|
||||
self.tool_dependency_cache_dir = self.get_app_option("tool_dependency_cache_dir")
|
||||
|
||||
def build_cache(self, requirements, **kwds):
|
||||
@@ -338,3 +368,6 @@ class NullDependencyManager(DependencyManager):
|
||||
|
||||
def find_dep(self, name, version=None, type='package', **kwds):
|
||||
return NullDependency(version=version, name=name)
|
||||
|
||||
def to_dict(self):
|
||||
return {"use_tool_dependencies": False}
|
||||
|
||||
@@ -148,7 +148,7 @@ class ContainerFinder(object):
|
||||
key = "%s_image" % container_type
|
||||
if key in destination_info:
|
||||
return destination_info.get(key)
|
||||
elif "%s_image_default" in destination_info:
|
||||
elif "%s_image_default" % container_type in destination_info:
|
||||
return self.__build_container_id_from_parts(container_type, destination_info, mode="default")
|
||||
return None
|
||||
|
||||
@@ -159,7 +159,7 @@ class ContainerFinder(object):
|
||||
|
||||
# TODO: Right now this assumes all containers available when a
|
||||
# container type is - there should be more thought put into this.
|
||||
# Checking which are availalbe - settings policies for what can be
|
||||
# Checking which are available - settings policies for what can be
|
||||
# auto-fetched, etc....
|
||||
return CONTAINER_CLASSES[container_type](container_id, self.app_info, tool_info, destination_info, job_info, container_description)
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CondaDependencyResolver(DependencyResolver, MultipleDependencyResolver, ListableDependencyResolver, InstallableDependencyResolver, SpecificationPatternDependencyResolver, MappableDependencyResolver):
|
||||
dict_collection_visible_keys = DependencyResolver.dict_collection_visible_keys + ['conda_prefix', 'versionless', 'ensure_channels', 'auto_install']
|
||||
dict_collection_visible_keys = DependencyResolver.dict_collection_visible_keys + ['conda_prefix', 'versionless', 'ensure_channels', 'auto_install', 'auto_init']
|
||||
resolver_type = "conda"
|
||||
config_options = {
|
||||
'prefix': None,
|
||||
|
||||
@@ -41,7 +41,7 @@ UNKNOWN_FIND_BY_MESSAGE = "ModuleDependencyResolver does not know how to find mo
|
||||
|
||||
|
||||
class ModuleDependencyResolver(DependencyResolver, MappableDependencyResolver):
|
||||
dict_collection_visible_keys = DependencyResolver.dict_collection_visible_keys + ['base_path', 'modulepath']
|
||||
dict_collection_visible_keys = DependencyResolver.dict_collection_visible_keys + ['base_path', 'modulepath', 'modulecmd', 'prefetch', 'default_indicator', 'find_by']
|
||||
resolver_type = "modules"
|
||||
|
||||
def __init__(self, dependency_manager, **kwds):
|
||||
@@ -50,6 +50,7 @@ class ModuleDependencyResolver(DependencyResolver, MappableDependencyResolver):
|
||||
self._setup_mapping(dependency_manager, **kwds)
|
||||
self.versionless = _string_as_bool(kwds.get('versionless', 'false'))
|
||||
find_by = kwds.get('find_by', 'avail')
|
||||
self.find_by = find_by
|
||||
prefetch = _string_as_bool(kwds.get('prefetch', DEFAULT_MODULE_PREFETCH))
|
||||
self.modulecmd = kwds.get('modulecmd', DEFAULT_MODULECMD_PATH)
|
||||
self.modulepath = kwds.get('modulepath', self.__default_modulespath())
|
||||
|
||||
@@ -10,12 +10,12 @@ from galaxy import model
|
||||
from galaxy.jobs.actions.post import ActionBox
|
||||
from galaxy.model import LibraryDatasetDatasetAssociation, WorkflowRequestInputParameter
|
||||
from galaxy.model.dataset_collections.builder import CollectionBuilder
|
||||
from galaxy.model.none_like import NoneDataset
|
||||
from galaxy.objectstore import ObjectStorePopulator
|
||||
from galaxy.tools.parameters import update_dataset_ids
|
||||
from galaxy.tools.parameters.basic import DataCollectionToolParameter, DataToolParameter, RuntimeValue
|
||||
from galaxy.tools.parameters.wrapped import WrappedParameters
|
||||
from galaxy.util import ExecutionTimer
|
||||
from galaxy.util.none_like import NoneDataset
|
||||
from galaxy.util.odict import odict
|
||||
from galaxy.util.template import fill_template
|
||||
from galaxy.web import url_for
|
||||
@@ -518,9 +518,8 @@ class DefaultToolAction(object):
|
||||
add_datasets_timer = ExecutionTimer()
|
||||
# Add all the top-level (non-child) datasets to the history unless otherwise specified
|
||||
datasets_to_persist = []
|
||||
for name in out_data.keys():
|
||||
for name, data in out_data.items():
|
||||
if name not in child_dataset_names and name not in incoming: # don't add children; or already existing datasets, i.e. async created
|
||||
data = out_data[name]
|
||||
datasets_to_persist.append(data)
|
||||
# Set HID and add to history.
|
||||
# This is brand new and certainly empty so don't worry about quota.
|
||||
|
||||
@@ -8,6 +8,7 @@ from six import string_types
|
||||
|
||||
from galaxy import model
|
||||
from galaxy.jobs.datasets import dataset_path_rewrites
|
||||
from galaxy.model.none_like import NoneDataset
|
||||
from galaxy.tools import global_tool_errors
|
||||
from galaxy.tools.parameters import (
|
||||
visit_input_values,
|
||||
@@ -38,7 +39,6 @@ from galaxy.util import (
|
||||
unicodify,
|
||||
)
|
||||
from galaxy.util.bunch import Bunch
|
||||
from galaxy.util.none_like import NoneDataset
|
||||
from galaxy.util.object_wrapper import wrap_with_safe_string
|
||||
from galaxy.util.template import fill_template
|
||||
from galaxy.work.context import WorkRequestContext
|
||||
|
||||
@@ -21,12 +21,15 @@ def lint_citations(tool_xml, lint_ctx):
|
||||
for citation in citations[0]:
|
||||
if citation.tag != "citation":
|
||||
lint_ctx.warn("Unknown tag discovered in citations block [%s], will be ignored." % citation.tag)
|
||||
if "type" in citation.attrib:
|
||||
citation_type = citation.attrib.get("type")
|
||||
if citation_type not in ["doi", "bibtex"]:
|
||||
lint_ctx.warn("Unknown citation type discovered [%s], will be ignored.", citation_type)
|
||||
else:
|
||||
valid_citations += 1
|
||||
continue
|
||||
citation_type = citation.attrib.get("type")
|
||||
if citation_type not in ('bibtex', 'doi'):
|
||||
lint_ctx.warn("Unknown citation type discovered [%s], will be ignored.", citation_type)
|
||||
continue
|
||||
if citation.text is None or not citation.text.strip():
|
||||
lint_ctx.error('Empty %s citation.' % citation_type)
|
||||
continue
|
||||
valid_citations += 1
|
||||
|
||||
if valid_citations > 0:
|
||||
lint_ctx.valid("Found %d likely valid citations.", valid_citations)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Linter for CWL tools."""
|
||||
|
||||
lint_tool_types = ["cwl"]
|
||||
|
||||
from galaxy.tool_util.cwl.schema import schema_loader
|
||||
|
||||
|
||||
def lint_cwl_validation(tool_source, lint_ctx):
|
||||
"""Determine in CWL tool validates against spec."""
|
||||
raw_reference = schema_loader.raw_process_reference(tool_source._source_path)
|
||||
validation_exception = None
|
||||
try:
|
||||
schema_loader.process_definition(raw_reference)
|
||||
except Exception as e:
|
||||
validation_exception = e
|
||||
if validation_exception:
|
||||
lint_ctx.error("Failed to valdiate CWL artifact [%s]", validation_exception)
|
||||
else:
|
||||
lint_ctx.info("CWL appears to be valid.")
|
||||
|
||||
|
||||
def lint_new_draft(tool_source, lint_ctx):
|
||||
"""Determine in CWL tool is valid, modern draft."""
|
||||
raw_reference = schema_loader.raw_process_reference(tool_source._source_path)
|
||||
cwl_version = raw_reference.process_object.get("cwlVersion", None)
|
||||
if cwl_version is None:
|
||||
lint_ctx.error("CWL file does not contain a 'cwlVersion'")
|
||||
if cwl_version not in ["v1.0"]:
|
||||
lint_ctx.warn("CWL version [%s] is unknown, we recommend the v1.0 the stable release." % cwl_version)
|
||||
else:
|
||||
lint_ctx.info("Modern CWL version [%s]", cwl_version)
|
||||
|
||||
|
||||
def lint_docker_image(tool_source, lint_ctx):
|
||||
_, containers = tool_source.parse_requirements_and_containers()
|
||||
if len(containers) == 0:
|
||||
lint_ctx.warn("Tool does not specify a DockerPull source.")
|
||||
else:
|
||||
identifier = containers[0].identifier
|
||||
lint_ctx.info("Tool will run in Docker image [%s]." % identifier)
|
||||
|
||||
|
||||
def lint_description(tool_source, lint_ctx):
|
||||
help = tool_source.parse_help()
|
||||
if not help:
|
||||
lint_ctx.warn("Description of tool is empty or absent.")
|
||||
elif "TODO" in help:
|
||||
lint_ctx.warn("Help contains TODO text.")
|
||||
@@ -21,7 +21,7 @@ log = logging.getLogger(__name__)
|
||||
@nottest
|
||||
def parse_tests(tool, tests_source):
|
||||
"""
|
||||
Build ToolTestBuilder objects for each "<test>" elements and
|
||||
Build ToolTestDescription objects for each "<test>" elements and
|
||||
return default interactor (if any).
|
||||
"""
|
||||
raw_tests_dict = tests_source.parse_tests_to_dict()
|
||||
|
||||
@@ -42,13 +42,12 @@ log = getLogger(__name__)
|
||||
VERBOSE_ERRORS = util.asbool(os.environ.get("GALAXY_TEST_VERBOSE_ERRORS", False))
|
||||
UPLOAD_ASYNC = util.asbool(os.environ.get("GALAXY_TEST_UPLOAD_ASYNC", True))
|
||||
ERROR_MESSAGE_DATASET_SEP = "--------------------------------------"
|
||||
DEFAULT_TOOL_TEST_WAIT = os.environ.get("GALAXY_TEST_DEFAULT_WAIT", 86400)
|
||||
DEFAULT_TOOL_TEST_WAIT = int(os.environ.get("GALAXY_TEST_DEFAULT_WAIT", 86400))
|
||||
|
||||
DEFAULT_FTYPE = 'auto'
|
||||
# This following default dbkey was traditionally hg17 before Galaxy 18.05,
|
||||
# restore this behavior by setting GALAXY_TEST_DEFAULT_DBKEY to hg17.
|
||||
DEFAULT_DBKEY = os.environ.get("GALAXY_TEST_DEFAULT_DBKEY", "?")
|
||||
DEFAULT_MAX_SECS = DEFAULT_TOOL_TEST_WAIT
|
||||
|
||||
|
||||
class OutputsDict(OrderedDict):
|
||||
@@ -73,7 +72,7 @@ class OutputsDict(OrderedDict):
|
||||
return item
|
||||
|
||||
|
||||
def stage_data_in_history(galaxy_interactor, tool_id, all_test_data, history=None, force_path_paste=False):
|
||||
def stage_data_in_history(galaxy_interactor, tool_id, all_test_data, history=None, force_path_paste=False, maxseconds=DEFAULT_TOOL_TEST_WAIT):
|
||||
# Upload any needed files
|
||||
upload_waits = []
|
||||
|
||||
@@ -81,12 +80,20 @@ def stage_data_in_history(galaxy_interactor, tool_id, all_test_data, history=Non
|
||||
|
||||
if UPLOAD_ASYNC:
|
||||
for test_data in all_test_data:
|
||||
upload_waits.append(galaxy_interactor.stage_data_async(test_data, history, tool_id, force_path_paste=force_path_paste))
|
||||
upload_waits.append(galaxy_interactor.stage_data_async(test_data,
|
||||
history,
|
||||
tool_id,
|
||||
force_path_paste=force_path_paste,
|
||||
maxseconds=maxseconds))
|
||||
for upload_wait in upload_waits:
|
||||
upload_wait()
|
||||
else:
|
||||
for test_data in all_test_data:
|
||||
upload_wait = galaxy_interactor.stage_data_async(test_data, history, tool_id, force_path_paste=force_path_paste)
|
||||
upload_wait = galaxy_interactor.stage_data_async(test_data,
|
||||
history,
|
||||
tool_id,
|
||||
force_path_paste=force_path_paste,
|
||||
maxseconds=maxseconds)
|
||||
upload_wait()
|
||||
|
||||
|
||||
@@ -240,7 +247,7 @@ class GalaxyInteractorApi(object):
|
||||
def wait_for(self, func, **kwd):
|
||||
sleep_amount = 0.2
|
||||
slept = 0
|
||||
walltime_exceeded = kwd.get("maxseconds", DEFAULT_TOOL_TEST_WAIT)
|
||||
walltime_exceeded = int(kwd.get("maxseconds", DEFAULT_TOOL_TEST_WAIT))
|
||||
|
||||
while slept <= walltime_exceeded:
|
||||
result = func()
|
||||
@@ -307,7 +314,7 @@ class GalaxyInteractorApi(object):
|
||||
output_id = output_data
|
||||
return output_id
|
||||
|
||||
def stage_data_async(self, test_data, history_id, tool_id, force_path_paste=False):
|
||||
def stage_data_async(self, test_data, history_id, tool_id, force_path_paste=False, maxseconds=DEFAULT_TOOL_TEST_WAIT):
|
||||
fname = test_data['fname']
|
||||
tool_input = {
|
||||
"file_type": test_data['ftype'],
|
||||
@@ -365,7 +372,7 @@ class GalaxyInteractorApi(object):
|
||||
assert "jobs" in submit_response, "Invalid response from server [%s], expecting jobs in response." % submit_response
|
||||
jobs = submit_response["jobs"]
|
||||
assert len(jobs) > 0, "Invalid response from server [%s], expecting a job." % submit_response
|
||||
return lambda: self.wait_for_job(jobs[0]["id"], history_id, DEFAULT_TOOL_TEST_WAIT)
|
||||
return lambda: self.wait_for_job(jobs[0]["id"], history_id, maxseconds=maxseconds)
|
||||
|
||||
def run_tool(self, testdef, history_id, resource_parameters={}):
|
||||
# We need to handle the case where we've uploaded a valid compressed file since the upload
|
||||
@@ -726,21 +733,25 @@ def verify_tool(tool_id,
|
||||
quiet=False,
|
||||
test_history=None,
|
||||
force_path_paste=False,
|
||||
maxseconds=None,
|
||||
maxseconds=DEFAULT_TOOL_TEST_WAIT,
|
||||
tool_test_dicts=None):
|
||||
if resource_parameters is None:
|
||||
resource_parameters = {}
|
||||
tool_test_dicts = tool_test_dicts or galaxy_interactor.get_tool_tests(tool_id, tool_version=tool_version)
|
||||
tool_test_dict = tool_test_dicts[test_index]
|
||||
tool_test_dict.setdefault('maxseconds', maxseconds)
|
||||
testdef = ToolTestDescription(tool_test_dict)
|
||||
if maxseconds is not None:
|
||||
testdef.maxseconds = int(maxseconds)
|
||||
_handle_def_errors(testdef)
|
||||
|
||||
if test_history is None:
|
||||
test_history = galaxy_interactor.new_history()
|
||||
|
||||
stage_data_in_history(galaxy_interactor, tool_id, testdef.test_data(), history=test_history, force_path_paste=force_path_paste)
|
||||
stage_data_in_history(galaxy_interactor,
|
||||
tool_id,
|
||||
testdef.test_data(),
|
||||
history=test_history,
|
||||
force_path_paste=force_path_paste,
|
||||
maxseconds=maxseconds)
|
||||
|
||||
# Once data is ready, run the tool and check the outputs - record API
|
||||
# input, job info, tool run exception, as well as exceptions related to
|
||||
@@ -1027,7 +1038,7 @@ class ToolTestDescription(object):
|
||||
def __init__(self, processed_test_dict):
|
||||
test_index = processed_test_dict["test_index"]
|
||||
name = processed_test_dict.get('name', 'Test-%d' % (test_index + 1))
|
||||
maxseconds = processed_test_dict.get('maxseconds', DEFAULT_MAX_SECS)
|
||||
maxseconds = processed_test_dict.get('maxseconds', DEFAULT_TOOL_TEST_WAIT)
|
||||
if maxseconds is not None:
|
||||
maxseconds = int(maxseconds)
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ from six import string_types, text_type
|
||||
from six.moves import shlex_quote
|
||||
|
||||
from galaxy import exceptions
|
||||
from galaxy.model.none_like import NoneDataset
|
||||
from galaxy.util import odict
|
||||
from galaxy.util.none_like import NoneDataset
|
||||
from galaxy.util.object_wrapper import wrap_with_safe_string
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -402,6 +402,17 @@ def shrink_stream_by_size(value, size, join_by=b"..", left_larger=True, beginnin
|
||||
return unicodify(rval)
|
||||
|
||||
|
||||
def shrink_and_unicodify(stream):
|
||||
stream = unicodify(stream, strip_null=True) or u''
|
||||
if (len(stream) > DATABASE_MAX_STRING_SIZE):
|
||||
stream = shrink_string_by_size(stream,
|
||||
DATABASE_MAX_STRING_SIZE,
|
||||
join_by="\n..\n",
|
||||
left_larger=True,
|
||||
beginning_on_size_error=True)
|
||||
return stream
|
||||
|
||||
|
||||
def shrink_string_by_size(value, size, join_by="..", left_larger=True, beginning_on_size_error=False, end_on_size_error=False):
|
||||
if len(value) > size:
|
||||
len_join_by = len(join_by)
|
||||
@@ -993,7 +1004,7 @@ def roundify(amount, sfs=2):
|
||||
return amount[0:sfs] + '0' * (len(amount) - sfs)
|
||||
|
||||
|
||||
def unicodify(value, encoding=DEFAULT_ENCODING, error='replace'):
|
||||
def unicodify(value, encoding=DEFAULT_ENCODING, error='replace', strip_null=False):
|
||||
u"""
|
||||
Returns a Unicode string or None.
|
||||
|
||||
@@ -1008,7 +1019,7 @@ def unicodify(value, encoding=DEFAULT_ENCODING, error='replace'):
|
||||
>>> s = u'lâtín strìñg'; assert unicodify(s.encode('latin-1')) == u'l\ufffdt\ufffdn str\ufffd\ufffdg'
|
||||
>>> s = u'lâtín strìñg'; assert unicodify(s.encode('latin-1'), error='ignore') == u'ltn strg'
|
||||
"""
|
||||
if value is None or isinstance(value, text_type):
|
||||
if value is None:
|
||||
return value
|
||||
try:
|
||||
if isinstance(value, bytearray):
|
||||
@@ -1025,6 +1036,8 @@ def unicodify(value, encoding=DEFAULT_ENCODING, error='replace'):
|
||||
msg = "Value '%s' could not be coerced to Unicode" % value
|
||||
log.exception(msg)
|
||||
raise Exception(msg)
|
||||
if strip_null:
|
||||
return value.replace('\0', '')
|
||||
return value
|
||||
|
||||
|
||||
|
||||
@@ -47,26 +47,17 @@ class Node(dictobj.DictionaryObject):
|
||||
For example, users may want to pass "attr" or some other valid jsTree options.
|
||||
|
||||
Example:
|
||||
>>> import jstree
|
||||
>>> node = jstree.Node('a', None)
|
||||
>>> print(node)
|
||||
Node({'text': 'a', 'children': MutableDictionaryObject({})})
|
||||
>>> print(node.jsonData())
|
||||
{'text': 'a'}
|
||||
>>> node = Node('a', None)
|
||||
>>> assert node._items == {'text': 'a', 'children': dictobj.MutableDictionaryObject({})}
|
||||
>>> assert node.jsonData() == {'text': 'a'}
|
||||
|
||||
>>> import jstree
|
||||
>>> node = jstree.Node('a', 1)
|
||||
>>> print(node)
|
||||
Node({'text': 'a', 'children': MutableDictionaryObject({}), 'li_attr': DictionaryObject({'id': 1}), 'id': 1})
|
||||
>>> print(node.jsonData())
|
||||
{'text': 'a', 'id': 1, 'li_attr': {'id': 1}}
|
||||
>>> node = Node('a', 1)
|
||||
>>> assert node._items == {'text': 'a', 'children': dictobj.MutableDictionaryObject({}), 'li_attr': dictobj.DictionaryObject({'id': 1}), 'id': 1}
|
||||
>>> assert node.jsonData() == {'text': 'a', 'id': 1, 'li_attr': {'id': 1}}
|
||||
|
||||
>>> import jstree
|
||||
>>> node = jstree.Node('a', 5, icon="folder", state = {'opened': True})
|
||||
>>> print(node)
|
||||
Node({'text': 'a', 'id': 5, 'state': DictionaryObject({'opened': True}), 'children': MutableDictionaryObject({}), 'li_attr': DictionaryObject({'id': 5}), 'icon': 'folder'})
|
||||
>>> print(node.jsonData())
|
||||
{'text': 'a', 'state': {'opened': True}, 'id': 5, 'li_attr': {'id': 5}, 'icon': 'folder'}
|
||||
>>> node = Node('a', 5, icon="folder", state = {'opened': True})
|
||||
>>> assert node._items == {'text': 'a', 'id': 5, 'state': dictobj.DictionaryObject({'opened': True}), 'children': dictobj.MutableDictionaryObject({}), 'li_attr': dictobj.DictionaryObject({'id': 5}), 'icon': 'folder'}
|
||||
>>> assert node.jsonData() == {'text': 'a', 'state': {'opened': True}, 'id': 5, 'li_attr': {'id': 5}, 'icon': 'folder'}
|
||||
"""
|
||||
super(Node, self).__init__()
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ from __future__ import absolute_import
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from galaxy.web.stack import register_postfork_function
|
||||
from .sleeper import Sleeper
|
||||
from .web_compat import register_postfork_function
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -30,12 +30,14 @@ def plugins_dict(module, plugin_type_identifier):
|
||||
return plugin_dict
|
||||
|
||||
|
||||
def load_plugins(plugins_dict, plugin_source, extra_kwds={}):
|
||||
def load_plugins(plugins_dict, plugin_source, extra_kwds=None, plugin_type_keys=('type',)):
|
||||
if extra_kwds is None:
|
||||
extra_kwds = {}
|
||||
source_type, source = plugin_source
|
||||
if source_type == "xml":
|
||||
return __load_plugins_from_element(plugins_dict, source, extra_kwds)
|
||||
else:
|
||||
return __load_plugins_from_dicts(plugins_dict, source, extra_kwds)
|
||||
return __load_plugins_from_dicts(plugins_dict, source, extra_kwds, plugin_type_keys=plugin_type_keys)
|
||||
|
||||
|
||||
def __load_plugins_from_element(plugins_dict, plugins_element, extra_kwds):
|
||||
@@ -58,11 +60,16 @@ def __load_plugins_from_element(plugins_dict, plugins_element, extra_kwds):
|
||||
return plugins
|
||||
|
||||
|
||||
def __load_plugins_from_dicts(plugins_dict, configs, extra_kwds):
|
||||
def __load_plugins_from_dicts(plugins_dict, configs, extra_kwds, plugin_type_keys):
|
||||
plugins = []
|
||||
|
||||
for config in configs:
|
||||
plugin_type = config["type"]
|
||||
plugin_type = None
|
||||
for plugin_type_key in plugin_type_keys:
|
||||
if plugin_type_key in config:
|
||||
plugin_type = config[plugin_type_key]
|
||||
break
|
||||
assert plugin_type is not None, "Could not determine plugin type for [%s]" % config
|
||||
plugin_kwds = config
|
||||
plugin_kwds.update(extra_kwds)
|
||||
plugin = plugins_dict[plugin_type](**plugin_kwds)
|
||||
|
||||
@@ -21,7 +21,7 @@ except ImportError:
|
||||
can_watch = False
|
||||
|
||||
from galaxy.util.hash_util import md5_hash_file
|
||||
from galaxy.web.stack import register_postfork_function
|
||||
from .web_compat import register_postfork_function
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
"""Work around for gross circular dependency between galaxy.util and galaxy.web.stack.
|
||||
|
||||
Provide a function that will delay to forking in a uwsgi environment but run immediately
|
||||
otherwise.
|
||||
"""
|
||||
try:
|
||||
from galaxy.web.stack import register_postfork_function
|
||||
except ImportError:
|
||||
def register_postfork_function(f, *args, **kwargs):
|
||||
f(*args, **kwargs)
|
||||
@@ -1,3 +1,3 @@
|
||||
VERSION_MAJOR = "19.05"
|
||||
VERSION_MAJOR = "19.09"
|
||||
VERSION_MINOR = "dev"
|
||||
VERSION = VERSION_MAJOR + ('.' + VERSION_MINOR if VERSION_MINOR else '')
|
||||
|
||||
@@ -223,7 +223,7 @@ class VisualizationsRegistry(object):
|
||||
|
||||
def get_plugins(self):
|
||||
result = []
|
||||
for plugin in self.plugins.itervalues():
|
||||
for plugin in self.plugins.values():
|
||||
result.append(plugin.to_dict())
|
||||
return sorted(result, key=lambda k: k.get('html'))
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Contains functionality needed in every web interface
|
||||
"""
|
||||
import logging
|
||||
import re
|
||||
|
||||
from six import string_types
|
||||
from sqlalchemy import true
|
||||
@@ -54,13 +53,6 @@ log = logging.getLogger(__name__)
|
||||
SUCCESS, INFO, WARNING, ERROR = "done", "info", "warning", "error"
|
||||
|
||||
|
||||
def _is_valid_slug(slug):
|
||||
""" Returns true if slug is valid. """
|
||||
|
||||
VALID_SLUG_RE = re.compile(r"^[a-z0-9\-]+$")
|
||||
return VALID_SLUG_RE.match(slug)
|
||||
|
||||
|
||||
class BaseController(object):
|
||||
"""
|
||||
Base class for Galaxy web application controllers.
|
||||
@@ -1118,7 +1110,7 @@ class UsesVisualizationMixin(UsesLibraryMixinItems):
|
||||
title_err = slug_err = ""
|
||||
if not title:
|
||||
title_err = "visualization name is required"
|
||||
elif slug and not _is_valid_slug(slug):
|
||||
elif slug and not managers_base.is_valid_slug(slug):
|
||||
slug_err = "visualization identifier must consist of only lowercase letters, numbers, and the '-' character"
|
||||
elif slug and trans.sa_session.query(trans.model.Visualization).filter_by(user=user, slug=slug, deleted=False).first():
|
||||
slug_err = "visualization identifier must be unique"
|
||||
@@ -1361,7 +1353,7 @@ class SharableMixin(object):
|
||||
|
||||
def _is_valid_slug(self, slug):
|
||||
""" Returns true if slug is valid. """
|
||||
return _is_valid_slug(slug)
|
||||
return managers_base.is_valid_slug(slug)
|
||||
|
||||
@web.expose
|
||||
@web.require_login("modify Galaxy items")
|
||||
|
||||
@@ -99,6 +99,8 @@ class JobFilesAPIController(BaseAPIController):
|
||||
else:
|
||||
input_file = payload.get("file",
|
||||
payload.get("__file", None)).file
|
||||
target_dir = os.path.dirname(path)
|
||||
util.safe_makedirs(target_dir)
|
||||
try:
|
||||
shutil.move(input_file.name, path)
|
||||
finally:
|
||||
|
||||
@@ -3,9 +3,9 @@ API for updating Galaxy Pages
|
||||
"""
|
||||
import logging
|
||||
|
||||
from galaxy import exceptions
|
||||
from galaxy.managers.base import get_object
|
||||
from galaxy.managers.pages import PageManager
|
||||
from galaxy.model.item_attrs import UsesAnnotations
|
||||
from galaxy.util.sanitize_html import sanitize_html
|
||||
from galaxy.web import expose_api
|
||||
from galaxy.web.base.controller import (
|
||||
BaseAPIController,
|
||||
@@ -18,6 +18,10 @@ log = logging.getLogger(__name__)
|
||||
|
||||
class PageRevisionsController(BaseAPIController, SharableItemSecurityMixin, UsesAnnotations, SharableMixin):
|
||||
|
||||
def __init__(self, app):
|
||||
super(PageRevisionsController, self).__init__(app)
|
||||
self.manager = PageManager(app)
|
||||
|
||||
@expose_api
|
||||
def index(self, trans, page_id, **kwd):
|
||||
"""
|
||||
@@ -30,13 +34,13 @@ class PageRevisionsController(BaseAPIController, SharableItemSecurityMixin, Uses
|
||||
:rtype: list
|
||||
:returns: dictionaries containing different revisions of the page
|
||||
"""
|
||||
page = self._get_page(trans, page_id)
|
||||
self._verify_page_ownership(trans, page)
|
||||
|
||||
r = trans.sa_session.query(trans.app.model.PageRevision).filter_by(page_id=trans.security.decode_id(page_id))
|
||||
page = get_object(trans, page_id, 'Page', check_ownership=False, check_accessible=True)
|
||||
r = trans.sa_session.query(trans.app.model.PageRevision).filter_by(page_id=page.id)
|
||||
out = []
|
||||
for page in r:
|
||||
out.append(self.encode_all_ids(trans, page.to_dict(), True))
|
||||
as_dict = self.encode_all_ids(trans, page.to_dict(), True)
|
||||
self.manager.rewrite_content_for_export(trans, as_dict)
|
||||
out.append(as_dict)
|
||||
return out
|
||||
|
||||
@expose_api
|
||||
@@ -54,42 +58,8 @@ class PageRevisionsController(BaseAPIController, SharableItemSecurityMixin, Uses
|
||||
:rtype: dictionary
|
||||
:returns: Dictionary with 'success' or 'error' element to indicate the result of the request
|
||||
"""
|
||||
content = payload.get("content", None)
|
||||
if not content:
|
||||
raise exceptions.ObjectAttributeMissingException("content undefined or empty")
|
||||
|
||||
page = self._get_page(trans, page_id)
|
||||
self._verify_page_ownership(trans, page)
|
||||
|
||||
if 'title' in payload:
|
||||
title = payload['title']
|
||||
else:
|
||||
title = page.title
|
||||
|
||||
content = sanitize_html(content)
|
||||
|
||||
page_revision = trans.app.model.PageRevision()
|
||||
page_revision.title = title
|
||||
page_revision.page = page
|
||||
page.latest_revision = page_revision
|
||||
page_revision.content = content
|
||||
|
||||
# Persist
|
||||
session = trans.sa_session
|
||||
session.flush()
|
||||
|
||||
return page_revision.to_dict(view="element")
|
||||
|
||||
def _get_page(self, trans, page_id):
|
||||
page = None
|
||||
try:
|
||||
page = trans.sa_session.query(trans.app.model.Page).get(trans.security.decode_id(page_id))
|
||||
except Exception:
|
||||
pass
|
||||
if not page:
|
||||
raise exceptions.ObjectNotFound()
|
||||
return page
|
||||
|
||||
def _verify_page_ownership(self, trans, page):
|
||||
if not self.security_check(trans, page, True, True):
|
||||
raise exceptions.ItemOwnershipException()
|
||||
page = get_object(trans, page_id, 'Page', check_ownership=True)
|
||||
page_revision = self.manager.save_new_revision(trans, page, payload)
|
||||
rval = self.encode_all_ids(trans, page_revision.to_dict(view="element"), True)
|
||||
self.manager.rewrite_content_for_export(trans, rval)
|
||||
return rval
|
||||
|
||||
@@ -3,13 +3,12 @@ API for updating Galaxy Pages
|
||||
"""
|
||||
import logging
|
||||
|
||||
from galaxy import exceptions
|
||||
from galaxy.managers.base import get_object
|
||||
from galaxy.managers.pages import (
|
||||
PageManager,
|
||||
PageSerializer
|
||||
)
|
||||
from galaxy.model.item_attrs import UsesAnnotations
|
||||
from galaxy.util.sanitize_html import sanitize_html
|
||||
from galaxy.web import expose_api
|
||||
from galaxy.web.base.controller import (
|
||||
BaseAPIController,
|
||||
@@ -81,39 +80,10 @@ class PagesController(BaseAPIController, SharableItemSecurityMixin, UsesAnnotati
|
||||
:rtype: dict
|
||||
:returns: Dictionary return of the Page.to_dict call
|
||||
"""
|
||||
user = trans.get_user()
|
||||
|
||||
if not payload.get("title", None):
|
||||
raise exceptions.ObjectAttributeMissingException("Page name is required")
|
||||
elif not payload.get("slug", None):
|
||||
raise exceptions.ObjectAttributeMissingException("Page id is required")
|
||||
elif not self._is_valid_slug(payload["slug"]):
|
||||
raise exceptions.ObjectAttributeInvalidException("Page identifier must consist of only lowercase letters, numbers, and the '-' character")
|
||||
elif trans.sa_session.query(trans.app.model.Page).filter_by(user=user, slug=payload["slug"], deleted=False).first():
|
||||
raise exceptions.DuplicatedSlugException("Page slug must be unique")
|
||||
|
||||
content = payload.get("content", "")
|
||||
content = sanitize_html(content)
|
||||
|
||||
# Create the new stored page
|
||||
page = trans.app.model.Page()
|
||||
page.title = payload['title']
|
||||
page.slug = payload['slug']
|
||||
page_annotation = sanitize_html(payload.get("annotation", ""))
|
||||
self.add_item_annotation(trans.sa_session, trans.get_user(), page, page_annotation)
|
||||
page.user = user
|
||||
# And the first (empty) page revision
|
||||
page_revision = trans.app.model.PageRevision()
|
||||
page_revision.title = payload['title']
|
||||
page_revision.page = page
|
||||
page.latest_revision = page_revision
|
||||
page_revision.content = content
|
||||
# Persist
|
||||
session = trans.sa_session
|
||||
session.add(page)
|
||||
session.flush()
|
||||
|
||||
page = self.manager.create(trans, payload)
|
||||
rval = self.encode_all_ids(trans, page.to_dict(), True)
|
||||
rval['content'] = page.latest_revision.content
|
||||
self.manager.rewrite_content_for_export(trans, rval)
|
||||
return rval
|
||||
|
||||
@expose_api
|
||||
@@ -128,7 +98,7 @@ class PagesController(BaseAPIController, SharableItemSecurityMixin, UsesAnnotati
|
||||
:rtype: dict
|
||||
:returns: Dictionary with 'success' or 'error' element to indicate the result of the request
|
||||
"""
|
||||
page = self._get_page(trans, id)
|
||||
page = get_object(trans, id, 'Page', check_ownership=True)
|
||||
|
||||
# Mark a page as deleted
|
||||
page.deleted = True
|
||||
@@ -147,22 +117,8 @@ class PagesController(BaseAPIController, SharableItemSecurityMixin, UsesAnnotati
|
||||
:rtype: dict
|
||||
:returns: Dictionary return of the Page.to_dict call with the 'content' field populated by the most recent revision
|
||||
"""
|
||||
page = self._get_page(trans, id)
|
||||
self.security_check(trans, page, check_ownership=False, check_accessible=True)
|
||||
page = get_object(trans, id, 'Page', check_ownership=False, check_accessible=True)
|
||||
rval = self.encode_all_ids(trans, page.to_dict(), True)
|
||||
rval['content'] = page.latest_revision.content
|
||||
self.manager.rewrite_content_for_export(trans, rval)
|
||||
return rval
|
||||
|
||||
def _get_page(self, trans, id): # Fetches page object and verifies security.
|
||||
try:
|
||||
page = trans.sa_session.query(trans.app.model.Page).get(trans.security.decode_id(id))
|
||||
except Exception:
|
||||
page = None
|
||||
|
||||
if not page:
|
||||
raise exceptions.ObjectNotFound()
|
||||
|
||||
if page.user != trans.user and not trans.user_is_admin:
|
||||
raise exceptions.ItemOwnershipException()
|
||||
|
||||
return page
|
||||
|
||||
@@ -2364,6 +2364,13 @@ mapping:
|
||||
job_config:
|
||||
!include job_config_schema.yml
|
||||
|
||||
dependency_resolvers:
|
||||
type: seq
|
||||
desc: |
|
||||
Rather than specifying a dependency_resolvers_config_file, the definition of the
|
||||
resolvers to enable can be embedded into Galaxy's config with this option.
|
||||
This has no effect if a dependency_resolvers_config_file is used.
|
||||
|
||||
default_job_resubmission_condition:
|
||||
type: str
|
||||
required: false
|
||||
|
||||
@@ -142,7 +142,7 @@ def inputs_recursive(trans, input_params, param_values, depth=1, upgrade_message
|
||||
if upgrade_messages is None:
|
||||
upgrade_messages = {}
|
||||
|
||||
for input_index, input in enumerate(input_params.itervalues()):
|
||||
for input_index, input in enumerate(input_params.values()):
|
||||
if input.name in param_values:
|
||||
if input.type == "repeat":
|
||||
for i in range(len(param_values[input.name])):
|
||||
|
||||
@@ -566,7 +566,7 @@ class AdminGalaxy(controller.JSAppLauncher, AdminActions, UsesQuotaMixin, QuotaP
|
||||
)
|
||||
|
||||
for data_table_elem_name, data_table in sorted_data_tables:
|
||||
for filename, file_dict in data_table.filenames.iteritems():
|
||||
for filename, file_dict in data_table.filenames.items():
|
||||
file_missing = ['file missing'] \
|
||||
if not file_dict.get('found') else []
|
||||
data.append({
|
||||
|
||||
@@ -23,7 +23,7 @@ class DataManager(BaseUIController):
|
||||
message = kwd.get('message', '')
|
||||
status = kwd.get('status', 'info')
|
||||
data_managers = []
|
||||
for data_manager_id, data_manager in sorted(trans.app.data_managers.data_managers.iteritems(),
|
||||
for data_manager_id, data_manager in sorted(trans.app.data_managers.data_managers.items(),
|
||||
key=lambda data_manager: data_manager[1].name):
|
||||
data_managers.append({'toolUrl': web.url_for(controller='root',
|
||||
tool_id=data_manager.tool.id),
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import re
|
||||
from json import loads
|
||||
|
||||
from markupsafe import escape
|
||||
from six.moves.html_entities import name2codepoint
|
||||
from six.moves.html_parser import HTMLParser
|
||||
from sqlalchemy import (
|
||||
and_,
|
||||
desc,
|
||||
@@ -16,11 +12,18 @@ from sqlalchemy.orm import (
|
||||
)
|
||||
|
||||
from galaxy import (
|
||||
managers,
|
||||
exceptions,
|
||||
model,
|
||||
util,
|
||||
web
|
||||
)
|
||||
from galaxy.managers.hdas import HDAManager
|
||||
from galaxy.managers.histories import HistoryManager, HistorySerializer
|
||||
from galaxy.managers.pages import (
|
||||
get_page_identifiers,
|
||||
PageContentProcessor,
|
||||
PageManager,
|
||||
)
|
||||
from galaxy.model.item_attrs import UsesItemRatings
|
||||
from galaxy.util import unicodify
|
||||
from galaxy.util.sanitize_html import sanitize_html
|
||||
@@ -40,55 +43,6 @@ from galaxy.web.framework.helpers import (
|
||||
)
|
||||
|
||||
|
||||
# Copied from https://github.com/kurtmckee/feedparser
|
||||
_cp1252 = {
|
||||
128: u'\u20ac', # euro sign
|
||||
130: u'\u201a', # single low-9 quotation mark
|
||||
131: u'\u0192', # latin small letter f with hook
|
||||
132: u'\u201e', # double low-9 quotation mark
|
||||
133: u'\u2026', # horizontal ellipsis
|
||||
134: u'\u2020', # dagger
|
||||
135: u'\u2021', # double dagger
|
||||
136: u'\u02c6', # modifier letter circumflex accent
|
||||
137: u'\u2030', # per mille sign
|
||||
138: u'\u0160', # latin capital letter s with caron
|
||||
139: u'\u2039', # single left-pointing angle quotation mark
|
||||
140: u'\u0152', # latin capital ligature oe
|
||||
142: u'\u017d', # latin capital letter z with caron
|
||||
145: u'\u2018', # left single quotation mark
|
||||
146: u'\u2019', # right single quotation mark
|
||||
147: u'\u201c', # left double quotation mark
|
||||
148: u'\u201d', # right double quotation mark
|
||||
149: u'\u2022', # bullet
|
||||
150: u'\u2013', # en dash
|
||||
151: u'\u2014', # em dash
|
||||
152: u'\u02dc', # small tilde
|
||||
153: u'\u2122', # trade mark sign
|
||||
154: u'\u0161', # latin small letter s with caron
|
||||
155: u'\u203a', # single right-pointing angle quotation mark
|
||||
156: u'\u0153', # latin small ligature oe
|
||||
158: u'\u017e', # latin small letter z with caron
|
||||
159: u'\u0178', # latin capital letter y with diaeresis
|
||||
}
|
||||
|
||||
|
||||
PAGE_MAXRAW = 10**15
|
||||
|
||||
|
||||
def _get_page_identifiers(item_id, app):
|
||||
# Assume if item id is integer and less than 10**15, it's unencoded.
|
||||
try:
|
||||
decoded_id = int(item_id)
|
||||
if decoded_id >= PAGE_MAXRAW:
|
||||
raise ValueError("Identifier larger than maximum expected raw int, must be already encoded.")
|
||||
encoded_id = app.security.encode_id(item_id)
|
||||
except ValueError:
|
||||
# It's an encoded id.
|
||||
encoded_id = item_id
|
||||
decoded_id = app.security.decode_id(item_id)
|
||||
return (encoded_id, decoded_id)
|
||||
|
||||
|
||||
def format_bool(b):
|
||||
if b:
|
||||
return "yes"
|
||||
@@ -300,166 +254,6 @@ class VisualizationSelectionGrid(ItemSelectionGrid):
|
||||
|
||||
|
||||
# Adapted from the _BaseHTMLProcessor class of https://github.com/kurtmckee/feedparser
|
||||
class _PageContentProcessor(HTMLParser, object):
|
||||
"""
|
||||
Processes page content to produce HTML that is suitable for display.
|
||||
For now, processor renders embedded objects.
|
||||
"""
|
||||
bare_ampersand = re.compile(r"&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)")
|
||||
elements_no_end_tag = set([
|
||||
'area', 'base', 'basefont', 'br', 'col', 'command', 'embed', 'frame',
|
||||
'hr', 'img', 'input', 'isindex', 'keygen', 'link', 'meta', 'param',
|
||||
'source', 'track', 'wbr'
|
||||
])
|
||||
|
||||
def __init__(self, trans, render_embed_html_fn):
|
||||
HTMLParser.__init__(self)
|
||||
self.trans = trans
|
||||
self.ignore_content = False
|
||||
self.num_open_tags_for_ignore = 0
|
||||
self.render_embed_html_fn = render_embed_html_fn
|
||||
|
||||
def reset(self):
|
||||
self.pieces = []
|
||||
HTMLParser.reset(self)
|
||||
|
||||
def _shorttag_replace(self, match):
|
||||
tag = match.group(1)
|
||||
if tag in self.elements_no_end_tag:
|
||||
return '<' + tag + ' />'
|
||||
else:
|
||||
return '<' + tag + '></' + tag + '>'
|
||||
|
||||
def feed(self, data):
|
||||
data = re.compile(r'<!((?!DOCTYPE|--|\[))', re.IGNORECASE).sub(r'<!\1', data)
|
||||
data = re.sub(r'<([^<>\s]+?)\s*/>', self._shorttag_replace, data)
|
||||
data = data.replace(''', "'")
|
||||
data = data.replace('"', '"')
|
||||
HTMLParser.feed(self, data)
|
||||
HTMLParser.close(self)
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
"""
|
||||
Called for each start tag
|
||||
|
||||
attrs is a list of (attr, value) tuples, e.g. for <pre class='screen'>,
|
||||
tag='pre', attrs=[('class', 'screen')]
|
||||
"""
|
||||
|
||||
# If ignoring content, just increment tag count and ignore.
|
||||
if self.ignore_content:
|
||||
self.num_open_tags_for_ignore += 1
|
||||
return
|
||||
|
||||
# Not ignoring tag; look for embedded content.
|
||||
embedded_item = False
|
||||
for attribute in attrs:
|
||||
if (attribute[0] == "class") and ("embedded-item" in attribute[1].split(" ")):
|
||||
embedded_item = True
|
||||
break
|
||||
# For embedded content, set ignore flag to ignore current content and add new content for embedded item.
|
||||
if embedded_item:
|
||||
# Set processing attributes to ignore content.
|
||||
self.ignore_content = True
|
||||
self.num_open_tags_for_ignore = 1
|
||||
|
||||
# Insert content for embedded element.
|
||||
for attribute in attrs:
|
||||
name = attribute[0]
|
||||
if name == "id":
|
||||
# ID has form '<class_name>-<encoded_item_id>'
|
||||
item_class, item_id = attribute[1].split("-")
|
||||
embed_html = self.render_embed_html_fn(self.trans, item_class, item_id)
|
||||
self.pieces.append(embed_html)
|
||||
return
|
||||
|
||||
# Default behavior: not ignoring and no embedded content.
|
||||
uattrs = []
|
||||
strattrs = ''
|
||||
if attrs:
|
||||
for key, value in attrs:
|
||||
value = value.replace('>', '>').replace('<', '<').replace('"', '"')
|
||||
value = self.bare_ampersand.sub("&", value)
|
||||
uattrs.append((key, value))
|
||||
strattrs = ''.join(' %s="%s"' % (k, v) for k, v in uattrs)
|
||||
if tag in self.elements_no_end_tag:
|
||||
self.pieces.append('<%s%s />' % (tag, strattrs))
|
||||
else:
|
||||
self.pieces.append('<%s%s>' % (tag, strattrs))
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
"""
|
||||
Called for each end tag
|
||||
|
||||
E.g. for </pre>, tag will be 'pre'
|
||||
"""
|
||||
# If ignoring content, see if current tag is the end of content to ignore.
|
||||
if self.ignore_content:
|
||||
self.num_open_tags_for_ignore -= 1
|
||||
if self.num_open_tags_for_ignore == 0:
|
||||
# Done ignoring content.
|
||||
self.ignore_content = False
|
||||
return
|
||||
|
||||
# Default behavior: reconstruct the original end tag.
|
||||
if tag not in self.elements_no_end_tag:
|
||||
self.pieces.append("</%s>" % tag)
|
||||
|
||||
def handle_charref(self, ref):
|
||||
# called for each character reference, e.g. for ' ', ref will be '160'
|
||||
# Reconstruct the original character reference.
|
||||
ref = ref.lower()
|
||||
if ref.startswith('x'):
|
||||
value = int(ref[1:], 16)
|
||||
else:
|
||||
value = int(ref)
|
||||
|
||||
if value in _cp1252:
|
||||
self.pieces.append('&#%s;' % hex(ord(_cp1252[value]))[1:])
|
||||
else:
|
||||
self.pieces.append('&#%s;' % ref)
|
||||
|
||||
def handle_entityref(self, ref):
|
||||
# called for each entity reference, e.g. for '©', ref will be 'copy'
|
||||
# Reconstruct the original entity reference.
|
||||
if ref in name2codepoint or ref == 'apos':
|
||||
self.pieces.append('&%s;' % ref)
|
||||
else:
|
||||
self.pieces.append('&%s' % ref)
|
||||
|
||||
def handle_data(self, text):
|
||||
"""
|
||||
Called for each block of plain text
|
||||
|
||||
Called outside of any tag and not containing any character or entity
|
||||
references. Store the original text verbatim.
|
||||
"""
|
||||
if self.ignore_content:
|
||||
return
|
||||
self.pieces.append(text)
|
||||
|
||||
def handle_comment(self, text):
|
||||
# called for each HTML comment, e.g. <!-- insert Javascript code here -->
|
||||
# Reconstruct the original comment.
|
||||
self.pieces.append('<!--%s-->' % text)
|
||||
|
||||
def handle_decl(self, text):
|
||||
# called for the DOCTYPE, if present, e.g.
|
||||
# <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
# "http://www.w3.org/TR/html4/loose.dtd">
|
||||
# Reconstruct original DOCTYPE
|
||||
self.pieces.append('<!%s>' % text)
|
||||
|
||||
def handle_pi(self, text):
|
||||
# called for each processing instruction, e.g. <?instruction>
|
||||
# Reconstruct original processing instruction.
|
||||
self.pieces.append('<?%s>' % text)
|
||||
|
||||
def output(self):
|
||||
'''Return processed HTML as a single string'''
|
||||
return ''.join(self.pieces)
|
||||
|
||||
|
||||
class PageController(BaseUIController, SharableMixin,
|
||||
UsesStoredWorkflowMixin, UsesVisualizationMixin, UsesItemRatings):
|
||||
|
||||
@@ -473,9 +267,10 @@ class PageController(BaseUIController, SharableMixin,
|
||||
|
||||
def __init__(self, app):
|
||||
super(PageController, self).__init__(app)
|
||||
self.history_manager = managers.histories.HistoryManager(app)
|
||||
self.history_serializer = managers.histories.HistorySerializer(self.app)
|
||||
self.hda_manager = managers.hdas.HDAManager(app)
|
||||
self.page_manager = PageManager(app)
|
||||
self.history_manager = HistoryManager(app)
|
||||
self.history_serializer = HistorySerializer(self.app)
|
||||
self.hda_manager = HDAManager(app)
|
||||
|
||||
@web.expose
|
||||
@web.json
|
||||
@@ -541,37 +336,11 @@ class PageController(BaseUIController, SharableMixin,
|
||||
}]
|
||||
}
|
||||
else:
|
||||
user = trans.get_user()
|
||||
p_title = payload.get('title')
|
||||
p_slug = payload.get('slug')
|
||||
p_annotation = payload.get('annotation')
|
||||
if not p_title:
|
||||
return self.message_exception(trans, 'Please provide a page name is required.')
|
||||
elif not p_slug:
|
||||
return self.message_exception(trans, 'Please provide a unique identifier.')
|
||||
elif not self._is_valid_slug(p_slug):
|
||||
return self.message_exception(trans, 'Page identifier can only contain lowercase letters, numbers, and dashes (-).')
|
||||
elif trans.sa_session.query(model.Page).filter_by(user=user, slug=p_slug, deleted=False).first():
|
||||
return self.message_exception(trans, 'Page id must be unique.')
|
||||
else:
|
||||
# Create the new stored page
|
||||
p = model.Page()
|
||||
p.title = p_title
|
||||
p.slug = p_slug
|
||||
p.user = user
|
||||
if p_annotation:
|
||||
p_annotation = sanitize_html(p_annotation)
|
||||
self.add_item_annotation(trans.sa_session, user, p, p_annotation)
|
||||
# And the first (empty) page revision
|
||||
p_revision = model.PageRevision()
|
||||
p_revision.title = p_title
|
||||
p_revision.page = p
|
||||
p.latest_revision = p_revision
|
||||
p_revision.content = ""
|
||||
# Persist
|
||||
trans.sa_session.add(p)
|
||||
trans.sa_session.flush()
|
||||
return {'message': 'Page \'%s\' successfully created.' % p.title, 'status': 'success'}
|
||||
try:
|
||||
page = self.page_manager.create(trans, payload)
|
||||
except exceptions.MessageException as e:
|
||||
return self.message_exception(trans, str(e))
|
||||
return {'message': 'Page \'%s\' successfully created.' % page.title, 'status': 'success'}
|
||||
|
||||
@web.legacy_expose_api
|
||||
@web.require_login("edit pages")
|
||||
@@ -637,11 +406,7 @@ class PageController(BaseUIController, SharableMixin,
|
||||
id = self.decode_id(id)
|
||||
page = trans.sa_session.query(model.Page).get(id)
|
||||
assert page.user == trans.user
|
||||
content = page.latest_revision.content
|
||||
processor = _PageContentProcessor(trans, _placeholderRenderForEdit)
|
||||
processor.feed(content)
|
||||
content = unicodify(processor.output(), 'utf-8')
|
||||
return trans.fill_template("page/editor.mako", page=page, content=content)
|
||||
return trans.fill_template("page/editor.mako", page=page)
|
||||
|
||||
@web.expose
|
||||
@web.require_login("use Galaxy pages")
|
||||
@@ -685,57 +450,11 @@ class PageController(BaseUIController, SharableMixin,
|
||||
|
||||
@web.expose
|
||||
@web.require_login()
|
||||
def save(self, trans, id, content, annotations):
|
||||
def save(self, trans, id, content):
|
||||
id = self.decode_id(id)
|
||||
page = trans.sa_session.query(model.Page).get(id)
|
||||
assert page.user == trans.user
|
||||
|
||||
# Sanitize content
|
||||
content = sanitize_html(content)
|
||||
processor = _PageContentProcessor(trans, _placeholderRenderForSave)
|
||||
processor.feed(content)
|
||||
# Output is string, so convert to unicode for saving.
|
||||
content = unicodify(processor.output(), 'utf-8')
|
||||
|
||||
# Add a new revision to the page with the provided content.
|
||||
page_revision = model.PageRevision()
|
||||
page_revision.title = page.title
|
||||
page_revision.page = page
|
||||
page.latest_revision = page_revision
|
||||
page_revision.content = content
|
||||
|
||||
# Save annotations.
|
||||
annotations = loads(annotations)
|
||||
for annotation_dict in annotations:
|
||||
item_id = self.decode_id(annotation_dict['item_id'])
|
||||
item_class = self.get_class(annotation_dict['item_class'])
|
||||
item = trans.sa_session.query(item_class).filter_by(id=item_id).first()
|
||||
if not item:
|
||||
raise RuntimeError("cannot find annotated item")
|
||||
text = sanitize_html(annotation_dict['text'])
|
||||
|
||||
# Add/update annotation.
|
||||
if item_id and item_class and text:
|
||||
# Get annotation association.
|
||||
annotation_assoc_class = eval("model.%sAnnotationAssociation" % item_class.__name__)
|
||||
annotation_assoc = trans.sa_session.query(annotation_assoc_class).filter_by(user=trans.get_user())
|
||||
if item_class == model.History.__class__:
|
||||
annotation_assoc = annotation_assoc.filter_by(history=item)
|
||||
elif item_class == model.HistoryDatasetAssociation.__class__:
|
||||
annotation_assoc = annotation_assoc.filter_by(hda=item)
|
||||
elif item_class == model.StoredWorkflow.__class__:
|
||||
annotation_assoc = annotation_assoc.filter_by(stored_workflow=item)
|
||||
elif item_class == model.WorkflowStep.__class__:
|
||||
annotation_assoc = annotation_assoc.filter_by(workflow_step=item)
|
||||
annotation_assoc = annotation_assoc.first()
|
||||
if not annotation_assoc:
|
||||
# Create association.
|
||||
annotation_assoc = annotation_assoc_class()
|
||||
item.annotations.append(annotation_assoc)
|
||||
annotation_assoc.user = trans.get_user()
|
||||
# Set annotation user text.
|
||||
annotation_assoc.annotation = text
|
||||
trans.sa_session.flush()
|
||||
self.page_manager.save_new_revision(trans, page, {"content": content})
|
||||
|
||||
@web.expose
|
||||
@web.require_login()
|
||||
@@ -760,7 +479,7 @@ class PageController(BaseUIController, SharableMixin,
|
||||
self.security_check(trans, page, False, True)
|
||||
|
||||
# Process page content.
|
||||
processor = _PageContentProcessor(trans, self._get_embed_html)
|
||||
processor = PageContentProcessor(trans, self._get_embed_html)
|
||||
processor.feed(page.latest_revision.content)
|
||||
# Output is string, so convert to unicode for display.
|
||||
page_content = unicodify(processor.output(), 'utf-8')
|
||||
@@ -942,7 +661,7 @@ class PageController(BaseUIController, SharableMixin,
|
||||
def _get_embed_html(self, trans, item_class, item_id):
|
||||
""" Returns HTML for embedding an item in a page. """
|
||||
item_class = self.get_class(item_class)
|
||||
encoded_id, decoded_id = _get_page_identifiers(item_id, trans.app)
|
||||
encoded_id, decoded_id = get_page_identifiers(item_id, trans.app)
|
||||
if item_class == model.History:
|
||||
return self._get_embedded_history_html(trans, decoded_id)
|
||||
|
||||
@@ -967,52 +686,3 @@ class PageController(BaseUIController, SharableMixin,
|
||||
|
||||
elif item_class == model.Page:
|
||||
pass
|
||||
|
||||
|
||||
PLACEHOLDER_TEMPLATE = '''<div class="embedded-item {class_shorthand_lower} placeholder" id="{item_class}-{item_id}"><p class="title">Embedded Galaxy {class_shorthand} - '{item_name}'</p><p class="content">[Do not edit this block; Galaxy will fill it in with the annotated {class_shorthand} when it is displayed]</p></div>'''
|
||||
|
||||
# This is a mapping of the id portion of page contents to the cssclass/shortname.
|
||||
PAGE_CLASS_MAPPING = {
|
||||
'History': 'History',
|
||||
'HistoryDatasetAssociation': 'Dataset',
|
||||
'StoredWorkflow': 'Workflow',
|
||||
'Visualization': 'Visualization'
|
||||
}
|
||||
|
||||
|
||||
def _placeholderRenderForEdit(trans, item_class, item_id):
|
||||
return _placeholderRenderForSave(trans, item_class, item_id, encode=True)
|
||||
|
||||
|
||||
def _placeholderRenderForSave(trans, item_class, item_id, encode=False):
|
||||
encoded_item_id, decoded_item_id = _get_page_identifiers(item_id, trans.app)
|
||||
item_name = ''
|
||||
if item_class == 'History':
|
||||
history = trans.sa_session.query(trans.model.History).get(decoded_item_id)
|
||||
history = managers.base.security_check(trans, history, False, True)
|
||||
item_name = history.name
|
||||
elif item_class == 'HistoryDatasetAssociation':
|
||||
hda = trans.sa_session.query(trans.model.HistoryDatasetAssociation).get(decoded_item_id)
|
||||
hda_manager = managers.hdas.HDAManager(trans.app)
|
||||
hda = hda_manager.get_accessible(decoded_item_id, trans.user)
|
||||
item_name = hda.name
|
||||
elif item_class == 'StoredWorkflow':
|
||||
wf = trans.sa_session.query(trans.model.StoredWorkflow).get(decoded_item_id)
|
||||
wf = managers.base.security_check(trans, wf, False, True)
|
||||
item_name = wf.name
|
||||
elif item_class == 'Visualization':
|
||||
visualization = trans.sa_session.query(trans.model.Visualization).get(decoded_item_id)
|
||||
visualization = managers.base.security_check(trans, visualization, False, True)
|
||||
item_name = visualization.title
|
||||
class_shorthand = PAGE_CLASS_MAPPING[item_class]
|
||||
if encode:
|
||||
item_id = encoded_item_id
|
||||
else:
|
||||
item_id = decoded_item_id
|
||||
return PLACEHOLDER_TEMPLATE.format(
|
||||
item_class=item_class,
|
||||
class_shorthand=class_shorthand,
|
||||
class_shorthand_lower=class_shorthand.lower(),
|
||||
item_id=item_id,
|
||||
item_name=item_name
|
||||
)
|
||||
|
||||
@@ -364,10 +364,9 @@ class MatchedRepositoryGrid(grids.Grid):
|
||||
if match_tuples:
|
||||
for match_tuple in match_tuples:
|
||||
repository_id, changeset_revision = match_tuple
|
||||
clause_list.append("%s=%d and %s='%s'" % (model.RepositoryMetadata.table.c.repository_id,
|
||||
int(repository_id),
|
||||
model.RepositoryMetadata.table.c.changeset_revision,
|
||||
changeset_revision))
|
||||
clause_list.append(and_(
|
||||
model.RepositoryMetadata.repository_id == int(repository_id),
|
||||
model.RepositoryMetadata.changeset_revision == changeset_revision))
|
||||
return trans.sa_session.query(model.RepositoryMetadata) \
|
||||
.join(model.Repository) \
|
||||
.filter(and_(model.Repository.table.c.deleted == false(),
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
.. :changelog:
|
||||
|
||||
History
|
||||
-------
|
||||
|
||||
.. to_doc
|
||||
|
||||
---------------------
|
||||
19.9.0.dev0
|
||||
---------------------
|
||||
|
||||
* Initial import from dev branch of Galaxy during 19.09 development cycle.
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../LICENSE.txt
|
||||
@@ -0,0 +1 @@
|
||||
include *.rst LICENSE
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../package.Makefile
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
.. image:: https://badge.fury.io/py/galaxy-containers.svg
|
||||
:target: https://pypi.python.org/pypi/galaxy-containers/
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The Galaxy_ containers module - interfaces for modeling and interacting with container backends (docker and docker swarm currently).
|
||||
|
||||
* Free software: Academic Free License version 3.0
|
||||
* Code: https://github.com/galaxyproject/galaxy
|
||||
|
||||
.. _Galaxy: http://galaxyproject.org/
|
||||
@@ -0,0 +1 @@
|
||||
../package-dev-requirements.txt
|
||||
@@ -0,0 +1 @@
|
||||
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../lib/galaxy/containers
|
||||
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__version__ = '19.9.0.dev0'
|
||||
|
||||
PROJECT_NAME = "galaxy-containers"
|
||||
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
|
||||
PROJECT_URL = "https://github.com/galaxyproject/galaxy"
|
||||
PROJECT_AUTHOR = 'Galaxy Project and Community'
|
||||
PROJECT_DESCRIPTION = 'Galaxy Container Modeling and Interaction Abstractions'
|
||||
PROJECT_EMAIL = 'jmchilton@gmail.com'
|
||||
RAW_CONTENT_URL = "https://raw.github.com/%s/%s/master/" % (
|
||||
PROJECT_USERAME, PROJECT_NAME
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
galaxy-util
|
||||
requests
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../build_scripts
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../setup.cfg
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import ast
|
||||
import os
|
||||
import re
|
||||
try:
|
||||
from setuptools import setup
|
||||
except ImportError:
|
||||
from distutils.core import setup
|
||||
|
||||
SOURCE_DIR = "galaxy"
|
||||
|
||||
_version_re = re.compile(r'__version__\s+=\s+(.*)')
|
||||
|
||||
with open('%s/project_galaxy_containers.py' % SOURCE_DIR, 'rb') as f:
|
||||
init_contents = f.read().decode('utf-8')
|
||||
|
||||
def get_var(var_name):
|
||||
pattern = re.compile(r'%s\s+=\s+(.*)' % var_name)
|
||||
match = pattern.search(init_contents).group(1)
|
||||
return str(ast.literal_eval(match))
|
||||
|
||||
version = get_var("__version__")
|
||||
PROJECT_NAME = get_var("PROJECT_NAME")
|
||||
PROJECT_URL = get_var("PROJECT_URL")
|
||||
PROJECT_AUTHOR = get_var("PROJECT_AUTHOR")
|
||||
PROJECT_EMAIL = get_var("PROJECT_EMAIL")
|
||||
PROJECT_DESCRIPTION = get_var("PROJECT_DESCRIPTION")
|
||||
|
||||
TEST_DIR = 'tests'
|
||||
PACKAGES = [
|
||||
'galaxy',
|
||||
'galaxy.containers',
|
||||
]
|
||||
ENTRY_POINTS = '''
|
||||
[console_scripts]
|
||||
'''
|
||||
PACKAGE_DATA = {
|
||||
# Be sure to update MANIFEST.in for source dist.
|
||||
'galaxy': [
|
||||
],
|
||||
}
|
||||
PACKAGE_DIR = {
|
||||
SOURCE_DIR: SOURCE_DIR,
|
||||
}
|
||||
|
||||
readme = open('README.rst').read()
|
||||
history = open('HISTORY.rst').read().replace('.. :changelog:', '')
|
||||
|
||||
if os.path.exists("requirements.txt"):
|
||||
requirements = open("requirements.txt").read().split("\n")
|
||||
else:
|
||||
# In tox, it will cover them anyway.
|
||||
requirements = []
|
||||
|
||||
|
||||
test_requirements = [
|
||||
# TODO: put package test requirements here
|
||||
]
|
||||
|
||||
|
||||
setup(
|
||||
name=PROJECT_NAME,
|
||||
version=version,
|
||||
description=PROJECT_DESCRIPTION,
|
||||
long_description=readme + '\n\n' + history,
|
||||
long_description_content_type='text/x-rst',
|
||||
author=PROJECT_AUTHOR,
|
||||
author_email=PROJECT_EMAIL,
|
||||
url=PROJECT_URL,
|
||||
packages=PACKAGES,
|
||||
entry_points=ENTRY_POINTS,
|
||||
package_data=PACKAGE_DATA,
|
||||
package_dir=PACKAGE_DIR,
|
||||
include_package_data=True,
|
||||
install_requires=requirements,
|
||||
extras_require={
|
||||
'docker': ['docker'],
|
||||
},
|
||||
license="AFL",
|
||||
zip_safe=False,
|
||||
keywords='galaxy',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'Environment :: Console',
|
||||
'License :: OSI Approved :: Academic Free License (AFL)',
|
||||
'Operating System :: POSIX',
|
||||
'Topic :: Software Development',
|
||||
'Topic :: Software Development :: Code Generators',
|
||||
'Topic :: Software Development :: Testing',
|
||||
'Natural Language :: English',
|
||||
"Programming Language :: Python :: 2",
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
],
|
||||
test_suite=TEST_DIR,
|
||||
tests_require=test_requirements
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
galaxy-objectstore
|
||||
galaxy-util
|
||||
bdbag
|
||||
bx
|
||||
bx-python
|
||||
h5py
|
||||
isa-rwval
|
||||
numpy
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../../test/unit/unittest_utils
|
||||
@@ -0,0 +1 @@
|
||||
../../test/unit/unittest_utils/__init__.py
|
||||
@@ -0,0 +1 @@
|
||||
../../test/unit/unittest_utils/tempfilecache.py
|
||||
@@ -0,0 +1 @@
|
||||
../../test/unit/unittest_utils/utility.py
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__version__ = '19.9.0.dev1'
|
||||
__version__ = '19.9.0.dev2'
|
||||
|
||||
PROJECT_NAME = "galaxy-job-metrics"
|
||||
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__version__ = '19.9.0.dev1'
|
||||
__version__ = '19.9.0.dev2'
|
||||
|
||||
PROJECT_NAME = "galaxy-objectstore"
|
||||
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
|
||||
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Change to packages directory.
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Use a throw-away virtualenv
|
||||
TEST_PYTHON=${TEST_PYTHON:-"python"}
|
||||
TEST_ENV_DIR=${TEST_ENV_DIR:-$(mktemp -d -t gxpkgtestenvXXXXXX)}
|
||||
|
||||
virtualenv -p "$TEST_PYTHON" "$TEST_ENV_DIR"
|
||||
. "${TEST_ENV_DIR}/bin/activate"
|
||||
pip install pytest
|
||||
|
||||
# ensure ordered by dependency dag
|
||||
PACKAGE_DIRS=(
|
||||
util
|
||||
objectstore
|
||||
job_metrics
|
||||
containers
|
||||
tool_util
|
||||
data
|
||||
)
|
||||
# containers has no tests, tool_util not yet working 100%,
|
||||
# data has many problems quota, tool shed install database, etc..
|
||||
RUN_TESTS=(1 1 1 0 0 0 0)
|
||||
|
||||
for ((i=0; i<${#PACKAGE_DIRS[@]}; i++)); do
|
||||
package_dir=${PACKAGE_DIRS[$i]}
|
||||
run_tests=${RUN_TESTS[$i]}
|
||||
|
||||
cd "$package_dir"
|
||||
pip install -e .
|
||||
if [ "$package_dir" = "util" ]; then
|
||||
pip install -e '.[template,jstree]'
|
||||
fi
|
||||
|
||||
if [[ "$run_tests" == "1" ]]; then
|
||||
pytest --doctest-modules galaxy tests
|
||||
fi
|
||||
cd ..
|
||||
done
|
||||
@@ -1,2 +1,4 @@
|
||||
include *.rst LICENSE
|
||||
|
||||
include galaxy/tool_util/deps/mulled/invfile.lua
|
||||
include galaxy/tool_util/deps/resolvers/default_conda_mapping.yml
|
||||
include galaxy/tool_util/xsd/*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__version__ = '19.9.0.dev1'
|
||||
__version__ = '19.9.0.dev2'
|
||||
|
||||
PROJECT_NAME = "galaxy-tool-util"
|
||||
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
|
||||
|
||||
@@ -53,6 +53,7 @@ PACKAGE_DATA = {
|
||||
'galaxy': [
|
||||
'tool_util/deps/mulled/invfile.lua',
|
||||
'tool_util/deps/resolvers/default_conda_mapping.yml',
|
||||
'tool_util/xsd/galaxy.xsd',
|
||||
],
|
||||
}
|
||||
PACKAGE_DIR = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__version__ = '19.9.0.dev2'
|
||||
__version__ = '19.9.0.dev3'
|
||||
|
||||
PROJECT_NAME = "galaxy-util"
|
||||
PROJECT_OWNER = PROJECT_USERAME = "galaxyproject"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
bleach
|
||||
boltons
|
||||
bz2file; python_version < '3.3'
|
||||
Cheetah3
|
||||
docutils
|
||||
markupsafe
|
||||
packaging
|
||||
|
||||
@@ -80,6 +80,10 @@ setup(
|
||||
package_dir=PACKAGE_DIR,
|
||||
include_package_data=True,
|
||||
install_requires=requirements,
|
||||
extras_require={
|
||||
'template': ['future', 'Cheetah3'],
|
||||
'jstree': ['dictobj'],
|
||||
},
|
||||
license="AFL",
|
||||
zip_safe=False,
|
||||
keywords='galaxy',
|
||||
|
||||
+10
-20
@@ -1,7 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
pwd_dir=$(pwd)
|
||||
cd `dirname $0`
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
rm -f run_functional_tests.log
|
||||
|
||||
@@ -318,8 +317,8 @@ then
|
||||
-e "GALAXY_TEST_DATABASE_TYPE=$db_type" \
|
||||
-e "LC_ALL=C" \
|
||||
--rm \
|
||||
-v `pwd`:/galaxy \
|
||||
-v `pwd`/test/docker/base/run_test_wrapper.sh:/usr/local/bin/run_test_wrapper.sh $DOCKER_IMAGE "$@"
|
||||
-v "$(pwd)":/galaxy \
|
||||
-v "$(pwd)"/test/docker/base/run_test_wrapper.sh:/usr/local/bin/run_test_wrapper.sh "$DOCKER_IMAGE" "$@"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
@@ -374,7 +373,6 @@ do
|
||||
api_script="./test/api"
|
||||
shift 1
|
||||
fi
|
||||
coverage_file="api_coverage.xml"
|
||||
;;
|
||||
-selenium|--selenium)
|
||||
GALAXY_TEST_TOOL_CONF="config/tool_conf.xml.sample,test/functional/tools/samples_tool_conf.xml"
|
||||
@@ -412,15 +410,15 @@ do
|
||||
shift
|
||||
;;
|
||||
--external_url)
|
||||
GALAXY_TEST_EXTERNAL=$2
|
||||
export GALAXY_TEST_EXTERNAL=$2
|
||||
shift 2
|
||||
;;
|
||||
--external_master_key)
|
||||
GALAXY_CONFIG_MASTER_KEY=$2
|
||||
export GALAXY_CONFIG_MASTER_API_KEY=$2
|
||||
shift 2
|
||||
;;
|
||||
--external_user_key)
|
||||
GALAXY_TEST_USER_API_KEY=$2
|
||||
export GALAXY_TEST_USER_API_KEY=$2
|
||||
shift 2
|
||||
;;
|
||||
-f|-framework|--framework)
|
||||
@@ -428,7 +426,6 @@ do
|
||||
marker="-m tool"
|
||||
test_script="pytest"
|
||||
report_file="run_framework_tests.html"
|
||||
coverage_file="framework_coverage.xml"
|
||||
framework_test=1;
|
||||
shift 1
|
||||
;;
|
||||
@@ -437,7 +434,6 @@ do
|
||||
marker="-m tool"
|
||||
test_script="pytest"
|
||||
report_file="run_framework_tests.html"
|
||||
coverage_file="main_tools_coverage.xml"
|
||||
framework_test=1;
|
||||
shift 1
|
||||
;;
|
||||
@@ -445,7 +441,6 @@ do
|
||||
marker="-m data_manager"
|
||||
test_script="pytest"
|
||||
report_file="run_data_managers_tests.html"
|
||||
coverage_file="data_managers_coverage.xml"
|
||||
data_managers_test=1;
|
||||
shift 1
|
||||
;;
|
||||
@@ -454,7 +449,6 @@ do
|
||||
marker="-m tool"
|
||||
test_script="pytest"
|
||||
report_file="run_migrated_tests.html"
|
||||
coverage_file="migrated_coverage.xml"
|
||||
migrated_test=1;
|
||||
shift
|
||||
;;
|
||||
@@ -463,7 +457,6 @@ do
|
||||
marker="-m tool"
|
||||
test_script="pytest"
|
||||
report_file="run_installed_tests.html"
|
||||
coverage_file="installed_coverage.xml"
|
||||
installed_test=1;
|
||||
shift
|
||||
;;
|
||||
@@ -503,7 +496,6 @@ do
|
||||
# Must have coverage installed (try `which coverage`) - only valid with --unit
|
||||
# for now. Would be great to get this to work with functional tests though.
|
||||
coverage_arg="--with-coverage"
|
||||
NOSE_WITH_COVERAGE=true
|
||||
shift
|
||||
;;
|
||||
--debug)
|
||||
@@ -515,7 +507,7 @@ do
|
||||
-u|-unit|--unit)
|
||||
report_file="run_unit_tests.html"
|
||||
test_script="pytest"
|
||||
unit_extra='--doctest-modules --ignore lib/galaxy/web/proxy/js/node_modules/ --ignore lib/galaxy/webapps/tool_shed/controllers --ignore lib/galaxy/jobs/runners/chronos.py --ignore lib/galaxy/webapps/tool_shed/model/migrate --ignore lib/galaxy/util/jstree.py'
|
||||
unit_extra='--doctest-modules --ignore lib/galaxy/web/proxy/js/node_modules/ --ignore lib/galaxy/webapps/tool_shed/controllers --ignore lib/galaxy/jobs/runners/chronos.py --ignore lib/galaxy/webapps/tool_shed/model/migrate'
|
||||
if [ $# -gt 1 ]; then
|
||||
unit_extra="$unit_extra $2"
|
||||
shift 2
|
||||
@@ -523,7 +515,6 @@ do
|
||||
unit_extra="$unit_extra lib test/unit"
|
||||
shift 1
|
||||
fi
|
||||
coverage_file="unit_coverage.xml"
|
||||
;;
|
||||
-i|-integration|--integration)
|
||||
GALAXY_TEST_TOOL_CONF="config/tool_conf.xml.sample,test/functional/tools/samples_tool_conf.xml"
|
||||
@@ -535,7 +526,6 @@ do
|
||||
else
|
||||
integration_extra="./test/integration"
|
||||
shift 1
|
||||
coverage_file="integration_coverage.xml"
|
||||
fi
|
||||
;;
|
||||
--no_cleanup)
|
||||
@@ -616,7 +606,7 @@ elif [ -n "$toolshed_script" ]; then
|
||||
elif [ -n "$api_script" ]; then
|
||||
extra_args="$api_script"
|
||||
elif [ -n "$section_id" ]; then
|
||||
extra_args=`python tool_list.py $section_id`
|
||||
extra_args=$(python tool_list.py "$section_id")
|
||||
elif [ -n "$unit_extra" ]; then
|
||||
extra_args="$unit_extra"
|
||||
elif [ -n "$integration_extra" ]; then
|
||||
@@ -645,8 +635,8 @@ else
|
||||
fi
|
||||
export GALAXY_TEST_TOOL_CONF
|
||||
if [ "$test_script" = 'pytest' ]; then
|
||||
if [ "$coverage_arg" = "--with_coverage" ]; then
|
||||
coverage_arg="--cov-report term --cov-report xml:cov-unit.xml --cov=lib"
|
||||
if [ "$coverage_arg" = '--with-coverage' ]; then
|
||||
coverage_arg="--cov-report term --cov=lib"
|
||||
fi
|
||||
"$test_script" -v --html "$report_file" $coverage_arg $xunit_args $extra_args "$@"
|
||||
else
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd `dirname $0`/../..
|
||||
python ./scripts/cleanup_datasets/cleanup_datasets.py -d 10 -6 -r $@ >> ./scripts/cleanup_datasets/delete_datasets.log
|
||||
cd "$(dirname "$0")"/../..
|
||||
python ./scripts/cleanup_datasets/cleanup_datasets.py -d 10 -6 -r "$@" >> ./scripts/cleanup_datasets/delete_datasets.log
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user