diff --git a/client/src/components/Grid/GridList.vue b/client/src/components/Grid/GridList.vue
index d5eb85074aa1..11ba2e2387be 100644
--- a/client/src/components/Grid/GridList.vue
+++ b/client/src/components/Grid/GridList.vue
@@ -326,7 +326,7 @@ watch(operationMessage, () => {
:title="rowData[fieldEntry.key]"
@execute="onOperation($event, rowData)" />
-
+
{
- url_data[`f-${k[0]}`] = k[1];
- });
- return url_data;
- },
-
- // Return URL for obtaining a new grid
- get_url: function (args) {
- return `${this.get("url_base")}?${$.param(this.get_url_data())}&${$.param(args)}`;
- },
-});
diff --git a/client/src/mvc/grid/grid-template.js b/client/src/mvc/grid/grid-template.js
deleted file mode 100644
index cedaa1a07e92..000000000000
--- a/client/src/mvc/grid/grid-template.js
+++ /dev/null
@@ -1,556 +0,0 @@
-import { sanitize } from "dompurify";
-import $ from "jquery";
-import _ from "underscore";
-
-// grid view templates
-export default {
- // template
- grid: function (options) {
- let tmpl;
- if (options.embedded) {
- tmpl = this.grid_header(options) + this.grid_table(options);
- } else {
- tmpl = `
-
-
-
- ${this.grid_header(options)} |
- |
- |
-
-
- |
- |
- |
-
-
- ${this.grid_table(options)}
- `;
- }
-
- // add info text
- if (options.info_text) {
- tmpl += `
${options.info_text}
`;
- }
-
- // return
- return tmpl;
- },
-
- // template
- grid_table: function () {
- return `
- `;
- },
-
- // template
- grid_header: function (options) {
- var tmpl = '";
-
- // return template
- return tmpl;
- },
-
- // template
- header: function (options) {
- // start
- var tmpl = "";
-
- // add checkbox
- if (options.show_item_checkboxes) {
- tmpl += "";
- if (options.items.length > 0) {
- tmpl +=
- '' +
- '';
- }
- tmpl += " | ";
- }
-
- // create header elements
- for (const column of options.columns) {
- if (column.visible) {
- tmpl += ``;
- if (column.sortable) {
- tmpl += `${column.label}`;
- } else {
- tmpl += column.label;
- }
- tmpl += `${column.extra} | `;
- }
- }
-
- // finalize
- tmpl += "
";
-
- // return template
- return tmpl;
- },
-
- // template
- body: function (options) {
- // initialize
- var tmpl = "";
- var items_length = options.items.length;
-
- // empty grid?
- if (items_length === 0) {
- // No results.
- const filters = options.filters;
- const searchTerm = filters["free-text-search"] || "";
- const tags = filters.tags && filters.tags !== "All" ? `tags:${filters.tags} ` : "";
- const name = filters.name && filters.name !== "All" ? `name:${filters.name}` : "";
- const searchMsg = searchTerm || `${tags}${name}`;
- const noItemsMsg = searchMsg ? `No matching entries found for ${searchMsg}` : "No items";
-
- tmpl += `${noItemsMsg} |
`;
- }
-
- // create rows
- for (const item of options.items) {
- // Tag current
- tmpl += "";
-
- // Item selection column
- if (options.show_item_checkboxes) {
- tmpl += ` | `;
- }
-
- // Data columns
- for (const column of options.columns) {
- if (column.visible) {
- // Nowrap
- var nowrap = "";
- if (column.nowrap) {
- nowrap = 'style="white-space:nowrap;"';
- }
-
- // get column settings
- var column_settings = item.column_config[column.label];
-
- // load attributes
- var link = column_settings.link;
- var value = column_settings.value;
- var target = column_settings.target;
-
- // unescape value
- if ($.type(value) === "string") {
- value = value.replace(/\/\//g, "/");
- }
-
- // Attach popup menu?
- var popup_id = "";
- if (column.attach_popup) {
- popup_id = `grid-${item.encode_id}-popup`;
- }
-
- // Check for row wrapping
- tmpl += ``;
-
- // Determine cell content
- if (column.delayed) {
- tmpl += ` `;
- } else if (column.attach_popup && link) {
- tmpl += `
-
-
- `;
- } else if (column.attach_popup) {
- tmpl += ``;
- } else if (link) {
- tmpl += `${value}`;
- } else {
- tmpl += ``;
- }
- tmpl += " | ";
- }
- }
- tmpl += "
";
- }
- return tmpl;
- },
-
- // template
- footer: function (options) {
- // create template string
- var tmpl = "";
-
- // paging
- if (options.use_paging && options.num_pages > 1) {
- // get configuration
- var num_page_links = options.num_page_links;
- var cur_page_num = options.cur_page_num;
- var num_pages = options.num_pages;
-
- // First pass on min page.
- var page_link_range = num_page_links / 2;
- var min_page = cur_page_num - page_link_range;
- var min_offset = 0;
- if (min_page <= 0) {
- // Min page is too low.
- min_page = 1;
- min_offset = page_link_range - (cur_page_num - min_page);
- }
-
- // Set max page.
- var max_range = page_link_range + min_offset;
- var max_page = cur_page_num + max_range;
- var max_offset;
- if (max_page <= num_pages) {
- // Max page is fine.
- max_offset = 0;
- } else {
- // Max page is too high.
- max_page = num_pages;
- // +1 to account for the +1 in the loop below.
- max_offset = max_range - (max_page + 1 - cur_page_num);
- }
-
- // Second and final pass on min page to add any unused
- // offset from max to min.
- if (max_offset !== 0) {
- min_page -= max_offset;
- if (min_page < 1) {
- min_page = 1;
- }
- }
-
- // template header
- tmpl += '';
- if (options.show_item_checkboxes) {
- tmpl += " | ";
- }
- tmpl += '' + '' + "Page:";
-
- if (min_page > 1) {
- tmpl +=
- '1 ...';
- }
-
- // create page urls
- for (var page_index = min_page; page_index < max_page + 1; page_index++) {
- if (page_index == options.cur_page_num) {
- tmpl += `${page_index}`;
- } else {
- tmpl += `${page_index}`;
- }
- }
-
- // show last page
- if (max_page < num_pages) {
- tmpl += `...${num_pages}`;
- }
- tmpl += "";
-
- // Show all link
- tmpl += `
- | Show All
- |
-
`;
- }
-
- // Grid operations for multiple items.
- if (options.show_item_checkboxes) {
- // start template
- tmpl += `
-
-
- |
-
- For selected items:
- `;
-
- // configure buttons for operations
- for (const operation of options.operations) {
- if (operation.allow_multiple) {
- tmpl += ` `;
- }
- }
-
- // finalize template
- tmpl += " | " + "
";
- }
-
- // count global operations
- var found_global = false;
- for (const operation of options.operations) {
- if (operation.global_operation) {
- found_global = true;
- break;
- }
- }
-
- // add global operations
- if (found_global) {
- tmpl += "" + '';
- for (const operation of options.operations) {
- if (operation.global_operation) {
- tmpl += `${operation.label}`;
- }
- }
- tmpl += " | " + "
";
- }
-
- // add legend
- if (options.legend) {
- tmpl += `${options.legend} |
`;
- }
-
- // return
- return tmpl;
- },
-
- // template
- message: function (options) {
- var status = options.status;
- if (["success", "ok"].indexOf(status) != -1) {
- status = "done";
- }
- return `${_.escape(
- options.message
- )}
`;
- },
-
- // template
- grid_filters: function (options) {
- // get filters
- var default_filter_dict = options.default_filter_dict;
- var filters = options.filters;
-
- // show advanced search if flag set or if there are filters for advanced search fields
- var advanced_search_display = "none";
- if (options.advanced_search) {
- advanced_search_display = "block";
- }
-
- // identify columns with advanced filtering
- var show_advanced_search_link = false;
- for (const column of options.columns) {
- if (column.filterable == "advanced") {
- var column_key = column.key;
- var f_key = filters[column_key];
- var d_key = default_filter_dict[column_key];
- if (f_key && d_key && f_key != d_key) {
- advanced_search_display = "block";
- }
- show_advanced_search_link = true;
- }
- }
-
- // hide standard search if advanced is shown
- var standard_search_display = "block";
- if (advanced_search_display == "block") {
- standard_search_display = "none";
- }
-
- //
- // standard search
- //
- var tmpl = `");
- this.init_grid(grid_config);
- }
-
- // fix padding
- if (grid_config.use_panels) {
- $("#center").css({
- padding: "10px",
- overflow: "auto",
- });
- }
- },
-
- openAdvancedSearch: function () {
- var isOpen = $("#advanced-search").is(":visible");
- if (!isOpen) {
- $("#standard-search").slideToggle("fast");
- $("#advanced-search").slideToggle("fast");
- }
- },
-
- // refresh frames
- handle_refresh: function (refresh_frames) {
- if (refresh_frames) {
- if ($.inArray("history", refresh_frames) > -1) {
- const Galaxy = getGalaxyInstance();
- if (Galaxy && Galaxy.currHistoryPanel) {
- Galaxy.currHistoryPanel.loadCurrentHistory();
- }
- }
- }
- },
-
- // Initialize
- init_grid: function (grid_config) {
- this.grid.set(grid_config);
-
- // get options
- var options = this.grid.attributes;
-
- if (this.allow_title_display && options.title) {
- Utils.setWindowTitle(options.title);
- }
- // handle refresh requests
- this.handle_refresh(options.refresh_frames);
-
- // strip protocol and domain
- var url = this.grid.get("url_base");
- url = url.replace(/^.*\/\/[^/]+/, "");
- this.grid.set("url_base", url);
-
- // append main template
- this.$el.html(Templates.grid(options));
-
- // add a class identifier for styling purposes
- this.$el.addClass(this.getRootClassName(grid_config));
-
- // update div contents
- this.$el.find("#grid-table-header").html(Templates.header(options));
- this.$el.find("#grid-table-body").html(Templates.body(options));
- this.$el.find("#grid-table-footer").html(Templates.footer(options));
-
- // update message
- if (options.message) {
- this.$el.find("#grid-message").html(Templates.message(options));
- var self = this;
- if (options.use_hide_message) {
- window.setTimeout(() => {
- self.$el.find("#grid-message").html("");
- }, 5000);
- }
- }
-
- // configure elements
- this.init_grid_elements();
- this.init_grid_controls();
-
- // attach global event handler
- // TODO: redundant (the onload/standard page handlers do this) - but needed because these are constructed after page ready
- init_refresh_on_change();
- },
-
- // Initialize grid controls
- init_grid_controls: function () {
- // link
- var self = this;
-
- // Initialize grid operation button.
- this.$el.find(".operation-button").each(function () {
- $(this).off();
- $(this).click(function () {
- self.submit_operation(this);
- return false;
- });
- });
-
- // Initialize text filters to select text on click and use normal font when user is typing.
- this.$el.find("input[type=text]").each(function () {
- $(this).off();
- $(this)
- .click(function () {
- $(this).select();
- })
- .keyup(function () {
- $(this).css("font-style", "normal");
- });
- });
-
- // Initialize sort links.
- this.$el.find(".sort-link").each(function () {
- $(this).off();
- $(this).click(function () {
- self.set_sort_condition($(this).attr("sort_key"));
- return false;
- });
- });
-
- // Initialize text filters.
- this.$el.find(".text-filter-form").each(function () {
- $(this).off();
- $(this).submit(function () {
- var column_key = $(this).attr("column_key");
- var text_input_obj = $(`#input-${column_key}-filter`);
- var text_input = text_input_obj.val();
- text_input_obj.val("");
- self.add_filter_condition(column_key, text_input);
- return false;
- });
- });
-
- // Initialize categorical filters.
- this.$el.find(".text-filter-val > a").each(function () {
- $(this).off();
- $(this).click(function () {
- // Remove visible element.
- $(this).parent().remove();
-
- // Remove filter condition.
- self.remove_filter_condition($(this).attr("filter_key"), $(this).attr("filter_val"));
-
- // Return
- return false;
- });
- });
-
- // Initialize categorical filters.
- this.$el.find(".categorical-filter > a").each(function () {
- $(this).off();
- $(this).click(function () {
- self.set_categorical_filter($(this).attr("filter_key"), $(this).attr("filter_val"));
- return false;
- });
- });
-
- // Initialize standard, advanced search toggles.
- this.$el.find(".advanced-search-toggle").each(function () {
- $(this).off();
- $(this).click(() => {
- self.$el.find("#standard-search").slideToggle("fast");
- self.$el.find("#advanced-search").slideToggle("fast");
- return false;
- });
- });
-
- // Add event to check all box
- this.$el.find("#check_all").off();
- this.$el.find("#check_all").on("click", () => {
- self.check_all_items();
- });
- },
-
- // Initialize grid elements.
- init_grid_elements: function () {
- // Initialize grid selection checkboxes.
- this.$el.find(".grid").each(function () {
- var checkboxes = $(this).find("input.grid-row-select-checkbox");
- var check_count = $(this).find("span.grid-selected-count");
- var update_checked = () => {
- check_count.text($(checkboxes).filter(":checked").length);
- };
-
- $(checkboxes).each(function () {
- $(this).change(update_checked);
- });
- update_checked();
- });
-
- // Initialize ratings.
- if (this.$el.find(".community_rating_star").length !== 0) {
- this.$el.find(".community_rating_star").rating({});
- }
-
- // get options
- var options = this.grid.attributes;
- var self = this;
-
- //
- // add page click events
- //
- this.$el.find(".page-link-grid > a").each(function () {
- $(this).click(function () {
- self.set_page($(this).attr("page_num"));
- return false;
- });
- });
-
- //
- // add inbound/outbound events
- //
- this.$el.find(".use-target").each(function () {
- $(this).click(function () {
- self.execute({
- href: $(this).attr("href"),
- target: $(this).attr("target"),
- });
- return false;
- });
- });
-
- // empty grid?
- var items_length = options.items.length;
- if (items_length === 0) {
- return;
- }
-
- // add operation popup menus
- _.each(options.items, (item, index) => {
- var button = self.$(`#grid-${item.encode_id}-popup`).off();
- var popup = new PopupMenu(button);
- _.each(options.operations, (operation) => {
- self.add_operation(popup, operation, item);
- });
- });
- },
-
- /** Add an operation to the items menu */
- add_operation: function (popup, operation, item) {
- var self = this;
- var settings = item.operation_config[operation.label];
- if (settings.allowed && operation.allow_popup) {
- popup.addItem({
- html: operation.label,
- href: settings.url_args,
- target: settings.target,
- confirmation_text: operation.confirm,
- func: function (e) {
- e.preventDefault();
- var label = $(e.target).html();
- if (operation.onclick) {
- operation.onclick(item.encode_id);
- } else {
- self.execute(this.findItemByHtml(label));
- }
- },
- });
- }
- },
-
- // Add a condition to the grid filter; this adds the condition and refreshes the grid.
- add_filter_condition: function (name, value) {
- // Do nothing is value is empty.
- if (value === "") {
- return false;
- }
-
- // Add condition to grid.
- this.grid.add_filter(name, value, true);
-
- this.render_filter_button(name, value);
-
- // execute
- this.go_page_one();
- this.execute();
- },
-
- render_filter_button: function (name, value) {
- // Add button that displays filter and provides a button to delete it.
- var t = $(Templates.filter_element(name, value));
- var self = this;
- t.click(function () {
- // Remove visible element.
- $(this).remove();
-
- // Remove filter condition.
- self.remove_filter_condition(name, value);
- });
-
- // append to container
- var container = this.$el.find(`#${name}-filtering-criteria`);
- container.append(t);
- },
-
- // Remove a condition to the grid filter; this adds the condition and refreshes the grid.
- remove_filter_condition: function (name, value) {
- // Remove filter condition.
- this.grid.remove_filter(name, value);
-
- // Execute
- this.go_page_one();
- this.execute();
- },
-
- // Set sort condition for grid.
- set_sort_condition: function (col_key) {
- // Set new sort condition. New sort is col_key if sorting new column; if reversing sort on
- // currently sorted column, sort is reversed.
- var cur_sort = this.grid.get("sort_key");
- var new_sort = col_key;
- if (cur_sort.indexOf(col_key) !== -1) {
- // Reverse sort.
- if (cur_sort.substring(0, 1) !== "-") {
- new_sort = `-${col_key}`;
- }
- }
-
- // Remove sort arrows elements.
- this.$el.find(".sort-arrow").remove();
-
- // Add sort arrow element to new sort column.
- var sort_arrow = new_sort.substring(0, 1) == "-" ? "↑" : "↓";
- var t = $(`
${sort_arrow}`).addClass("sort-arrow");
-
- // Add to header
- this.$el.find(`#${col_key}-header`).append(t);
-
- // Update grid.
- this.grid.set("sort_key", new_sort);
- this.go_page_one();
- this.execute();
- },
-
- // Set new value for categorical filter.
- set_categorical_filter: function (name, new_value) {
- // Update filter hyperlinks to reflect new filter value.
- var category_filter = this.grid.get("categorical_filters")[name];
-
- var cur_value = this.grid.get("filters")[name];
- var self = this;
- this.$el.find(`.${name}-filter`).each(function () {
- var text = $.trim($(this).text());
- var filter = category_filter[text];
- var filter_value = filter[name];
- if (filter_value == new_value) {
- // Remove filter link since grid will be using this filter. It is assumed that
- // this element has a single child, a hyperlink/anchor with text.
- $(this).empty();
- $(this).addClass("current-filter");
- $(this).append(text);
- } else if (filter_value == cur_value) {
- // Add hyperlink for this filter since grid will no longer be using this filter. It is assumed that
- // this element has a single child, a hyperlink/anchor.
- $(this).empty();
- var t = $(`
${text}`);
- t.click(() => {
- self.set_categorical_filter(name, filter_value);
- });
- $(this).removeClass("current-filter");
- $(this).append(t);
- }
- });
-
- // Update grid.
- this.grid.add_filter(name, new_value);
- this.go_page_one();
- this.execute();
- },
-
- // Set page to view.
- set_page: function (new_page) {
- // Update page hyperlink to reflect new page.
- var self = this;
- this.$el.find(".page-link").each(function () {
- var id = $(this).attr("id");
-
- var // Id has form 'page-link-
- page_num = parseInt(id.split("-")[2], 10);
-
- var cur_page = self.grid.get("cur_page");
- var text;
- if (page_num === new_page) {
- // Remove link to page since grid will be on this page. It is assumed that
- // this element has a single child, a hyperlink/anchor with text.
- text = $(this).children().text();
- $(this).empty();
- $(this).addClass("inactive-link");
- $(this).text(text);
- } else if (page_num === cur_page) {
- // Add hyperlink to this page since grid will no longer be on this page. It is assumed that
- // this element has a single child, a hyperlink/anchor.
- text = $(this).text();
- $(this).empty();
- $(this).removeClass("inactive-link");
- var t = $(`${text}`);
- t.click(() => {
- self.set_page(page_num);
- });
- $(this).append(t);
- }
- });
-
- if (new_page === "all") {
- this.grid.set("cur_page", new_page);
- } else {
- this.grid.set("cur_page", parseInt(new_page, 10));
- }
- this.execute();
- },
-
- // confirmation/submission of operation request
- submit_operation: function (operation_button, confirmation_text) {
- // identify operation
- var operation_name = $(operation_button).val();
-
- // verify any item is selected
- var number_of_checked_ids = this.$el.find('input[name="id"]:checked').length;
- if (number_of_checked_ids < 1) {
- return false;
- }
-
- // Check to see if there's grid confirmation text for this operation
- var operation = _.findWhere(this.grid.attributes.operations, {
- label: operation_name,
- });
- if (operation && !confirmation_text) {
- confirmation_text = operation.confirm || "";
- }
-
- // collect ids
- var item_ids = [];
- this.$el.find("input[name=id]:checked").each(function () {
- item_ids.push($(this).val());
- });
-
- // execute operation
- var options = {
- operation: operation_name,
- id: item_ids,
- confirmation_text: confirmation_text,
- };
- if (operation.target == "top" || operation.target == "center") {
- options = _.extend(options, {
- href: operation.href,
- target: operation.target,
- });
- }
- this.execute(options);
- return true;
- },
-
- check_all_items: function () {
- var check = this.$(".grid-row-select-checkbox");
- var state = this.$("#check_all").prop("checked");
- _.each(check, (c) => {
- $(c).prop("checked", state);
- });
- this.init_grid_elements();
- },
-
- // Go back to page one; this is useful when a filter is applied.
- go_page_one: function () {
- // Need to go back to page 1 if not showing all.
- var cur_page = this.grid.get("cur_page");
- if (cur_page !== null && cur_page !== undefined && cur_page !== "all") {
- this.grid.set("cur_page", 1);
- }
- },
-
- //
- // execute operations and hyperlink requests
- //
- execute: function (options) {
- // get url
- var id = null;
- var href = null;
- var operation = null;
- var confirmation_text = null;
- var target = null;
-
- // check for options
- if (options) {
- // get options
- href = options.href;
- operation = options.operation;
- id = options.id;
- confirmation_text = options.confirmation_text;
- target = options.target;
-
- // check if input contains the operation tag
- if (href !== undefined && href.indexOf("operation=") != -1) {
- // Get operation, id in hyperlink's href.
- var href_parts = href.split("?");
- if (href_parts.length > 1) {
- var href_parms_str = href_parts[1];
- var href_parms = href_parms_str.split("&");
- for (var index = 0; index < href_parms.length; index++) {
- if (href_parms[index].indexOf("operation") != -1) {
- // Found operation parm; get operation value.
- operation = href_parms[index].split("=")[1];
- operation = operation.replace(/\+/g, " ");
- } else if (href_parms[index].indexOf("id") != -1) {
- // Found id parm; get id value.
- id = href_parms[index].split("=")[1];
- }
- }
- }
- }
- }
-
- // check for operation details
- if (operation && id) {
- // show confirmation box
- if (
- confirmation_text &&
- confirmation_text !== "" &&
- confirmation_text != "None" &&
- confirmation_text != "null"
- ) {
- if (!window.confirm(confirmation_text)) {
- return false;
- }
- }
-
- // use small characters for operation?!
- operation = operation.toLowerCase();
-
- // Update grid.
- this.grid.set({
- operation: operation,
- item_ids: id,
- });
-
- // Do operation. If operation cannot be performed asynchronously, redirect to location.
- if (target == "top") {
- window.top.location = `${href}?${$.param(this.grid.get_url_data())}`;
- } else if (target == "center") {
- $("#galaxy_main").attr("src", `${href}?${$.param(this.grid.get_url_data())}`);
- } else {
- this.update_grid();
- }
-
- // done
- return false;
- }
-
- // refresh grid
- if (href) {
- this.go_to(target, href);
- return false;
- }
-
- // refresh grid
- this.update_grid();
-
- // done
- return false;
- },
-
- // go to url
- go_to: function (target, href) {
- // get slide status
- var advanced_search = this.$el.find("#advanced-search").is(":visible");
- this.grid.set("advanced_search", advanced_search);
-
- // get default url
- if (!href) {
- href = `${this.grid.get("url_base")}?${$.param(this.grid.get_url_data())}`;
- }
-
- // clear grid of transient request attributes.
- this.grid.set({
- operation: undefined,
- item_ids: undefined,
- });
- switch (target) {
- case "center":
- $("#galaxy_main").attr("src", href);
- break;
- case "top":
- window.top.location = href;
- break;
- default:
- window.location = href;
- }
- },
-
- // Update grid.
- update_grid: function () {
- // If there's an operation, do POST; otherwise, do GET.
- var method = this.grid.get("operation") ? "POST" : "GET";
-
- // Show overlay to indicate loading and prevent user actions.
- this.$el.find(".loading-elt-overlay").show();
- var self = this;
- $.ajax({
- type: method,
- url: self.grid.get("url_base"),
- data: self.grid.get_url_data(),
- error: function () {
- alert("Grid refresh failed");
- },
- success: function (response_text) {
- // backup
- var embedded = self.grid.get("embedded");
- var insert = self.grid.get("insert");
- var advanced_search = self.$el.find("#advanced-search").is(":visible");
-
- // request new configuration
- var json = response_text;
-
- // update
- json.embedded = embedded;
- json.insert = insert;
- json.advanced_search = advanced_search;
-
- // Initialize new grid config
- self.init_grid(json);
-
- // Hide loading overlay.
- self.$el.find(".loading-elt-overlay").hide();
- },
- complete: function () {
- // Clear grid of transient request attributes.
- self.grid.set({
- operation: undefined,
- item_ids: undefined,
- });
- },
- });
- },
-
- // Generates a class name at the root of the view that we can
- // use for conditional styling in the various kinds of grids
- // instead of acres of if/then statements in javascript
- getRootClassName({ title = "grid" }) {
- return slugify(title).toLowerCase();
- },
-});
diff --git a/client/src/mvc/visualization/chart/chart-client.js b/client/src/mvc/visualization/chart/chart-client.js
index 4369e1512b2d..4f7a25b2ff17 100644
--- a/client/src/mvc/visualization/chart/chart-client.js
+++ b/client/src/mvc/visualization/chart/chart-client.js
@@ -1,15 +1,16 @@
import { getGalaxyInstance } from "app";
import Backbone from "backbone";
import $ from "jquery";
-import Ui from "mvc/ui/ui-misc";
import Modal from "mvc/ui/ui-modal";
-import Chart from "mvc/visualization/chart/components/model";
-import Editor from "mvc/visualization/chart/views/editor";
-import Menu from "mvc/visualization/chart/views/menu";
-import Viewer from "mvc/visualization/chart/views/viewer";
import { getAppRoot } from "onload/loadConfig";
import Deferred from "utils/deferred";
+import Chart from "./components/model";
+import Editor from "./views/editor";
+import Menu from "./views/menu";
+import Ui from "./views/misc";
+import Viewer from "./views/viewer";
+
/** Get boolean as string */
function asBoolean(value) {
return String(value).toLowerCase() == "true";
diff --git a/client/src/mvc/ui/ui-buttons.js b/client/src/mvc/visualization/chart/views/buttons.js
similarity index 100%
rename from client/src/mvc/ui/ui-buttons.js
rename to client/src/mvc/visualization/chart/views/buttons.js
diff --git a/client/src/mvc/visualization/chart/views/editor.js b/client/src/mvc/visualization/chart/views/editor.js
index d30911d94157..3c9c21bb3f32 100644
--- a/client/src/mvc/visualization/chart/views/editor.js
+++ b/client/src/mvc/visualization/chart/views/editor.js
@@ -4,11 +4,12 @@
*/
import Backbone from "backbone";
import $ from "jquery";
-import Ui from "mvc/ui/ui-misc";
-import Tabs from "mvc/ui/ui-tabs";
-import Description from "mvc/visualization/chart/views/description";
-import Groups from "mvc/visualization/chart/views/groups";
-import Settings from "mvc/visualization/chart/views/settings";
+
+import Description from "./description";
+import Groups from "./groups";
+import Ui from "./misc";
+import Settings from "./settings";
+import Tabs from "./tabs";
export default Backbone.View.extend({
initialize: function (app, options) {
diff --git a/client/src/mvc/visualization/chart/views/menu.js b/client/src/mvc/visualization/chart/views/menu.js
index 283c329767f9..8ed0575bb970 100644
--- a/client/src/mvc/visualization/chart/views/menu.js
+++ b/client/src/mvc/visualization/chart/views/menu.js
@@ -1,6 +1,7 @@
/** This class renders the chart menu options. */
import Backbone from "backbone";
-import Ui from "mvc/ui/ui-misc";
+
+import Ui from "./misc";
export default Backbone.View.extend({
initialize: function (app) {
diff --git a/client/src/mvc/ui/ui-misc.js b/client/src/mvc/visualization/chart/views/misc.js
similarity index 99%
rename from client/src/mvc/ui/ui-misc.js
rename to client/src/mvc/visualization/chart/views/misc.js
index af8d4a236540..64c5a100c871 100644
--- a/client/src/mvc/ui/ui-misc.js
+++ b/client/src/mvc/visualization/chart/views/misc.js
@@ -3,10 +3,11 @@
*/
import Backbone from "backbone";
import $ from "jquery";
-import Buttons from "mvc/ui/ui-buttons";
import Modal from "mvc/ui/ui-modal";
import _ from "underscore";
+import Buttons from "./buttons";
+
/** Displays messages used e.g. in the tool form */
export var Message = Backbone.View.extend({
initialize: function (options) {
diff --git a/client/src/mvc/visualization/chart/views/portlet.js b/client/src/mvc/visualization/chart/views/portlet.js
index 3dfd60c03a9a..6bc6a542fd0f 100644
--- a/client/src/mvc/visualization/chart/views/portlet.js
+++ b/client/src/mvc/visualization/chart/views/portlet.js
@@ -1,8 +1,9 @@
import Backbone from "backbone";
import $ from "jquery";
-import Ui from "mvc/ui/ui-misc";
import Utils from "utils/utils";
+import Ui from "./misc";
+
export var View = Backbone.View.extend({
visible: false,
initialize: function (options) {
diff --git a/client/src/mvc/visualization/chart/views/repeat.js b/client/src/mvc/visualization/chart/views/repeat.js
index 027494c138ad..f88059fdbd16 100644
--- a/client/src/mvc/visualization/chart/views/repeat.js
+++ b/client/src/mvc/visualization/chart/views/repeat.js
@@ -1,11 +1,11 @@
/** This class creates a ui component which enables the dynamic creation of portlets */
import Backbone from "backbone";
import $ from "jquery";
-import Ui from "mvc/ui/ui-misc";
import _ from "underscore";
import _l from "utils/localization";
import Utils from "utils/utils";
+import Ui from "./misc";
import Portlet from "./portlet";
export var View = Backbone.View.extend({
diff --git a/client/src/mvc/ui/ui-tabs.js b/client/src/mvc/visualization/chart/views/tabs.js
similarity index 100%
rename from client/src/mvc/ui/ui-tabs.js
rename to client/src/mvc/visualization/chart/views/tabs.js
diff --git a/client/src/mvc/ui/icon-button.js b/client/src/viz/icon-button.js
similarity index 100%
rename from client/src/mvc/ui/icon-button.js
rename to client/src/viz/icon-button.js
diff --git a/client/src/viz/trackster.js b/client/src/viz/trackster.js
index 9b6fe3bbe86a..d3c3162479a4 100644
--- a/client/src/viz/trackster.js
+++ b/client/src/viz/trackster.js
@@ -16,7 +16,6 @@ import "ui/editable-text";
import { getGalaxyInstance } from "app";
import Backbone from "backbone";
import $ from "jquery";
-import IconButton from "mvc/ui/icon-button";
import { getAppRoot } from "onload/loadConfig";
import _ from "underscore";
import _l from "utils/localization";
@@ -24,6 +23,8 @@ import query_string from "utils/query-string-parsing";
import tracks from "viz/trackster/tracks";
import visualization from "viz/visualization";
+import IconButton from "./icon-button";
+
//import "static/style/jquery-ui/smoothness/jquery-ui.css";
//import "static/style/library.css";
//import "static/style/trackster.css";
diff --git a/lib/galaxy/web/framework/helpers/grids.py b/lib/galaxy/web/framework/helpers/grids.py
index 1306b7306f6f..98795511186d 100644
--- a/lib/galaxy/web/framework/helpers/grids.py
+++ b/lib/galaxy/web/framework/helpers/grids.py
@@ -1,40 +1,15 @@
import logging
-import math
-from json import (
- dumps,
- loads,
-)
from typing import (
- Dict,
List,
Optional,
)
from markupsafe import escape
-from sqlalchemy.sql.expression import (
- and_,
- false,
- func,
- null,
- or_,
- true,
-)
-from galaxy.model.item_attrs import (
- get_foreign_key,
- UsesAnnotations,
- UsesItemRatings,
-)
from galaxy.util import (
- restore_text,
- sanitize_text,
string_as_bool,
unicodify,
)
-from galaxy.web.framework import (
- decorators,
- url_for,
-)
log = logging.getLogger(__name__)
@@ -47,16 +22,6 @@ def __init__(
model_class=None,
method=None,
format=None,
- link=None,
- attach_popup=False,
- visible=True,
- nowrap=False,
- # Valid values for filterable are ['standard', 'advanced', None]
- filterable=None,
- sortable=True,
- label_id_prefix=None,
- target=None,
- delayed=False,
escape=True,
):
"""Create a grid column."""
@@ -65,17 +30,7 @@ def __init__(
self.model_class = model_class
self.method = method
self.format = format
- self.link = link
- self.target = target
- self.nowrap = nowrap
- self.attach_popup = attach_popup
- self.visible = visible
- self.filterable = filterable
- self.delayed = delayed
self.escape = escape
- # Column must have a key to be sortable.
- self.sortable = self.key is not None and sortable
- self.label_id_prefix = label_id_prefix or ""
def get_value(self, trans, grid, item):
if self.method:
@@ -91,30 +46,6 @@ def get_value(self, trans, grid, item):
else:
return value
- def get_link(self, trans, grid, item):
- if self.link and self.link(item):
- return self.link(item)
- return None
-
- def filter(self, trans, user, query, column_filter):
- """Modify query to reflect the column filter."""
- if column_filter == "All":
- pass
- if column_filter == "True":
- query = query.filter_by(**{self.key: True})
- elif column_filter == "False":
- query = query.filter_by(**{self.key: False})
- return query
-
- def get_accepted_filters(self):
- """Returns a list of accepted filters for this column."""
- accepted_filters_vals = ["False", "True", "All"]
- accepted_filters = []
- for val in accepted_filters_vals:
- args = {self.key: val}
- accepted_filters.append(GridColumnFilter(val, args))
- return accepted_filters
-
def sort(self, trans, query, ascending, column_name=None):
"""Sort query using this column."""
if column_name is None:
@@ -129,931 +60,6 @@ def sort(self, trans, query, ascending, column_name=None):
return query
-class ReverseSortColumn(GridColumn):
- """Column that reverses sorting; this is useful when the natural sort is descending."""
-
- def sort(self, trans, query, ascending, column_name=None):
- return GridColumn.sort(self, trans, query, (not ascending), column_name=column_name)
-
-
-class TextColumn(GridColumn):
- """Generic column that employs freetext and, hence, supports freetext, case-independent filtering."""
-
- def filter(self, trans, user, query, column_filter):
- """Modify query to filter using free text, case independence."""
- if column_filter == "All":
- pass
- elif column_filter:
- query = query.filter(self.get_filter(trans, user, column_filter))
- return query
-
- def get_filter(self, trans, user, column_filter):
- """Returns a SQLAlchemy criterion derived from column_filter."""
- if isinstance(column_filter, str):
- return self.get_single_filter(user, column_filter)
- elif isinstance(column_filter, list):
- clause_list = []
- for filter in column_filter:
- clause_list.append(self.get_single_filter(user, filter))
- return and_(*clause_list)
-
- def get_single_filter(self, user, a_filter):
- """
- Returns a SQLAlchemy criterion derived for a single filter. Single filter
- is the most basic filter--usually a string--and cannot be a list.
- """
- # Queries that include table joins cannot guarantee that table column names will be
- # unique, so check to see if a_filter is of type ..
- if self.key.find(".") > -1:
- a_key = self.key.split(".")[1]
- else:
- a_key = self.key
- model_class_key_field = getattr(self.model_class, a_key)
- return func.lower(model_class_key_field).like(f"%{a_filter.lower()}%")
-
- def sort(self, trans, query, ascending, column_name=None):
- """Sort column using case-insensitive alphabetical sorting."""
- if column_name is None:
- column_name = self.key
- if ascending:
- query = query.order_by(func.lower(self.model_class.table.c.get(column_name)).asc())
- else:
- query = query.order_by(func.lower(self.model_class.table.c.get(column_name)).desc())
- return query
-
-
-class DateTimeColumn(TextColumn):
- def sort(self, trans, query, ascending, column_name=None):
- """Sort query using this column."""
- return GridColumn.sort(self, trans, query, ascending, column_name=column_name)
-
-
-class BooleanColumn(TextColumn):
- def sort(self, trans, query, ascending, column_name=None):
- """Sort query using this column."""
- return GridColumn.sort(self, trans, query, ascending, column_name=column_name)
-
- def get_single_filter(self, user, a_filter):
- if self.key.find(".") > -1:
- a_key = self.key.split(".")[1]
- else:
- a_key = self.key
- model_class_key_field = getattr(self.model_class, a_key)
- return model_class_key_field == a_filter
-
-
-class IntegerColumn(TextColumn):
- """
- Integer column that employs freetext, but checks that the text is an integer,
- so support filtering on integer values.
-
- IMPORTANT NOTE: grids that use this column type should not include the column
- in the cols_to_filter list of MulticolFilterColumn ( i.e., searching on this
- column type should not be performed in the grid's standard search - it won't
- throw exceptions, but it also will not find what you're looking for ). Grids
- that search on this column should use 'filterable="advanced"' so that searching
- is only performed in the advanced search component, restricting the search to
- the specific column.
-
- This is useful for searching on object ids or other integer columns. See the
- JobIdColumn column in the SpecifiedDateListGrid class in the jobs controller of
- the reports webapp for an example.
- """
-
- def get_single_filter(self, user, a_filter):
- model_class_key_field = getattr(self.model_class, self.key)
- assert int(a_filter), "The search entry must be an integer"
- return model_class_key_field == int(a_filter)
-
- def sort(self, trans, query, ascending, column_name=None):
- """Sort query using this column."""
- return GridColumn.sort(self, trans, query, ascending, column_name=column_name)
-
-
-class CommunityRatingColumn(GridColumn, UsesItemRatings):
- """Column that displays community ratings for an item."""
-
- def get_value(self, trans, grid, item):
- if not hasattr(item, "average_rating"):
- # No prefetched column property, generate it on the fly.
- ave_item_rating, num_ratings = self.get_ave_item_rating_data(
- trans.sa_session, item, webapp_model=trans.model
- )
- else:
- ave_item_rating = item.average_rating
- num_ratings = 2 # just used for pluralization
- if not ave_item_rating:
- ave_item_rating = 0
- return trans.fill_template(
- "tool_shed_rating.mako",
- ave_item_rating=ave_item_rating,
- num_ratings=num_ratings,
- item_id=trans.security.encode_id(item.id),
- )
-
- def sort(self, trans, query, ascending, column_name=None):
- # Get the columns that connect item's table and item's rating association table.
- item_rating_assoc_class = getattr(trans.model, f"{self.model_class.__name__}RatingAssociation")
- foreign_key = get_foreign_key(item_rating_assoc_class, self.model_class)
- fk_col = foreign_key.parent
- referent_col = foreign_key.get_referent(self.model_class.table)
- # Do sorting using a subquery.
- # Subquery to get average rating for each item.
- ave_rating_subquery = (
- trans.sa_session.query(fk_col, func.avg(item_rating_assoc_class.table.c.rating).label("avg_rating"))
- .group_by(fk_col)
- .subquery()
- )
- # Integrate subquery into main query.
- query = query.outerjoin((ave_rating_subquery, referent_col == ave_rating_subquery.columns[fk_col.name]))
- # Sort using subquery results; use coalesce to avoid null values.
- if (
- not ascending
- ): # TODO: for now, reverse sorting b/c first sort is ascending, and that should be the natural sort.
- query = query.order_by(func.coalesce(ave_rating_subquery.c.avg_rating, 0).asc())
- else:
- query = query.order_by(func.coalesce(ave_rating_subquery.c.avg_rating, 0).desc())
- return query
-
-
-class OwnerAnnotationColumn(TextColumn, UsesAnnotations):
- """Column that displays and filters item owner's annotations."""
-
- def __init__(self, col_name, key, model_class=None, model_annotation_association_class=None, filterable=None):
- GridColumn.__init__(self, col_name, key=key, model_class=model_class, filterable=filterable)
- self.sortable = False
- self.model_annotation_association_class = model_annotation_association_class
-
- def get_value(self, trans, grid, item):
- """Returns first 150 characters of annotation."""
- annotation = self.get_item_annotation_str(trans.sa_session, item.user, item)
- if annotation:
- ann_snippet = annotation[:155]
- if len(annotation) > 155:
- ann_snippet = ann_snippet[: ann_snippet.rfind(" ")]
- ann_snippet += "..."
- else:
- ann_snippet = ""
- return escape(ann_snippet)
-
- def get_single_filter(self, user, a_filter):
- """Filter by annotation and annotation owner."""
- return self.model_class.annotations.any(
- and_(
- func.lower(self.model_annotation_association_class.annotation).like(f"%{a_filter.lower()}%"),
- # TODO: not sure why, to filter by owner's annotations, we have to do this rather than
- # 'self.model_class.user==self.model_annotation_association_class.user'
- self.model_annotation_association_class.table.c.user_id == self.model_class.table.c.user_id,
- )
- )
-
-
-class CommunityTagsColumn(TextColumn):
- """Column that supports community tags."""
-
- def __init__(
- self, col_name, key, model_class=None, model_tag_association_class=None, filterable=None, grid_name=None
- ):
- GridColumn.__init__(
- self, col_name, key=key, model_class=model_class, nowrap=True, filterable=filterable, sortable=False
- )
- self.model_tag_association_class = model_tag_association_class
- # Column-specific attributes.
- self.grid_name = grid_name
-
- def get_value(self, trans, grid, item):
- return trans.fill_template(
- "/tagging_common.mako",
- tag_type="community",
- trans=trans,
- user=trans.get_user(),
- tagged_item=item,
- elt_context=self.grid_name,
- tag_click_fn="add_tag_to_grid_filter",
- use_toggle_link=True,
- )
-
- def filter(self, trans, user, query, column_filter):
- """Modify query to filter model_class by tag. Multiple filters are ANDed."""
- if column_filter == "All":
- pass
- elif column_filter:
- query = query.filter(self.get_filter(trans, user, column_filter))
- return query
-
- def get_filter(self, trans, user, column_filter):
- # Parse filter to extract multiple tags.
- if isinstance(column_filter, list):
- # Collapse list of tags into a single string; this is redundant but effective. TODO: fix this by iterating over tags.
- column_filter = ",".join(column_filter)
- raw_tags = trans.app.tag_handler.parse_tags(column_filter)
- clause_list = []
- for name, value in raw_tags:
- if name:
- # Filter by all tags.
- clause_list.append(
- self.model_class.tags.any(
- func.lower(self.model_tag_association_class.user_tname).like(f"%{name.lower()}%")
- )
- )
- if value:
- # Filter by all values.
- clause_list.append(
- self.model_class.tags.any(
- func.lower(self.model_tag_association_class.user_value).like(f"%{value.lower()}%")
- )
- )
- return and_(*clause_list)
-
-
-class IndividualTagsColumn(CommunityTagsColumn):
- """Column that supports individual tags."""
-
- def get_value(self, trans, grid, item):
- return trans.fill_template(
- "/tagging_common.mako",
- tag_type="individual",
- user=trans.user,
- tagged_item=item,
- elt_context=self.grid_name,
- tag_click_fn="add_tag_to_grid_filter",
- use_toggle_link=True,
- )
-
- def get_filter(self, trans, user, column_filter):
- # Parse filter to extract multiple tags.
- if isinstance(column_filter, list):
- # Collapse list of tags into a single string; this is redundant but effective. TODO: fix this by iterating over tags.
- column_filter = ",".join(column_filter)
- raw_tags = trans.app.tag_handler.parse_tags(column_filter)
- clause_list = []
- for name, value in raw_tags:
- if name:
- # Filter by individual's tag names.
- clause_list.append(
- self.model_class.tags.any(
- and_(
- func.lower(self.model_tag_association_class.user_tname).like(f"%{name.lower()}%"),
- self.model_tag_association_class.user == user,
- )
- )
- )
- if value:
- # Filter by individual's tag values.
- clause_list.append(
- self.model_class.tags.any(
- and_(
- func.lower(self.model_tag_association_class.user_value).like(f"%{value.lower()}%"),
- self.model_tag_association_class.user == user,
- )
- )
- )
- return and_(*clause_list)
-
-
-class MulticolFilterColumn(TextColumn):
- """Column that performs multicolumn filtering."""
-
- def __init__(self, col_name, cols_to_filter, key, visible, filterable="default"):
- GridColumn.__init__(self, col_name, key=key, visible=visible, filterable=filterable)
- self.cols_to_filter = cols_to_filter
-
- def filter(self, trans, user, query, column_filter):
- """Modify query to filter model_class by tag. Multiple filters are ANDed."""
- if column_filter == "All":
- return query
- if isinstance(column_filter, list):
- clause_list = []
- for filter in column_filter:
- part_clause_list = []
- for column in self.cols_to_filter:
- part_clause_list.append(column.get_filter(trans, user, filter))
- clause_list.append(or_(*part_clause_list))
- complete_filter = and_(*clause_list)
- else:
- clause_list = []
- for column in self.cols_to_filter:
- clause_list.append(column.get_filter(trans, user, column_filter))
- complete_filter = or_(*clause_list)
- return query.filter(complete_filter)
-
-
-class OwnerColumn(TextColumn):
- """Column that lists item's owner."""
-
- def get_value(self, trans, grid, item):
- return item.user.username
-
- def sort(self, trans, query, ascending, column_name=None):
- """Sort column using case-insensitive alphabetical sorting on item's username."""
- if ascending:
- query = query.order_by(func.lower(self.model_class.username).asc())
- else:
- query = query.order_by(func.lower(self.model_class.username).desc())
- return query
-
-
-class PublicURLColumn(TextColumn):
- """Column displays item's public URL based on username and slug."""
-
- def get_link(self, trans, grid, item):
- if item.user.username and item.slug:
- return dict(action="display_by_username_and_slug", username=item.user.username, slug=item.slug)
- elif not item.user.username:
- # TODO: provide link to set username.
- return None
- elif not item.user.slug:
- # TODO: provide link to set slug.
- return None
-
-
-class DeletedColumn(GridColumn):
- """Column that tracks and filters for items with deleted attribute."""
-
- def get_accepted_filters(self):
- """Returns a list of accepted filters for this column."""
- accepted_filter_labels_and_vals = {"active": "False", "deleted": "True", "all": "All"}
- accepted_filters = []
- for label, val in accepted_filter_labels_and_vals.items():
- args = {self.key: val}
- accepted_filters.append(GridColumnFilter(label, args))
- return accepted_filters
-
- def filter(self, trans, user, query, column_filter):
- """Modify query to filter self.model_class by state."""
- if column_filter == "All":
- pass
- elif column_filter in ["True", "False"]:
- query = query.filter(self.model_class.deleted == (column_filter == "True"))
- return query
-
-
-class PurgedColumn(GridColumn):
- """Column that tracks and filters for items with purged attribute."""
-
- def get_accepted_filters(self):
- """Returns a list of accepted filters for this column."""
- accepted_filter_labels_and_vals = {"nonpurged": "False", "purged": "True", "all": "All"}
- accepted_filters = []
- for label, val in accepted_filter_labels_and_vals.items():
- args = {self.key: val}
- accepted_filters.append(GridColumnFilter(label, args))
- return accepted_filters
-
- def filter(self, trans, user, query, column_filter):
- """Modify query to filter self.model_class by state."""
- if column_filter == "All":
- pass
- elif column_filter in ["True", "False"]:
- query = query.filter(self.model_class.purged == (column_filter == "True"))
- return query
-
-
-class StateColumn(GridColumn):
- """
- Column that tracks and filters for items with state attribute.
-
- IMPORTANT NOTE: self.model_class must have a states Bunch or dict if
- this column type is used in the grid.
- """
-
- def get_value(self, trans, grid, item):
- return item.state
-
- def filter(self, trans, user, query, column_filter):
- """Modify query to filter self.model_class by state."""
- if column_filter == "All":
- pass
- elif column_filter in [v for k, v in self.model_class.states.items()]:
- query = query.filter(self.model_class.state == column_filter)
- return query
-
- def get_accepted_filters(self):
- """Returns a list of accepted filters for this column."""
- all = GridColumnFilter("all", {self.key: "All"})
- accepted_filters = [all]
- for v in self.model_class.states.values():
- args = {self.key: v}
- accepted_filters.append(GridColumnFilter(v, args))
- return accepted_filters
-
-
-class SharingStatusColumn(GridColumn):
- """Grid column to indicate sharing status."""
-
- def __init__(self, *args, **kwargs):
- self.use_shared_with_count = kwargs.pop("use_shared_with_count", False)
- super().__init__(*args, **kwargs)
-
- def get_value(self, trans, grid, item):
- # Delete items cannot be shared.
- if item.deleted:
- return ""
- # Build a list of sharing for this item.
- sharing_statuses = []
- if self._is_shared(item):
- sharing_statuses.append("Shared")
- if item.importable:
- sharing_statuses.append("Accessible")
- if item.published:
- sharing_statuses.append("Published")
- return ", ".join(sharing_statuses)
-
- def _is_shared(self, item):
- if self.use_shared_with_count:
- # optimization to skip join for users_shared_with and loading in that data.
- return item.users_shared_with_count > 0
-
- return item.users_shared_with
-
- def filter(self, trans, user, query, column_filter):
- """Modify query to filter histories by sharing status."""
- if column_filter == "All":
- pass
- elif column_filter:
- if column_filter == "private":
- query = query.filter(self.model_class.users_shared_with == null())
- query = query.filter(self.model_class.importable == false())
- elif column_filter == "shared":
- query = query.filter(self.model_class.users_shared_with != null())
- elif column_filter == "accessible":
- query = query.filter(self.model_class.importable == true())
- elif column_filter == "published":
- query = query.filter(self.model_class.published == true())
- return query
-
- def get_accepted_filters(self):
- """Returns a list of accepted filters for this column."""
- accepted_filter_labels_and_vals = {}
- accepted_filter_labels_and_vals["private"] = "private"
- accepted_filter_labels_and_vals["shared"] = "shared"
- accepted_filter_labels_and_vals["accessible"] = "accessible"
- accepted_filter_labels_and_vals["published"] = "published"
- accepted_filter_labels_and_vals["all"] = "All"
- accepted_filters = []
- for label, val in accepted_filter_labels_and_vals.items():
- args = {self.key: val}
- accepted_filters.append(GridColumnFilter(label, args))
- return accepted_filters
-
-
-class GridOperation:
- def __init__(
- self,
- label,
- key=None,
- condition=None,
- allow_multiple=True,
- allow_popup=True,
- target=None,
- url_args=None,
- async_compatible=False,
- confirm=None,
- global_operation=None,
- ):
- self.label = label
- self.key = key
- self.allow_multiple = allow_multiple
- self.allow_popup = allow_popup
- self.condition = condition
- self.target = target
- self.url_args = url_args
- self.async_compatible = async_compatible
- # if 'confirm' is set, then ask before completing the operation
- self.confirm = confirm
- # specify a general operation that acts on the full grid
- # this should be a function returning a dictionary with parameters
- # to pass to the URL, similar to GridColumn links:
- # global_operation=(lambda: dict(operation="download")
- self.global_operation = global_operation
-
- def get_url_args(self, item):
- if self.url_args:
- if callable(self.url_args):
- url_args = self.url_args(item)
- else:
- url_args = dict(self.url_args)
- url_args["id"] = item.id
- return url_args
- else:
- return dict(operation=self.label, id=item.id)
-
- def allowed(self, item):
- if self.condition:
- return bool(self.condition(item))
- else:
- return True
-
-
-class DisplayByUsernameAndSlugGridOperation(GridOperation):
- """Operation to display an item by username and slug."""
-
- def get_url_args(self, item):
- return {"action": "display_by_username_and_slug", "username": item.user.username, "slug": item.slug}
-
-
-class GridAction:
- def __init__(self, label=None, url_args=None, target=None):
- self.label = label
- self.url_args = url_args
- self.target = target
-
-
-class GridColumnFilter:
- def __init__(self, label, args=None):
- self.label = label
- self.args = args
-
- def get_url_args(self):
- rval = {}
- for k, v in self.args.items():
- rval[f"f-{k}"] = v
- return rval
-
-
-class Grid:
- """
- Specifies the content and format of a grid (data table).
- """
-
- title = ""
- model_class: Optional[type] = None
- show_item_checkboxes = False
- use_hide_message = True
- global_actions: List[GridAction] = []
- columns: List[GridColumn] = []
- operations: List[GridOperation] = []
- standard_filters: List[GridColumnFilter] = []
- # Any columns that are filterable (either standard or advanced) should have a default value set in the default filter.
- default_filter: Dict[str, str] = {}
- default_sort_key: Optional[str] = None
- use_paging = False
- num_rows_per_page = 25
- num_page_links = 10
- # Set preference names.
- cur_filter_pref_name = ".filter"
- cur_sort_key_pref_name = ".sort_key"
- legend = None
- info_text: Optional[str] = None
-
- def __init__(self):
- # Determine if any multiple row operations are defined
- self.has_multiple_item_operations = False
- for operation in self.operations:
- if operation.allow_multiple:
- self.has_multiple_item_operations = True
- break
-
- # If a column does not have a model class, set the column's model class
- # to be the grid's model class.
- for column in self.columns:
- if not column.model_class:
- column.model_class = self.model_class
-
- def __call__(self, trans, **kwargs):
- # Get basics.
- # FIXME: pretty sure this is only here to pass along, can likely be eliminated
- status = kwargs.get("status", None)
- message = kwargs.get("message", None)
- # Build a base filter and sort key that is the combination of the saved state and defaults.
- # Saved state takes preference over defaults.
- base_filter = {}
- if self.default_filter:
- # default_filter is a dictionary that provides a default set of filters based on the grid's columns.
- base_filter = self.default_filter.copy()
- base_sort_key = self.default_sort_key
- # Build initial query
- query = self.build_initial_query(trans, **kwargs)
- query = self.apply_query_filter(trans, query, **kwargs)
- # Maintain sort state in generated urls
- extra_url_args = {}
- # Determine whether use_default_filter flag is set.
- use_default_filter = False
- if use_default_filter_str := kwargs.get("use_default_filter"):
- use_default_filter = use_default_filter_str.lower() == "true"
- # Process filtering arguments to (a) build a query that represents the filter and (b) build a
- # dictionary that denotes the current filter.
- cur_filter_dict = {}
- for column in self.columns:
- if column.key:
- # Get the filter criterion for the column. Precedence is (a) if using default filter, only look there; otherwise,
- # (b) look in kwargs; and (c) look in base filter.
- column_filter = None
- if use_default_filter:
- if self.default_filter:
- column_filter = self.default_filter.get(column.key)
- elif f"f-{column.model_class.__name__}.{column.key}" in kwargs:
- # Queries that include table joins cannot guarantee unique column names. This problem is
- # handled by setting the column_filter value to ..
- column_filter = kwargs.get(f"f-{column.model_class.__name__}.{column.key}")
- elif f"f-{column.key}" in kwargs:
- column_filter = kwargs.get(f"f-{column.key}")
- elif column.key in base_filter:
- column_filter = base_filter.get(column.key)
-
- # Method (1) combines a mix of strings and lists of strings into a single string and (2) attempts to de-jsonify all strings.
- def loads_recurse(item):
- decoded_list = []
- if isinstance(item, str):
- try:
- # Not clear what we're decoding, so recurse to ensure that we catch everything.
- decoded_item = loads(item)
- if isinstance(decoded_item, list):
- decoded_list = loads_recurse(decoded_item)
- else:
- decoded_list = [str(decoded_item)]
- except ValueError:
- decoded_list = [str(item)]
- elif isinstance(item, list):
- for element in item:
- a_list = loads_recurse(element)
- decoded_list = decoded_list + a_list
- return decoded_list
-
- # If column filter found, apply it.
- if column_filter is not None:
- # TextColumns may have a mix of json and strings.
- if isinstance(column, TextColumn):
- column_filter = loads_recurse(column_filter)
- if len(column_filter) == 1:
- column_filter = column_filter[0]
- # Interpret ',' as a separator for multiple terms.
- if isinstance(column_filter, str) and column_filter.find(",") != -1:
- column_filter = column_filter.split(",")
-
- # Check if filter is empty
- if isinstance(column_filter, list):
- # Remove empty strings from filter list
- column_filter = [x for x in column_filter if x != ""]
- if len(column_filter) == 0:
- continue
- elif isinstance(column_filter, str):
- # If filter criterion is empty, do nothing.
- if column_filter == "":
- continue
-
- # Update query.
- query = column.filter(trans, trans.user, query, column_filter)
- # Upate current filter dict.
- # Column filters are rendered in various places, sanitize them all here.
- cur_filter_dict[column.key] = sanitize_text(column_filter)
- # Carry filter along to newly generated urls; make sure filter is a string so
- # that we can encode to UTF-8 and thus handle user input to filters.
- if isinstance(column_filter, list):
- # Filter is a list; process each item.
- extra_url_args[f"f-{column.key}"] = dumps(column_filter)
- else:
- # Process singleton filter.
- extra_url_args[f"f-{column.key}"] = column_filter
- # Process sort arguments.
- sort_key = None
- if "sort" in kwargs:
- sort_key = kwargs["sort"]
- elif base_sort_key:
- sort_key = base_sort_key
- if sort_key:
- ascending = not (sort_key.startswith("-"))
- # Queries that include table joins cannot guarantee unique column names. This problem is
- # handled by setting the column_filter value to ..
- table_name = None
- if sort_key.find(".") > -1:
- a_list = sort_key.split(".")
- if ascending:
- table_name = a_list[0]
- else:
- table_name = a_list[0][1:]
- column_name = a_list[1]
- elif ascending:
- column_name = sort_key
- else:
- column_name = sort_key[1:]
- # Sort key is a column key.
- for column in self.columns:
- if column.key and column.key.find(".") > -1:
- column_key = column.key.split(".")[1]
- else:
- column_key = column.key
- if (table_name is None or table_name == column.model_class.__name__) and column_key == column_name:
- query = column.sort(trans, query, ascending, column_name=column_name)
- break
- extra_url_args["sort"] = sort_key
- # There might be a current row
- current_item = self.get_current_item(trans, **kwargs)
- # Process page number.
- num_pages = None
- total_row_count_query = query # query without limit applied to get total number of rows.
- if self.use_paging:
- if "page" in kwargs:
- if kwargs["page"] == "all":
- page_num = 0
- else:
- page_num = int(kwargs["page"])
- else:
- page_num = 1
- if page_num == 0:
- num_pages = 1
- page_num = 1
- else:
- query = query.limit(self.num_rows_per_page).offset((page_num - 1) * self.num_rows_per_page)
- else:
- # Defaults.
- page_num = 1
- # There are some places in grid templates where it's useful for a grid
- # to have its current filter.
- self.cur_filter_dict = cur_filter_dict
-
- # Log grid view.
- context = str(self.__class__.__name__)
- params = cur_filter_dict.copy()
- params["sort"] = sort_key
-
- # Render grid.
- def url(*args, **kwargs):
- route_name = kwargs.pop("__route_name__", None)
- # Only include sort/filter arguments if not linking to another
- # page. This is a bit of a hack.
- if "action" in kwargs:
- new_kwargs = dict()
- else:
- new_kwargs = dict(extra_url_args)
- # Extend new_kwargs with first argument if found
- if len(args) > 0:
- new_kwargs.update(args[0])
- new_kwargs.update(kwargs)
- # We need to encode item ids
- if "id" in new_kwargs:
- id = new_kwargs["id"]
- if isinstance(id, list):
- new_kwargs["id"] = [trans.security.encode_id(i) for i in id]
- else:
- new_kwargs["id"] = trans.security.encode_id(id)
- # The url_for invocation *must* include a controller and action.
- if "controller" not in new_kwargs:
- new_kwargs["controller"] = trans.controller
- if "action" not in new_kwargs:
- new_kwargs["action"] = trans.action
- if route_name:
- return url_for(route_name, **new_kwargs)
- return url_for(**new_kwargs)
-
- self.use_panels = kwargs.get("use_panels", False) in [True, "True", "true"]
- self.advanced_search = kwargs.get("advanced_search", False) in [True, "True", "true"]
- # Currently, filling the template returns a str object; this requires decoding the string into a
- # unicode object within mako templates. What probably should be done is to return the template as
- # utf-8 unicode; however, this would require encoding the object as utf-8 before returning the grid
- # results via a controller method, which is require substantial changes. Hence, for now, return grid
- # as str.
- grid_config = {
- "title": self.title,
- "title_id": getattr(self, "title_id", None),
- "url_base": trans.request.path_url,
- "async_ops": [],
- "categorical_filters": {},
- "filters": cur_filter_dict,
- "sort_key": sort_key,
- "show_item_checkboxes": self.show_item_checkboxes
- or kwargs.get("show_item_checkboxes", "") in ["True", "true"],
- "cur_page_num": page_num,
- "num_page_links": self.num_page_links,
- "status": status,
- "message": restore_text(message),
- "global_actions": [],
- "operations": [],
- "items": [],
- "columns": [],
- "model_class": str(self.model_class),
- "use_paging": self.use_paging,
- "legend": self.legend,
- "current_item_id": False,
- "use_hide_message": self.use_hide_message,
- "default_filter_dict": self.default_filter,
- "advanced_search": self.advanced_search,
- "info_text": self.info_text,
- "url": url(dict()),
- "refresh_frames": kwargs.get("refresh_frames", []),
- }
- if current_item:
- grid_config["current_item_id"] = current_item.id
- for column in self.columns:
- extra = ""
- if column.sortable:
- if sort_key.endswith(column.key):
- if not sort_key.startswith("-"):
- extra = "↓"
- else:
- extra = "↑"
- grid_config["columns"].append(
- {
- "key": column.key,
- "visible": column.visible,
- "nowrap": column.nowrap,
- "attach_popup": column.attach_popup,
- "label_id_prefix": column.label_id_prefix,
- "sortable": column.sortable,
- "label": column.label,
- "filterable": column.filterable,
- "delayed": column.delayed,
- "is_text": isinstance(column, TextColumn),
- "extra": extra,
- }
- )
- for operation in self.operations:
- grid_config["operations"].append(
- {
- "allow_multiple": operation.allow_multiple,
- "allow_popup": operation.allow_popup,
- "target": operation.target,
- "label": operation.label,
- "confirm": operation.confirm,
- "href": url(**operation.url_args) if isinstance(operation.url_args, dict) else None,
- "global_operation": False,
- }
- )
- if operation.allow_multiple:
- grid_config["show_item_checkboxes"] = True
- if operation.global_operation:
- grid_config["global_operation"] = url(**(operation.global_operation()))
- for action in self.global_actions:
- grid_config["global_actions"].append(
- {"url_args": url(**action.url_args), "label": action.label, "target": action.target}
- )
- for operation in [op for op in self.operations if op.async_compatible]:
- grid_config["async_ops"].append(operation.label.lower())
- for column in self.columns:
- if column.filterable is not None and not isinstance(column, TextColumn):
- grid_config["categorical_filters"][column.key] = {
- filter.label: filter.args for filter in column.get_accepted_filters()
- }
- for item in query:
- item_dict = {
- "id": item.id,
- "encode_id": trans.security.encode_id(item.id),
- "link": [],
- "operation_config": {},
- "column_config": {},
- }
- for column in self.columns:
- if column.visible:
- link = column.get_link(trans, self, item)
- if link:
- link = url(**link)
- else:
- link = None
- target = column.target
- value = unicodify(column.get_value(trans, self, item))
- if value:
- value = value.replace("/", "//")
- item_dict["column_config"][column.label] = {"link": link, "value": value, "target": target}
- for operation in self.operations:
- item_dict["operation_config"][operation.label] = {
- "allowed": operation.allowed(item),
- "url_args": url(**operation.get_url_args(item)),
- "target": operation.target,
- }
- grid_config["items"].append(item_dict)
-
- if self.use_paging and num_pages is None:
- # TODO: it would be better to just return this as None, render, and fire
- # off a second request for this count I think.
- total_num_rows = total_row_count_query.count()
- num_pages = int(math.ceil(float(total_num_rows) / self.num_rows_per_page))
-
- grid_config["num_pages"] = num_pages
-
- trans.log_action(trans.get_user(), "grid.view", context, params)
- return grid_config
-
- def get_ids(self, **kwargs):
- id = []
- if "id" in kwargs:
- id = kwargs["id"]
- # Coerce ids to list
- if not isinstance(id, list):
- id = id.split(",")
- # Ensure ids are integers
- try:
- id = list(map(int, id))
- except Exception:
- decorators.error("Invalid id")
- return id
-
- # ---- Override these ----------------------------------------------------
- def handle_operation(self, trans, operation, ids, **kwargs):
- pass
-
- def get_current_item(self, trans, **kwargs):
- return None
-
- def build_initial_query(self, trans, **kwargs):
- return trans.sa_session.query(self.model_class)
-
- def apply_query_filter(self, trans, query, **kwargs):
- # Applies a database filter that holds for all items in the grid.
- # (gvk) Is this method necessary? Why not simply build the entire query,
- # including applying filters in the build_initial_query() method?
- return query
-
-
class GridData:
"""
Specifies the content a grid (data table).
@@ -1075,7 +81,7 @@ def __call__(self, trans, **kwargs):
offset = kwargs.get("offset", 0)
# Build initial query
- query = self.build_initial_query(trans, **kwargs)
+ query = trans.sa_session.query(self.model_class)
query = self.apply_query_filter(query, **kwargs)
# Process sort arguments.
@@ -1104,13 +110,3 @@ def __call__(self, trans, **kwargs):
row_dict[column.key] = value
grid_config["rows"].append(row_dict)
return grid_config
-
- # ---- Override these ----------------------------------------------------
- def handle_operation(self, trans, operation, ids, **kwargs):
- pass
-
- def get_current_item(self, trans, **kwargs):
- return None
-
- def build_initial_query(self, trans, **kwargs):
- return trans.sa_session.query(self.model_class)
diff --git a/lib/galaxy/webapps/galaxy/controllers/admin.py b/lib/galaxy/webapps/galaxy/controllers/admin.py
index 4f3a20a30d4b..08bef7a9bb24 100644
--- a/lib/galaxy/webapps/galaxy/controllers/admin.py
+++ b/lib/galaxy/webapps/galaxy/controllers/admin.py
@@ -245,7 +245,7 @@ def get_value(self, trans, grid, group):
grids.GridColumn("Name", key="name"),
UsersColumn("Users", key="users"),
RolesColumn("Roles", key="roles"),
- grids.DeletedColumn("Deleted", key="deleted", escape=False),
+ grids.GridColumn("Deleted", key="deleted", escape=False),
grids.GridColumn("Last Updated", key="update_time"),
]
@@ -281,7 +281,7 @@ def apply_query_filter(self, query, **kwargs):
class QuotaListGrid(grids.GridData):
- class AmountColumn(grids.TextColumn):
+ class AmountColumn(grids.GridColumn):
def get_value(self, trans, grid, quota):
return quota.operation + quota.display_amount
diff --git a/lib/galaxy/webapps/galaxy/controllers/forms.py b/lib/galaxy/webapps/galaxy/controllers/forms.py
index 4214d3797ce6..f62ef7c272cb 100644
--- a/lib/galaxy/webapps/galaxy/controllers/forms.py
+++ b/lib/galaxy/webapps/galaxy/controllers/forms.py
@@ -33,15 +33,15 @@
class FormsGrid(grids.GridData):
# Custom column types
- class NameColumn(grids.TextColumn):
+ class NameColumn(grids.GridColumn):
def get_value(self, trans, grid, form):
return form.latest_form.name
- class DescriptionColumn(grids.TextColumn):
+ class DescriptionColumn(grids.GridColumn):
def get_value(self, trans, grid, form):
return form.latest_form.desc
- class TypeColumn(grids.TextColumn):
+ class TypeColumn(grids.GridColumn):
def get_value(self, trans, grid, form):
return form.latest_form.type
diff --git a/lib/galaxy/webapps/galaxy/controllers/history.py b/lib/galaxy/webapps/galaxy/controllers/history.py
index 50cb04bbb20f..844945d285ff 100644
--- a/lib/galaxy/webapps/galaxy/controllers/history.py
+++ b/lib/galaxy/webapps/galaxy/controllers/history.py
@@ -1,13 +1,7 @@
import logging
from dateutil.parser import isoparse
-from markupsafe import escape
-from sqlalchemy import (
- false,
- select,
- true,
-)
-from sqlalchemy.orm import undefer
+from sqlalchemy import select
from galaxy import (
exceptions,
@@ -27,224 +21,20 @@
listify,
sanitize_text,
string_as_bool,
- unicodify,
)
from galaxy.web import (
expose_api_anonymous,
url_for,
)
-from galaxy.web.framework.helpers import (
- grids,
- iff,
- time_ago,
-)
from galaxy.webapps.base.controller import (
BaseUIController,
- ERROR,
- INFO,
SharableMixin,
- SUCCESS,
- WARNING,
)
from ..api import depends
log = logging.getLogger(__name__)
-class NameColumn(grids.TextColumn):
- def get_value(self, trans, grid, history):
- return escape(history.get_display_name())
-
-
-class HistoryListGrid(grids.Grid):
- # Custom column types
- class ItemCountColumn(grids.GridColumn):
- def get_value(self, trans, grid, history):
- return str(history.hid_counter - 1)
-
- class HistoryListNameColumn(NameColumn):
- def get_link(self, trans, grid, history):
- link = None
- if not history.deleted:
- link = dict(operation="Switch", id=history.id, use_panels=grid.use_panels, async_compatible=True)
- return link
-
- class StatusColumn(grids.GridColumn):
- def get_accepted_filters(self):
- """Returns a list of accepted filters for this column."""
- accepted_filter_labels_and_vals = {
- "active": "active",
- "deleted": "deleted",
- "archived": "archived",
- "all": "all",
- }
- accepted_filters = []
- for label, val in accepted_filter_labels_and_vals.items():
- args = {self.key: val}
- accepted_filters.append(grids.GridColumnFilter(label, args))
- return accepted_filters
-
- def filter(self, trans, user, query, column_filter):
- """Modify query to filter self.model_class by state."""
- if column_filter == "all":
- return query
- elif column_filter == "active":
- return query.filter(self.model_class.deleted == false(), self.model_class.archived == false())
- elif column_filter == "deleted":
- return query.filter(self.model_class.deleted == true())
- elif column_filter == "archived":
- return query.filter(self.model_class.archived == true())
-
- def get_value(self, trans, grid, history):
- if history == trans.history:
- return "current history"
- if history.purged:
- return "deleted permanently"
- elif history.deleted:
- return "deleted"
- elif history.archived:
- return "archived"
- return ""
-
- def sort(self, trans, query, ascending, column_name=None):
- if ascending:
- query = query.order_by(self.model_class.table.c.purged.asc(), self.model_class.update_time.desc())
- else:
- query = query.order_by(self.model_class.table.c.purged.desc(), self.model_class.update_time.desc())
- return query
-
- def build_initial_query(self, trans, **kwargs):
- # Override to preload sharing information used when fetching data for grid.
- query = super().build_initial_query(trans, **kwargs)
- query = query.options(undefer(self.model_class.users_shared_with_count))
- return query
-
- # Grid definition
- title = "Saved Histories"
- model_class = model.History
- default_sort_key = "-update_time"
- columns = [
- HistoryListNameColumn("Name", key="name", attach_popup=True, filterable="advanced"),
- ItemCountColumn("Items", key="item_count", sortable=False),
- grids.GridColumn("Datasets", key="datasets_by_state", sortable=False, nowrap=True, delayed=True),
- grids.IndividualTagsColumn(
- "Tags",
- key="tags",
- model_tag_association_class=model.HistoryTagAssociation,
- filterable="advanced",
- grid_name="HistoryListGrid",
- ),
- grids.SharingStatusColumn(
- "Sharing", key="sharing", filterable="advanced", sortable=False, use_shared_with_count=True
- ),
- grids.GridColumn("Size on Disk", key="disk_size", sortable=False, delayed=True),
- grids.GridColumn("Created", key="create_time", format=time_ago),
- grids.GridColumn("Last Updated", key="update_time", format=time_ago),
- StatusColumn("Status", key="status", filterable="advanced"),
- ]
- columns.append(
- grids.MulticolFilterColumn(
- "search history names and tags",
- cols_to_filter=[columns[0], columns[3]],
- key="free-text-search",
- visible=False,
- filterable="standard",
- )
- )
- global_actions = [grids.GridAction("Import history", dict(controller="", action="histories/import"))]
- operations = [
- grids.GridOperation(
- "Switch", allow_multiple=False, condition=(lambda item: not item.deleted), async_compatible=True
- ),
- grids.GridOperation("View", allow_multiple=False, url_args=dict(controller="", action="histories/view")),
- grids.GridOperation(
- "Share or Publish",
- allow_multiple=False,
- condition=(lambda item: not item.deleted),
- url_args=dict(controller="", action="histories/sharing"),
- ),
- grids.GridOperation(
- "Change Permissions",
- allow_multiple=False,
- condition=(lambda item: not item.deleted),
- url_args=dict(controller="", action="histories/permissions"),
- ),
- grids.GridOperation(
- "Copy", allow_multiple=False, condition=(lambda item: not item.deleted), async_compatible=False
- ),
- grids.GridOperation(
- "Rename",
- condition=(lambda item: not item.deleted),
- url_args=dict(controller="", action="histories/rename"),
- target="top",
- ),
- grids.GridOperation("Delete", condition=(lambda item: not item.deleted), async_compatible=True),
- grids.GridOperation(
- "Delete Permanently",
- condition=(lambda item: not item.purged),
- confirm="History contents will be removed from disk, this cannot be undone. Continue?",
- async_compatible=True,
- ),
- grids.GridOperation(
- "Undelete", condition=(lambda item: item.deleted and not item.purged), async_compatible=True
- ),
- ]
- standard_filters = [
- grids.GridColumnFilter("Active", args=dict(deleted=False)),
- grids.GridColumnFilter("Deleted", args=dict(deleted=True)),
- grids.GridColumnFilter("All", args=dict(deleted="All")),
- ]
- default_filter = dict(name="All", status="active", tags="All", sharing="All")
- num_rows_per_page = 15
- use_paging = True
- info_text = "Histories that have been deleted for more than a time period specified by the Galaxy administrator(s) may be permanently deleted."
-
- def get_current_item(self, trans, **kwargs):
- return trans.get_history()
-
- def apply_query_filter(self, trans, query, **kwargs):
- return query.filter_by(user=trans.user, importing=False)
-
-
-class SharedHistoryListGrid(grids.Grid):
- # Custom column types
- class DatasetsByStateColumn(grids.GridColumn):
- def get_value(self, trans, grid, history):
- rval = ""
- for state in ("ok", "running", "queued", "error"):
- total = sum(1 for d in history.active_datasets if d.state == state)
- if total:
- rval += f'{total}
'
- return rval
-
- class SharedByColumn(grids.GridColumn):
- def get_value(self, trans, grid, history):
- return escape(history.user.email)
-
- # Grid definition
- title = "Histories shared with you by others"
- model_class = model.History
- default_sort_key = "-update_time"
- columns = [
- grids.GridColumn("Name", key="name", attach_popup=True),
- DatasetsByStateColumn("Datasets", sortable=False),
- grids.GridColumn("Created", key="create_time", format=time_ago),
- grids.GridColumn("Last Updated", key="update_time", format=time_ago),
- SharedByColumn("Shared by", key="user_id"),
- ]
- operations = [
- grids.GridOperation("View", allow_multiple=False, url_args=dict(controller="", action="histories/view")),
- grids.GridOperation("Copy", allow_multiple=False),
- grids.GridOperation("Unshare", allow_multiple=False),
- ]
-
- def build_initial_query(self, trans, **kwargs):
- return trans.sa_session.query(self.model_class).join(self.model_class.users_shared_with)
-
- def apply_query_filter(self, trans, query, **kwargs):
- return query.filter(model.HistoryUserShareAssociation.user == trans.user)
-
-
class HistoryController(BaseUIController, SharableMixin, UsesAnnotations, UsesItemRatings):
history_manager: histories.HistoryManager = depends(histories.HistoryManager)
history_serializer: histories.HistorySerializer = depends(histories.HistorySerializer)
@@ -263,193 +53,6 @@ def list_as_xml(self, trans):
trans.response.set_content_type("text/xml")
return trans.fill_template("/history/list_as_xml.mako")
- # ......................................................................... lists
- stored_list_grid = HistoryListGrid()
- shared_list_grid = SharedHistoryListGrid()
-
- @web.legacy_expose_api
- @web.require_login("work with multiple histories")
- def list(self, trans, **kwargs):
- """List all available histories"""
- current_history = trans.get_history()
- message = kwargs.get("message")
- status = kwargs.get("status")
- if "operation" in kwargs:
- operation = kwargs["operation"].lower()
- history_ids = listify(kwargs.get("id", []))
- # Display no message by default
- status, message = None, None
- # Load the histories and ensure they all belong to the current user
- histories = []
- for history_id in history_ids:
- history = self.history_manager.get_owned(
- self.decode_id(history_id), trans.user, current_history=trans.history
- )
- if history:
- # Ensure history is owned by current user
- if history.user_id is not None and trans.user:
- assert trans.user.id == history.user_id, "History does not belong to current user"
- histories.append(history)
- else:
- log.warning("Invalid history id '%r' passed to list", history_id)
- if histories:
- if operation == "switch":
- status, message = self._list_switch(trans, histories)
- # Take action to update UI to reflect history switch. If
- # grid is using panels, it is standalone and hence a redirect
- # to root is needed; if grid is not using panels, it is nested
- # in the main Galaxy UI and refreshing the history frame
- # is sufficient.
- use_panels = kwargs.get("use_panels", False) == "True"
- if use_panels:
- return trans.response.send_redirect(url_for("/"))
- else:
- kwargs["refresh_frames"] = ["history"]
- elif operation in ("delete", "delete permanently"):
- status, message = self._list_delete(trans, histories, purge=(operation == "delete permanently"))
- if current_history in histories:
- # Deleted the current history, so a new, empty history was
- # created automatically, and we need to refresh the history frame
- kwargs["refresh_frames"] = ["history"]
- elif operation == "undelete":
- status, message = self._list_undelete(trans, histories)
-
- with transaction(trans.sa_session):
- trans.sa_session.commit()
- # Render the list view
- if message and status:
- kwargs["message"] = sanitize_text(message)
- kwargs["status"] = status
- return self.stored_list_grid(trans, **kwargs)
-
- def _list_delete(self, trans, histories, purge=False):
- """Delete histories"""
- n_deleted = 0
- deleted_current = False
- message_parts = []
- status = SUCCESS
- current_history = trans.get_history()
- for history in histories:
- try:
- if history.users_shared_with:
- raise exceptions.ObjectAttributeInvalidException(
- f"History ({history.name}) has been shared with others, unshare it before deleting it."
- )
- if purge:
- self.history_manager.purge(history, user=trans.user)
- else:
- self.history_manager.delete(history)
- if history == current_history:
- deleted_current = True
- except Exception as e:
- message_parts.append(unicodify(e))
- status = ERROR
- else:
- trans.log_event(f"History ({history.name}) marked as deleted")
- n_deleted += 1
-
- if n_deleted:
- part = "Deleted %d %s" % (n_deleted, iff(n_deleted != 1, "histories", "history"))
- if purge and trans.app.config.allow_user_dataset_purge:
- part += f" and removed {iff(n_deleted != 1, 'their', 'its')} dataset{iff(n_deleted != 1, 's', '')} from disk"
- elif purge:
- part += " but the datasets were not removed from disk because that feature is not enabled in this Galaxy instance"
- message_parts.append(f"{part}. ")
- if deleted_current:
- # if this history is the current history for this session,
- # - attempt to find the most recently used, undeleted history and switch to it.
- # - If no suitable recent history is found, create a new one and switch
- # note: this needs to come after commits above or will use an empty history that was deleted above
- not_deleted_or_purged = [model.History.deleted == false(), model.History.purged == false()]
- most_recent_history = self.history_manager.most_recent(user=trans.user, filters=not_deleted_or_purged)
- if most_recent_history:
- self.history_manager.set_current(trans, most_recent_history)
- else:
- trans.get_or_create_default_history()
- message_parts.append("Your active history was deleted, a new empty history is now active. ")
- status = INFO
- return (status, " ".join(message_parts))
-
- def _list_undelete(self, trans, histories):
- """Undelete histories"""
- n_undeleted = 0
- n_already_purged = 0
- for history in histories:
- if history.purged:
- n_already_purged += 1
- if history.deleted:
- history.deleted = False
- if not history.default_permissions:
- # For backward compatibility - for a while we were deleting all DefaultHistoryPermissions on
- # the history when we deleted the history. We are no longer doing this.
- # Need to add default DefaultHistoryPermissions in case they were deleted when the history was deleted
- default_action = trans.app.security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS
- private_user_role = trans.app.security_agent.get_private_user_role(history.user)
- default_permissions = {}
- default_permissions[default_action] = [private_user_role]
- trans.app.security_agent.history_set_default_permissions(history, default_permissions)
- n_undeleted += 1
- trans.log_event("History (%s) %d marked as undeleted" % (history.name, history.id))
- status = SUCCESS
- message_parts = []
- if n_undeleted:
- message_parts.append("Undeleted %d %s. " % (n_undeleted, iff(n_undeleted != 1, "histories", "history")))
- if n_already_purged:
- message_parts.append("%d histories have already been purged and cannot be undeleted." % n_already_purged)
- status = WARNING
- return status, "".join(message_parts)
-
- def _list_switch(self, trans, histories):
- """Switch to a new different history"""
- new_history = histories[0]
- galaxy_session = trans.get_galaxy_session()
- try:
- stmt = (
- select(trans.app.model.GalaxySessionToHistoryAssociation)
- .filter_by(session_id=galaxy_session.id, history_id=new_history.id)
- .limit(1)
- )
- association = trans.sa_session.scalars(stmt).first()
- except Exception:
- association = None
- new_history.add_galaxy_session(galaxy_session, association=association)
- trans.sa_session.add(new_history)
- with transaction(trans.sa_session):
- trans.sa_session.commit()
- trans.set_history(new_history)
- # No message
- return None, None
-
- @web.expose
- @web.json
- @web.require_login("work with shared histories")
- def list_shared(self, trans, **kwargs):
- """List histories shared with current user by others"""
- status = message = None
- if "operation" in kwargs:
- ids = listify(kwargs.get("id", []))
- operation = kwargs["operation"].lower()
- if operation == "unshare":
- if not ids:
- message = "Select a history to unshare"
- status = "error"
- for id in ids:
- # No need to check security, association below won't yield a
- # hit if this user isn't having the history shared with her.
- history = self.history_manager.by_id(self.decode_id(id))
- # Current user is the user with which the histories were shared
- stmt = select(trans.app.model.HistoryUserShareAssociation).filter_by(
- user=trans.user, history=history
- )
- association = trans.sa_session.execute(select(stmt)).scalar_one()
- trans.sa_session.delete(association)
- with transaction(trans.sa_session):
- trans.sa_session.commit()
- message = "Unshared %d shared histories" % len(ids)
- status = "done"
- # Render the list view
- return self.shared_list_grid(trans, status=status, message=message, **kwargs)
-
@web.expose
def as_xml(self, trans, id=None, show_deleted=None, show_hidden=None):
"""
diff --git a/lib/galaxy/webapps/galaxy/controllers/page.py b/lib/galaxy/webapps/galaxy/controllers/page.py
index 6b65c7917e77..0d4d11b993e6 100644
--- a/lib/galaxy/webapps/galaxy/controllers/page.py
+++ b/lib/galaxy/webapps/galaxy/controllers/page.py
@@ -1,16 +1,5 @@
-from markupsafe import escape
-from sqlalchemy import (
- false,
- true,
-)
-from sqlalchemy.orm import (
- joinedload,
- undefer,
-)
-
from galaxy import (
model,
- util,
web,
)
from galaxy.managers.hdas import HDAManager
@@ -20,7 +9,6 @@
)
from galaxy.managers.pages import (
get_page as get_page_,
- get_shared_pages,
page_exists,
PageManager,
)
@@ -32,14 +20,7 @@
from galaxy.schema.schema import CreatePagePayload
from galaxy.structured_app import StructuredApp
from galaxy.util.sanitize_html import sanitize_html
-from galaxy.web import (
- error,
- url_for,
-)
-from galaxy.web.framework.helpers import (
- grids,
- time_ago,
-)
+from galaxy.web import error
from galaxy.webapps.base.controller import (
BaseUIController,
SharableMixin,
@@ -49,317 +30,8 @@
from galaxy.webapps.galaxy.api import depends
-def format_bool(b):
- if b:
- return "yes"
- else:
- return ""
-
-
-class PageListGrid(grids.Grid):
- # Custom column.
- class URLColumn(grids.PublicURLColumn):
- def get_value(self, trans, grid, item):
- return url_for(
- controller="page", action="display_by_username_and_slug", username=item.user.username, slug=item.slug
- )
-
- # Grid definition
- use_panels = True
- title = "Pages"
- model_class = model.Page
- default_filter = {"published": "All", "tags": "All", "title": "All", "sharing": "All"}
- default_sort_key = "-update_time"
- columns = [
- grids.TextColumn(
- "Title",
- key="title",
- attach_popup=True,
- filterable="advanced",
- link=(
- lambda item: dict(action="display_by_username_and_slug", username=item.user.username, slug=item.slug)
- ),
- ),
- URLColumn("Permalink"),
- grids.OwnerAnnotationColumn(
- "Annotation",
- key="annotation",
- model_annotation_association_class=model.PageAnnotationAssociation,
- filterable="advanced",
- ),
- grids.IndividualTagsColumn(
- "Tags",
- key="tags",
- model_tag_association_class=model.PageTagAssociation,
- filterable="advanced",
- grid_name="PageListGrid",
- ),
- grids.SharingStatusColumn("Sharing", key="sharing", filterable="advanced", sortable=False),
- grids.GridColumn("Created", key="create_time", format=time_ago),
- grids.GridColumn("Last Updated", key="update_time", format=time_ago),
- ]
- columns.append(
- grids.MulticolFilterColumn(
- "Search",
- cols_to_filter=[columns[0], columns[2]],
- key="free-text-search",
- visible=False,
- filterable="standard",
- )
- )
- global_actions = [grids.GridAction("Add new page", dict(controller="", action="pages/create"))]
- operations = [
- grids.DisplayByUsernameAndSlugGridOperation("View", allow_multiple=False),
- grids.GridOperation("Edit content", allow_multiple=False, url_args=dict(controller="", action="pages/editor")),
- grids.GridOperation("Edit attributes", allow_multiple=False, url_args=dict(controller="", action="pages/edit")),
- grids.GridOperation(
- "Share or Publish",
- allow_multiple=False,
- condition=(lambda item: not item.deleted),
- url_args=dict(controller="", action="pages/sharing"),
- ),
- grids.GridOperation("Delete", confirm="Are you sure you want to delete this page?"),
- ]
-
- def apply_query_filter(self, trans, query, **kwargs):
- return query.filter_by(user=trans.user, deleted=False)
-
-
-class PageAllPublishedGrid(grids.Grid):
- # Grid definition
- use_panels = True
- title = "Published Pages"
- model_class = model.Page
- default_sort_key = "update_time"
- default_filter = dict(title="All", username="All")
- columns = [
- grids.PublicURLColumn("Title", key="title", filterable="advanced"),
- grids.OwnerAnnotationColumn(
- "Annotation",
- key="annotation",
- model_annotation_association_class=model.PageAnnotationAssociation,
- filterable="advanced",
- ),
- grids.OwnerColumn("Owner", key="username", model_class=model.User, filterable="advanced"),
- grids.CommunityRatingColumn("Community Rating", key="rating"),
- grids.CommunityTagsColumn(
- "Community Tags",
- key="tags",
- model_tag_association_class=model.PageTagAssociation,
- filterable="advanced",
- grid_name="PageAllPublishedGrid",
- ),
- grids.ReverseSortColumn("Last Updated", key="update_time", format=time_ago),
- ]
- columns.append(
- grids.MulticolFilterColumn(
- "Search title, annotation, owner, and tags",
- cols_to_filter=[columns[0], columns[1], columns[2], columns[4]],
- key="free-text-search",
- visible=False,
- filterable="standard",
- )
- )
-
- def build_initial_query(self, trans, **kwargs):
- # See optimization description comments and TODO for tags in matching public histories query.
- return (
- trans.sa_session.query(self.model_class)
- .join("user")
- .filter(model.User.deleted == false())
- .options(
- joinedload(self.model_class.user).load_only(self.model_class.username),
- joinedload(self.model_class.annotations),
- undefer(self.model_class.average_rating),
- )
- )
-
- def apply_query_filter(self, trans, query, **kwargs):
- return query.filter(self.model_class.deleted == false()).filter(self.model_class.published == true())
-
-
-class ItemSelectionGrid(grids.Grid):
- """Base class for pages' item selection grids."""
-
- # Custom columns.
- class NameColumn(grids.TextColumn):
- def get_value(self, trans, grid, item):
- if hasattr(item, "get_display_name"):
- return escape(item.get_display_name())
- else:
- return escape(item.name)
-
- # Grid definition.
- show_item_checkboxes = True
- default_filter = {"deleted": "False", "sharing": "All"}
- default_sort_key = "-update_time"
- use_paging = True
- num_rows_per_page = 10
-
- def apply_query_filter(self, trans, query, **kwargs):
- return query.filter_by(user=trans.user)
-
-
-class HistorySelectionGrid(ItemSelectionGrid):
- """Grid for selecting histories."""
-
- # Grid definition.
- title = "Saved Histories"
- model_class = model.History
- columns = [
- ItemSelectionGrid.NameColumn("Name", key="name", filterable="advanced"),
- grids.IndividualTagsColumn(
- "Tags", key="tags", model_tag_association_class=model.HistoryTagAssociation, filterable="advanced"
- ),
- grids.GridColumn("Last Updated", key="update_time", format=time_ago),
- # Columns that are valid for filtering but are not visible.
- grids.DeletedColumn("Deleted", key="deleted", visible=False, filterable="advanced"),
- grids.SharingStatusColumn("Sharing", key="sharing", filterable="advanced", sortable=False, visible=False),
- ]
- columns.append(
- grids.MulticolFilterColumn(
- "Search",
- cols_to_filter=[columns[0], columns[1]],
- key="free-text-search",
- visible=False,
- filterable="standard",
- )
- )
-
- def apply_query_filter(self, trans, query, **kwargs):
- return query.filter_by(user=trans.user, purged=False)
-
-
-class HistoryDatasetAssociationSelectionGrid(ItemSelectionGrid):
- """Grid for selecting HDAs."""
-
- # Grid definition.
- title = "Saved Datasets"
- model_class = model.HistoryDatasetAssociation
- columns = [
- ItemSelectionGrid.NameColumn("Name", key="name", filterable="advanced"),
- grids.IndividualTagsColumn(
- "Tags",
- key="tags",
- model_tag_association_class=model.HistoryDatasetAssociationTagAssociation,
- filterable="advanced",
- ),
- grids.GridColumn("Last Updated", key="update_time", format=time_ago),
- # Columns that are valid for filtering but are not visible.
- grids.DeletedColumn("Deleted", key="deleted", visible=False, filterable="advanced"),
- grids.SharingStatusColumn("Sharing", key="sharing", filterable="advanced", sortable=False, visible=False),
- ]
- columns.append(
- grids.MulticolFilterColumn(
- "Search",
- cols_to_filter=[columns[0], columns[1]],
- key="free-text-search",
- visible=False,
- filterable="standard",
- )
- )
-
- def apply_query_filter(self, trans, query, **kwargs):
- # To filter HDAs by user, need to join HDA and History table and then filter histories by user. This is necessary because HDAs do not have
- # a user relation.
- return query.select_from(model.HistoryDatasetAssociation.table.join(model.History.table)).filter(
- model.History.user == trans.user
- )
-
-
-class WorkflowSelectionGrid(ItemSelectionGrid):
- """Grid for selecting workflows."""
-
- # Grid definition.
- title = "Saved Workflows"
- model_class = model.StoredWorkflow
- columns = [
- ItemSelectionGrid.NameColumn("Name", key="name", filterable="advanced"),
- grids.IndividualTagsColumn(
- "Tags", key="tags", model_tag_association_class=model.StoredWorkflowTagAssociation, filterable="advanced"
- ),
- grids.GridColumn("Last Updated", key="update_time", format=time_ago),
- # Columns that are valid for filtering but are not visible.
- grids.DeletedColumn("Deleted", key="deleted", visible=False, filterable="advanced"),
- grids.SharingStatusColumn("Sharing", key="sharing", filterable="advanced", sortable=False, visible=False),
- ]
- columns.append(
- grids.MulticolFilterColumn(
- "Search",
- cols_to_filter=[columns[0], columns[1]],
- key="free-text-search",
- visible=False,
- filterable="standard",
- )
- )
-
-
-class PageSelectionGrid(ItemSelectionGrid):
- """Grid for selecting pages."""
-
- # Grid definition.
- title = "Saved Pages"
- model_class = model.Page
- columns = [
- grids.TextColumn("Title", key="title", filterable="advanced"),
- grids.IndividualTagsColumn(
- "Tags", key="tags", model_tag_association_class=model.PageTagAssociation, filterable="advanced"
- ),
- grids.GridColumn("Last Updated", key="update_time", format=time_ago),
- # Columns that are valid for filtering but are not visible.
- grids.DeletedColumn("Deleted", key="deleted", visible=False, filterable="advanced"),
- grids.SharingStatusColumn("Sharing", key="sharing", filterable="advanced", sortable=False, visible=False),
- ]
- columns.append(
- grids.MulticolFilterColumn(
- "Search",
- cols_to_filter=[columns[0], columns[1]],
- key="free-text-search",
- visible=False,
- filterable="standard",
- )
- )
-
-
-class VisualizationSelectionGrid(ItemSelectionGrid):
- """Grid for selecting visualizations."""
-
- # Grid definition.
- title = "Saved Visualizations"
- model_class = model.Visualization
- columns = [
- grids.TextColumn("Title", key="title", filterable="advanced"),
- grids.TextColumn("Type", key="type"),
- grids.IndividualTagsColumn(
- "Tags",
- key="tags",
- model_tag_association_class=model.VisualizationTagAssociation,
- filterable="advanced",
- grid_name="VisualizationListGrid",
- ),
- grids.SharingStatusColumn("Sharing", key="sharing", filterable="advanced", sortable=False),
- grids.GridColumn("Last Updated", key="update_time", format=time_ago),
- ]
- columns.append(
- grids.MulticolFilterColumn(
- "Search",
- cols_to_filter=[columns[0], columns[2]],
- key="free-text-search",
- visible=False,
- filterable="standard",
- )
- )
-
-
# Adapted from the _BaseHTMLProcessor class of https://github.com/kurtmckee/feedparser
class PageController(BaseUIController, SharableMixin, UsesStoredWorkflowMixin, UsesVisualizationMixin, UsesItemRatings):
- _page_list = PageListGrid()
- _all_published_list = PageAllPublishedGrid()
- _history_selection_grid = HistorySelectionGrid()
- _workflow_selection_grid = WorkflowSelectionGrid()
- _datasets_selection_grid = HistoryDatasetAssociationSelectionGrid()
- _page_selection_grid = PageSelectionGrid()
- _visualization_selection_grid = VisualizationSelectionGrid()
page_manager: PageManager = depends(PageManager)
history_manager: HistoryManager = depends(HistoryManager)
history_serializer: HistorySerializer = depends(HistorySerializer)
@@ -370,43 +42,6 @@ class PageController(BaseUIController, SharableMixin, UsesStoredWorkflowMixin, U
def __init__(self, app: StructuredApp):
super().__init__(app)
- @web.expose
- @web.json
- @web.require_login()
- def list(self, trans, *args, **kwargs):
- """List user's pages."""
- # Handle operation
- if "operation" in kwargs and "id" in kwargs:
- session = trans.sa_session
- operation = kwargs["operation"].lower()
- ids = util.listify(kwargs["id"])
- for id in ids:
- if operation == "delete":
- item = session.get(model.Page, self.decode_id(id))
- self.security_check(trans, item, check_ownership=True)
- item.deleted = True
- with transaction(session):
- session.commit()
-
- # Build grid dictionary.
- grid = self._page_list(trans, *args, **kwargs)
- grid["shared_by_others"] = self._get_shared(trans)
- return grid
-
- @web.expose
- @web.json
- def list_published(self, trans, *args, **kwargs):
- grid = self._all_published_list(trans, *args, **kwargs)
- grid["shared_by_others"] = self._get_shared(trans)
- return grid
-
- def _get_shared(self, trans):
- """Identify shared pages"""
- shared_by_others = get_shared_pages(trans.sa_session, trans.get_user())
- return [
- {"username": p.page.user.username, "slug": p.page.slug, "title": p.page.title} for p in shared_by_others
- ]
-
@web.expose_api
@web.require_login("create pages")
def create(self, trans, payload=None, **kwd):
@@ -560,41 +195,6 @@ def display_by_username_and_slug(self, trans, username, slug, **kwargs):
)
)
- @web.expose
- @web.json
- @web.require_login("select a history from saved histories")
- def list_histories_for_selection(self, trans, **kwargs):
- """Returns HTML that enables a user to select one or more histories."""
- return self._history_selection_grid(trans, **kwargs)
-
- @web.expose
- @web.json
- @web.require_login("select a workflow from saved workflows")
- def list_workflows_for_selection(self, trans, **kwargs):
- """Returns HTML that enables a user to select one or more workflows."""
- return self._workflow_selection_grid(trans, **kwargs)
-
- @web.expose
- @web.json
- @web.require_login("select a visualization from saved visualizations")
- def list_visualizations_for_selection(self, trans, **kwargs):
- """Returns HTML that enables a user to select one or more visualizations."""
- return self._visualization_selection_grid(trans, **kwargs)
-
- @web.expose
- @web.json
- @web.require_login("select a page from saved pages")
- def list_pages_for_selection(self, trans, **kwargs):
- """Returns HTML that enables a user to select one or more pages."""
- return self._page_selection_grid(trans, **kwargs)
-
- @web.expose
- @web.json
- @web.require_login("select a dataset from saved datasets")
- def list_datasets_for_selection(self, trans, **kwargs):
- """Returns HTML that enables a user to select one or more datasets."""
- return self._datasets_selection_grid(trans, **kwargs)
-
def get_page(self, trans, id, check_ownership=True, check_accessible=False):
"""Get a page from the database by id."""
# Load history from database
@@ -604,6 +204,3 @@ def get_page(self, trans, id, check_ownership=True, check_accessible=False):
error("Page not found")
else:
return self.security_check(trans, page, check_ownership, check_accessible)
-
- def get_item(self, trans, id):
- return self.get_page(trans, id)
diff --git a/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py
index 8cf8d5f629bc..2d2eec42e5d2 100644
--- a/lib/galaxy/webapps/galaxy/controllers/workflow.py
+++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py
@@ -3,12 +3,7 @@
from markupsafe import escape
from sqlalchemy import desc
-from sqlalchemy.orm import (
- joinedload,
- lazyload,
- undefer,
-)
-from sqlalchemy.sql import expression
+from sqlalchemy.orm import joinedload
from galaxy import (
model,
@@ -26,10 +21,6 @@
from galaxy.util import FILENAME_VALID_CHARS
from galaxy.util.sanitize_html import sanitize_html
from galaxy.web import url_for
-from galaxy.web.framework.helpers import (
- grids,
- time_ago,
-)
from galaxy.webapps.base.controller import (
BaseUIController,
SharableMixin,
@@ -44,131 +35,6 @@
log = logging.getLogger(__name__)
-class StoredWorkflowListGrid(grids.Grid):
- class StepsColumn(grids.GridColumn):
- def get_value(self, trans, grid, workflow):
- return len(workflow.latest_workflow.steps)
-
- # Grid definition
- use_panels = True
- title = "Saved Workflows"
- model_class = model.StoredWorkflow
- default_filter = {"name": "All", "tags": "All"}
- default_sort_key = "-update_time"
- columns = [
- grids.TextColumn("Name", key="name", attach_popup=True, filterable="advanced"),
- grids.IndividualTagsColumn(
- "Tags",
- "tags",
- model_tag_association_class=model.StoredWorkflowTagAssociation,
- filterable="advanced",
- grid_name="StoredWorkflowListGrid",
- ),
- StepsColumn("Steps"),
- grids.GridColumn("Created", key="create_time", format=time_ago),
- grids.GridColumn("Last Updated", key="update_time", format=time_ago),
- ]
- columns.append(
- grids.MulticolFilterColumn(
- "Search",
- cols_to_filter=[columns[0], columns[1]],
- key="free-text-search",
- visible=False,
- filterable="standard",
- )
- )
- operations = [
- grids.GridOperation(
- "Edit", allow_multiple=False, condition=(lambda item: not item.deleted), async_compatible=False
- ),
- grids.GridOperation("Run", condition=(lambda item: not item.deleted), async_compatible=False),
- grids.GridOperation("Copy", condition=(lambda item: not item.deleted), async_compatible=False),
- grids.GridOperation("Rename", condition=(lambda item: not item.deleted), async_compatible=False),
- grids.GridOperation("Sharing", condition=(lambda item: not item.deleted), async_compatible=False),
- grids.GridOperation("Delete", condition=(lambda item: item.deleted), async_compatible=True),
- ]
-
- def apply_query_filter(self, trans, query, **kwargs):
- return query.filter_by(user=trans.user, deleted=False)
-
-
-class StoredWorkflowAllPublishedGrid(grids.Grid):
- title = "Published Workflows"
- model_class = model.StoredWorkflow
- default_sort_key = "update_time"
- default_filter = dict(public_url="All", username="All", tags="All")
- columns = [
- grids.PublicURLColumn("Name", key="name", filterable="advanced", attach_popup=True),
- grids.OwnerAnnotationColumn(
- "Annotation",
- key="annotation",
- model_annotation_association_class=model.StoredWorkflowAnnotationAssociation,
- filterable="advanced",
- ),
- grids.OwnerColumn("Owner", key="username", model_class=model.User, filterable="advanced"),
- grids.CommunityRatingColumn("Community Rating", key="rating"),
- grids.CommunityTagsColumn(
- "Community Tags",
- key="tags",
- model_tag_association_class=model.StoredWorkflowTagAssociation,
- filterable="advanced",
- grid_name="PublicWorkflowListGrid",
- ),
- grids.ReverseSortColumn("Last Updated", key="update_time", format=time_ago),
- ]
- columns.append(
- grids.MulticolFilterColumn(
- "Search name, annotation, owner, and tags",
- cols_to_filter=[columns[0], columns[1], columns[2], columns[4]],
- key="free-text-search",
- visible=False,
- filterable="standard",
- )
- )
- operations = [
- grids.GridOperation(
- "Run",
- condition=(lambda item: not item.deleted),
- allow_multiple=False,
- url_args=dict(controller="workflows", action="run"),
- ),
- grids.GridOperation(
- "Import", condition=(lambda item: not item.deleted), allow_multiple=False, url_args=dict(action="imp")
- ),
- grids.GridOperation(
- "Save as File",
- condition=(lambda item: not item.deleted),
- allow_multiple=False,
- url_args=dict(action="export_to_file"),
- ),
- ]
- num_rows_per_page = 50
- use_paging = True
-
- def build_initial_query(self, trans, **kwargs):
- # See optimization description comments and TODO for tags in matching public histories query.
- # In addition to that - be sure to lazyload the latest_workflow - it isn't needed and it causes all
- # of its steps to be eagerly loaded.
- return (
- trans.sa_session.query(self.model_class)
- .join(self.model_class.user)
- .options(
- lazyload(self.model_class.latest_workflow),
- joinedload(self.model_class.user).load_only(model.User.username),
- joinedload(self.model_class.annotations),
- undefer(self.model_class.average_rating),
- )
- )
-
- def apply_query_filter(self, trans, query, **kwargs):
- # A public workflow is published, has a slug, and is not deleted.
- return (
- query.filter(self.model_class.published == expression.true())
- .filter(self.model_class.slug.isnot(None))
- .filter(self.model_class.deleted == expression.false())
- )
-
-
# Simple HTML parser to get all content in a single tag.
class SingleTagContentsParser(HTMLParser):
def __init__(self, target_tag):
@@ -189,29 +55,8 @@ def handle_data(self, text):
class WorkflowController(BaseUIController, SharableMixin, UsesStoredWorkflowMixin, UsesItemRatings):
- stored_list_grid = StoredWorkflowListGrid()
- published_list_grid = StoredWorkflowAllPublishedGrid()
slug_builder = SlugBuilder()
- @web.expose
- @web.require_login("use Galaxy workflows")
- def list_grid(self, trans, **kwargs):
- """List user's stored workflows."""
- # status = message = None
- if "operation" in kwargs:
- operation = kwargs["operation"].lower()
- if operation == "rename":
- return self.rename(trans, **kwargs)
- workflow_ids = util.listify(kwargs.get("id", []))
- if operation == "sharing":
- return self.sharing(trans, id=workflow_ids)
- return self.stored_list_grid(trans, **kwargs)
-
- @web.expose
- @web.json
- def list_published(self, trans, **kwargs):
- return self.published_list_grid(trans, **kwargs)
-
@web.expose
def display_by_username_and_slug(self, trans, username, slug, format="html", **kwargs):
"""
@@ -647,6 +492,3 @@ def build_from_current_history(
f'Workflow "{escape(workflow_name)}" created from current history. '
f'You can edit or run the workflow.'
)
-
- def get_item(self, trans, id):
- return self.get_stored_workflow(trans, id)