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

This commit is contained in:
Daniel Blankenberg
2019-06-17 09:02:21 -04:00
150 changed files with 2216 additions and 1647 deletions
+2 -2
View File
@@ -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
View File
@@ -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:
+1
View File
@@ -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();
}
});
};
+25 -675
View File
@@ -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;
});
});
}
+3
View File
@@ -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");
+15
View File
@@ -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;
}
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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" > $@
+5
View File
@@ -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
+1 -1
View File
@@ -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
+2
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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'
+9 -2
View File
@@ -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
+2
View File
@@ -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
+2 -1
View File
@@ -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/" \
+3 -3
View File
@@ -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))
+2 -1
View File
@@ -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))
+1 -1
View File
@@ -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)) \
+40 -12
View File
@@ -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)
+5 -3
View File
@@ -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 = []
+7
View File
@@ -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)
+351 -3
View File
@@ -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'&lt;!\1', data)
data = re.sub(r'<([^<>\s]+?)\s*/>', self._shorttag_replace, data)
data = data.replace('&#39;', "'")
data = data.replace('&#34;', '"')
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('>', '&gt;').replace('<', '&lt;').replace('"', '&quot;')
value = self.bare_ampersand.sub("&amp;", 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 '&#160;', 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 '&copy;', 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('&amp;%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
)
+16 -9
View File
@@ -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()
+2 -6
View File
@@ -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:
+3 -3
View File
@@ -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)
+58 -25
View File
@@ -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}
+2 -2
View File
@@ -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)
+1 -1
View File
@@ -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())
+2 -3
View File
@@ -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.
+1 -1
View File
@@ -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
+9 -6
View File
@@ -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)
+48
View File
@@ -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.")
+1 -1
View File
@@ -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()
+24 -13
View File
@@ -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)
+1 -1
View File
@@ -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__)
+15 -2
View File
@@ -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
+9 -18
View File
@@ -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__()
+1 -1
View File
@@ -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__)
+11 -4
View File
@@ -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)
+1 -1
View File
@@ -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__)
+10
View File
@@ -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 -1
View File
@@ -1,3 +1,3 @@
VERSION_MAJOR = "19.05"
VERSION_MAJOR = "19.09"
VERSION_MINOR = "dev"
VERSION = VERSION_MAJOR + ('.' + VERSION_MINOR if VERSION_MINOR else '')
+1 -1
View File
@@ -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 -10
View File
@@ -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:
+16 -46
View File
@@ -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
+7 -51
View File
@@ -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),
+22 -352
View File
@@ -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'&lt;!\1', data)
data = re.sub(r'<([^<>\s]+?)\s*/>', self._shorttag_replace, data)
data = data.replace('&#39;', "'")
data = data.replace('&#34;', '"')
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('>', '&gt;').replace('<', '&lt;').replace('"', '&quot;')
value = self.bare_ampersand.sub("&amp;", 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 '&#160;', 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 '&copy;', 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('&amp;%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
)
+3 -4
View File
@@ -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(),
+12
View File
@@ -0,0 +1,12 @@
.. :changelog:
History
-------
.. to_doc
---------------------
19.9.0.dev0
---------------------
* Initial import from dev branch of Galaxy during 19.09 development cycle.
+1
View File
@@ -0,0 +1 @@
../../LICENSE.txt
+1
View File
@@ -0,0 +1 @@
include *.rst LICENSE
+1
View File
@@ -0,0 +1 @@
../package.Makefile
+14
View File
@@ -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/
+1
View File
@@ -0,0 +1 @@
../package-dev-requirements.txt
+1
View File
@@ -0,0 +1 @@
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
+1
View File
@@ -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
)
+3
View File
@@ -0,0 +1,3 @@
galaxy-util
requests
+1
View File
@@ -0,0 +1 @@
../build_scripts
+1
View File
@@ -0,0 +1 @@
../../setup.cfg
+102
View File
@@ -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 -1
View File
@@ -1,7 +1,7 @@
galaxy-objectstore
galaxy-util
bdbag
bx
bx-python
h5py
isa-rwval
numpy
-1
View File
@@ -1 +0,0 @@
../../../test/unit/unittest_utils
+1
View File
@@ -0,0 +1 @@
../../test/unit/unittest_utils/__init__.py
+1
View File
@@ -0,0 +1 @@
../../test/unit/unittest_utils/tempfilecache.py
+1
View File
@@ -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"
+43
View File
@@ -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
+3 -1
View File
@@ -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"
+1
View File
@@ -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 -1
View File
@@ -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
View File
@@ -1,7 +1,6 @@
bleach
boltons
bz2file; python_version < '3.3'
Cheetah3
docutils
markupsafe
packaging
+4
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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