Merge pull request #14630 from guerler/remove_display_structured

Remove outdated display structured history view
This commit is contained in:
Björn Grüning
2022-09-16 15:10:14 +02:00
committed by GitHub
11 changed files with 0 additions and 607 deletions
-164
View File
@@ -1,164 +0,0 @@
<template>
<div>
<!-- eslint-disable-next-line vue/require-v-for-key -->
<div v-for="error in errorMessages">
<div class="alert alert-danger" role="alert">{{ error }}</div>
</div>
<div v-html="historyTemplate"></div>
</div>
</template>
<script>
import $ from "jquery";
import { getGalaxyInstance } from "app";
import { getAppRoot } from "onload/loadConfig";
import axios from "axios";
import HDAModel from "mvc/history/hda-model";
import HDAListItemEdit from "mvc/history/hda-li-edit";
export default {
props: {
id: {
type: String,
required: false,
},
},
data() {
return {
historyTemplate: "",
historyJSON: {},
// TODO: Error message standardization -- use bootstrap-vue component or the like.
errorMessages: [],
};
},
created: function () {
const historyId = this.id;
let url = "";
if (historyId !== undefined && historyId !== "" && historyId !== null) {
url = getAppRoot() + "history/display_structured?id=" + historyId;
} else {
url = getAppRoot() + "history/display_structured";
}
this.ajaxCall(url);
},
updated: function () {
this.makeHistoryView(this.historyJSON);
},
methods: {
ajaxCall: function (url) {
axios
.get(url)
.then((response) => {
this._updateHistoryData(response);
})
.catch((e) => {
console.error(e);
this.errorMessages.push(
"Error fetching history data -- reload the page to retry this request. Please contact an administrator if the problem persists."
);
});
},
_updateHistoryData: function (response) {
const historyItems = response.data;
this.historyTemplate = historyItems.template;
this.historyJSON = historyItems.history_json;
},
makeHistoryView: function (historyDict) {
window.hdas = historyDict.map((hda) => {
const Galaxy = getGalaxyInstance();
return new HDAListItemEdit.HDAListItemEdit({
model: new HDAModel.HistoryDatasetAssociation(hda),
el: $("#hda-" + hda.id),
linkTarget: "galaxy_main",
purgeAllowed: Galaxy.config.allow_user_dataset_purge,
logger: Galaxy.logger,
}).render(0);
});
// toggle the body section of each item in the structure
$(function () {
$(".workflow, .tool").each((index, element) => {
const body = $(element).children(".body");
$(element)
.children(".header")
.click((e) => {
body.toggle();
})
.addClass("clickable");
});
});
},
},
};
</script>
<style>
.bold {
font-weight: bold;
}
.light {
font-weight: lighter;
color: grey;
}
.right-aligned {
text-align: right;
}
.clickable {
cursor: pointer;
}
.workflow {
border: solid gray 1px;
margin-bottom: 1%;
}
.workflow > .header {
background: lightgray;
padding: 5px 10px;
}
.workflow > .light {
color: gray;
}
.workflow > .body {
border-top: solid gray 1px;
}
.workflow > .body > .tool {
border: 0px;
margin: 0;
}
div.tool {
margin-bottom: 5px;
border: 1px solid gray;
}
.tool > .header {
padding: 5px 10px;
}
.job-inputs {
margin: 0px 6px 0px 6px;
text-align: left;
}
.job-inputs td:nth-child(1) {
text-align: right;
font-weight: lighter;
color: gray;
}
.job-inputs td:nth-child(1):after {
content: ":";
}
.job-inputs td:nth-child(2) {
padding-left: 4px;
}
.job-inputs-show {
float: right;
}
.copied-from {
border: 1px solid lightgrey;
border-width: 1px 1px 0px 1px;
margin-bottom: 5px;
}
.copied-from .header {
border-bottom: 1px solid lightgrey;
padding: 5px;
}
.copied-from .header .bold,
.copied-from .header a {
color: #888;
}
</style>
@@ -64,14 +64,6 @@
<span v-localize>Resume Paused Jobs</span>
</b-dropdown-item>
<b-dropdown-item
data-description="show structure"
:title="l('Show Detailed Structure View of History')"
@click="$router.push('/histories/show_structure')">
<Icon fixed-width icon="code-branch" class="mr-1" />
<span v-localize>Show Structure</span>
</b-dropdown-item>
<b-dropdown-divider></b-dropdown-divider>
<b-dropdown-item
@@ -136,16 +128,6 @@
<Icon fixed-width icon="lock" class="mr-1" />
<span v-localize>Make Private</span>
</b-dropdown-item>
<b-dropdown-divider></b-dropdown-divider>
<b-dropdown-item
data-description="switch to legacy history view"
:title="l('Switch to Legacy History Panel')"
@click="switchToLegacyHistoryPanel">
<Icon fixed-width class="mr-1" icon="arrow-up" />
<span v-localize>Return to legacy panel</span>
</b-dropdown-item>
</b-dropdown>
</b-button-group>
</nav>
@@ -186,7 +168,6 @@
<script>
import { legacyNavigationMixin } from "components/plugins/legacyNavigation";
import { switchToLegacyHistoryPanel } from "components/History/adapters/betaToggle";
import CopyModal from "components/History/Modals/CopyModal";
import SelectorModal from "components/History/Modals/SelectorModal";
import { mapGetters } from "vuex";
@@ -207,7 +188,6 @@ export default {
...mapGetters("user", ["currentUser"]),
},
methods: {
switchToLegacyHistoryPanel,
userTitle(title) {
if (this.currentUser.isAnonymous) {
return this.l("Log in to") + " " + this.l(title);
@@ -1,21 +0,0 @@
const USE_LEGACY_HISTORY_STORAGE_KEY = "useLegacyHistory";
export const shouldUseLegacyHistory = (instanceDefaultUseLegacyHistory) => {
// If there's a value set, respect it. Otherwise get instance default from config.
const userPrefLegacyHistory = sessionStorage.getItem(USE_LEGACY_HISTORY_STORAGE_KEY);
if (userPrefLegacyHistory != null) {
return userPrefLegacyHistory === "1";
} else {
return instanceDefaultUseLegacyHistory;
}
};
export const switchToLegacyHistoryPanel = () => {
sessionStorage.setItem(USE_LEGACY_HISTORY_STORAGE_KEY, 1);
location.reload();
};
export const switchToBetaHistoryPanel = () => {
sessionStorage.setItem(USE_LEGACY_HISTORY_STORAGE_KEY, 0);
location.reload();
};
-8
View File
@@ -23,7 +23,6 @@ import DatasetDetails from "components/DatasetInformation/DatasetDetails";
import DatasetError from "components/DatasetInformation/DatasetError";
import DatasetList from "components/Dataset/DatasetList";
import AvailableDatatypes from "components/AvailableDatatypes/AvailableDatatypes";
import DisplayStructured from "components/DisplayStructured";
import FormGeneric from "components/Form/FormGeneric";
import Grid from "components/Grid/Grid";
import GridShared from "components/Grid/GridShared";
@@ -196,13 +195,6 @@ export function getRouter(Galaxy) {
component: HistoryMultipleView,
props: true,
},
{
path: "histories/show_structure",
component: DisplayStructured,
props: (route) => ({
id: route.query.id,
}),
},
{
path: "histories/:historyId/export",
component: HistoryExport,
-8
View File
@@ -6,7 +6,6 @@ import _l from "utils/localization";
import PopupMenu from "mvc/ui/popup-menu";
import historyCopyDialog from "mvc/history/copy-dialog";
import Webhooks from "mvc/webhooks";
import { switchToBetaHistoryPanel } from "../../components/History/adapters/betaToggle";
// ============================================================================
var menu = [
@@ -164,13 +163,6 @@ var menu = [
anon: false,
header: true,
},
{
html: _l("Use Beta History Panel"),
anon: false,
func: function () {
switchToBetaHistoryPanel();
},
},
];
// Webhooks
@@ -279,10 +279,6 @@ history_panel:
options_menu_item:
type: sizzle
selector: '.history-options-button-menu > a:contains("${option_label}")'
options_show_history_structure:
type: xpath
selector: '//a[contains(text(), "Show Structure")]'
options_show_export_history_to_file: 'a[data-description="export to file"]'
options_use_beta_history:
type: xpath
@@ -307,14 +303,6 @@ history_panel:
tooltip_name: 'Rename history...'
new_name: 'Unnamed history'
history_structure:
selectors:
_: '.tool'
header: '.tool > .header.clickable'
dataset: '.tool div.list-item.dataset.history-content'
details: '.tool .details'
edit_dataset_attributes:
selectors:
database_build_dropdown: '[data-label="Database/Build"]'
-6
View File
@@ -1668,12 +1668,6 @@ class NavigatesGalaxy(HasDriver):
else:
self.components.history_panel.multi_view_button.wait_for_and_click()
def history_panel_show_structure(self):
if self.is_beta_history():
self.use_bootstrap_dropdown(option="show structure", menu="history options")
else:
self.click_history_option(self.components.history_panel.options_show_history_structure)
def history_multi_view_display_collection_contents(self, collection_hid, collection_type="list"):
self.open_history_multi_view()
-1
View File
@@ -240,7 +240,6 @@ def app_pair(global_conf, load_app_kwds=None, wsgi_preflight=True, **kwargs):
webapp.add_client_route("/histories/permissions")
webapp.add_client_route("/histories/view")
webapp.add_client_route("/histories/view_multiple")
webapp.add_client_route("/histories/show_structure")
webapp.add_client_route("/datasets/list")
webapp.add_client_route("/datasets/edit")
webapp.add_client_route("/datasets/{dataset_id}/error")
@@ -1,225 +0,0 @@
"""
Methods to create template for history details recursively.
Used in dataset and history controllers
"""
from galaxy.util import listify
from galaxy.web import url_for
def render_item(trans, entity, children):
"""
Form HTML template for each item in the history
"""
template = ""
entity_name = entity.__class__.__name__
if entity_name == "HistoryDatasetAssociation":
template = render_item_hda(trans, entity, children)
elif entity_name == "Job":
template = render_item_job(trans, entity, children)
elif entity_name == "WorkflowInvocation":
template = render_item_wf(trans, entity, children)
return template
def render_item_hda(trans, hda, children):
"""
Render hdas as a id'd stub for js to fill later
"""
template = ""
if hda.copied_from_history_dataset_association:
template = render_hda_copied_from_history(trans, hda, children)
elif hda.copied_from_library_dataset_dataset_association:
template = render_hda_copied_from_library(trans, hda, children)
else:
template = f'<div id="hda-{trans.security.encode_id(hda.id)}" class="dataset hda state-{hda.state}"></div>'
return template
def render_hda_copied_from_history(trans, hda, children):
"""
Wrap an hda in info about the history from where it was copied
"""
template = ""
id = trans.security.encode_id(hda.id)
history_id = trans.security.encode_id(hda.copied_from_history_dataset_association.history_id)
url = url_for(f"/histories/view?id={history_id}")
template = '<div class="copied-from"><div class="header"><div>'
template += '<span class="light"> Copied from history dataset: </span>'
template += f'<span class="bold">{hda.copied_from_history_dataset_association.name}</span>'
template += '</div><div class="copied-from-source">'
template += '<span class="light">History: </span>'
template += '<span class="bold">'
template += f'<a href="{url}">{hda.copied_from_history_dataset_association.history.name}</a>'
template += "</span></div></div>"
template += f'<div id="hda-{id}" class="dataset hda state-{hda.state}"></div></div>'
return template
def render_hda_copied_from_library(trans, hda, children):
"""
Wrap an hda in info about the library from where it was copied
"""
template = ""
id = trans.security.encode_id(hda.id)
folder = hda.copied_from_library_dataset_dataset_association.library_dataset.folder
folder_id = f"F{trans.security.encode_id(folder.id)}"
url = url_for(f"/libraries/folders/{folder_id}")
template = '<div class="copied-from">'
template += '<div class="header">'
template += "<div>"
template += '<span class="light">Copied from library dataset:</span>'
template += f'<span class="bold">{hda.copied_from_library_dataset_dataset_association.name}</span>'
template += "</div>"
template += '<div class="copied-from-source">'
template += '<span class="light">Library: </span>'
template += '<span class="bold">'
template += f'<a href="{url}">{folder.name}</a></span></div></div>'
template += f'<div id="hda-{id}" class="dataset hda state-{hda.state}"></div></div>'
return template
def render_item_job(trans, job, children):
"""
Render a job and its children (hdas)
"""
template = ""
template = '<div class="tool">'
tool = trans.app.toolbox.get_tool(job.tool_id, tool_version=job.tool_version)
if tool:
tool_name = tool.name
tool_desc = tool.description
else:
tool_name = f"Unknown tool with id: {job.tool_id}"
tool_desc = ""
params_object = None
try:
params_object = job.get_param_values(trans.app, ignore_errors=True)
except Exception:
pass
template += f'<div class="header"><div><span class="bold">{tool_name}</span>'
template += f'<span class="light"> - {tool_desc}</span></div>'
if tool and params_object:
template += '<table class="job-inputs">'
template += inputs_recursive(trans, tool.inputs, params_object, depth=1)
template += "</table>"
else:
template += "<em>No parameter data available</em>"
template += "</div>"
template += '<div class="body">'
child_template = ""
for e, c in reversed(children):
child_template += render_item(trans, e, c)
template += child_template
template += "</div></div>"
return template
def render_item_wf(trans, wf, children):
"""
Render a workflow and its children (jobs)
"""
template = f'<div class="workflow"><div class="header"><span class="bold">{wf.workflow.name}</span>'
template += '<span class="light">- Workflow</span></div><div class="body">'
for e, c in reversed(children):
template += render_item(trans, e, c)
template += "</div></div>"
return template
def inputs_recursive_indent(text, depth):
"""
Add an indentation depending on the depth in a <tr>
"""
return f'<td style="padding-left:{str((depth - 1) * 10)}px">{text}</td>'
def inputs_recursive(trans, input_params, param_values, depth=1, upgrade_messages=None):
"""
Recursive method for tool parameter section
"""
tool_parameter_template = ""
if upgrade_messages is None:
upgrade_messages = {}
for input in input_params.values():
if input.name in param_values:
if input.type == "repeat":
for i in range(len(param_values[input.name])):
inputs_recursive(trans, input.inputs, param_values[input.name][i], depth=depth + 1)
elif input.type == "section":
tool_parameter_template += "<tr>"
tool_parameter_template += inputs_recursive_indent(text=input.name, depth=depth)
tool_parameter_template += "<td></td></tr>"
inputs_recursive(
trans,
input.inputs,
param_values[input.name],
depth=depth + 1,
upgrade_messages=upgrade_messages.get(input.name),
)
elif input.type == "conditional":
try:
current_case = param_values[input.name]["__current_case__"]
is_valid = True
except Exception:
current_case = None
is_valid = False
if is_valid:
tool_parameter_template += "<tr>"
tool_parameter_template += inputs_recursive_indent(text=input.test_param.label, depth=depth)
tool_parameter_template += f"<td>{input.cases[current_case].value}</td><td></td></tr>"
inputs_recursive(
trans,
input.cases[current_case].inputs,
param_values[input.name],
depth=depth + 1,
upgrade_messages=upgrade_messages.get(input.name),
)
else:
tool_parameter_template += "<tr>"
tool_parameter_template += inputs_recursive_indent(text=input.name, depth=depth)
tool_parameter_template += (
"<td><em> The previously used value is no longer valid </em></td><td></td></tr>"
)
elif input.type == "upload_dataset":
tool_parameter_template += "<tr>"
tool_parameter_template += inputs_recursive_indent(text=input.group_title(param_values), depth=depth)
tool_parameter_template += (
f"<td>{str(len(param_values[input.name]))} uploaded datasets</td><td></td></tr>"
)
elif input.type == "data":
tool_parameter_template += "<tr>"
tool_parameter_template += inputs_recursive_indent(text=input.label, depth=depth)
tool_parameter_template += "<td>"
for i, element in enumerate(listify(param_values[input.name])):
if i > 0:
tool_parameter_template += ","
if element.history_content_type == "dataset":
hda = element
encoded_id = trans.security.encode_id(hda.id)
dataset_info_url = url_for(controller="dataset", action="details", dataset_id=encoded_id)
tool_parameter_template += f'<a target="galaxy_main" data-hda-id="{encoded_id}"'
tool_parameter_template += f'href="{dataset_info_url}">{str(hda.hid)}:{hda.name}</a>'
else:
tool_parameter_template += f"{str(element.hid)}:{element.name}"
tool_parameter_template += "</td><td></td></tr>"
elif input.visible:
label = input.label if (hasattr(input, "label") and input.label) else input.name
tool_parameter_template += "<tr>"
tool_parameter_template += inputs_recursive_indent(text=label, depth=depth)
tool_parameter_template += f"<td>{input.value_to_display_text(param_values[input.name])}</td>"
tool_parameter_template += f"<td>{upgrade_messages.get(input.name, '')}</td></tr>"
else:
tool_parameter_template += "<tr>"
if input.type == "conditional":
label = input.test_param.label
elif input.type == "repeat":
label = input.label()
else:
label = input.label or input.name
tool_parameter_template += inputs_recursive_indent(text=label, depth=depth)
tool_parameter_template += (
"<td><em> not used (parameter was added after this job was run)</em></td><td></td></tr>"
)
return tool_parameter_template
@@ -47,7 +47,6 @@ from galaxy.webapps.base.controller import (
SUCCESS,
WARNING,
)
from ._create_history_template import render_item
from ..api import depends
log = logging.getLogger(__name__)
@@ -520,106 +519,6 @@ class HistoryController(BaseUIController, SharableMixin, UsesAnnotations, UsesIt
show_hidden=string_as_bool(show_hidden),
)
@web.expose
@web.json
def display_structured(self, trans, id=None):
"""
Display a history as a nested structure showing the jobs and workflow
invocations that created each dataset (if any).
"""
# Get history
if id is None:
id = trans.history.id
else:
id = self.decode_id(id)
# Expunge history from the session to allow us to force a reload
# with a bunch of eager loaded joins
trans.sa_session.expunge(trans.history)
history = (
trans.sa_session.query(model.History)
.options(
joinedload("active_datasets")
.joinedload("creating_job_associations")
.joinedload("job")
.joinedload("workflow_invocation_step")
.joinedload("workflow_invocation")
.joinedload("workflow"),
)
.get(id)
)
if not (
history
and (
(history.user and trans.user and history.user.id == trans.user.id)
or (trans.history and history.id == trans.history.id)
or trans.user_is_admin
)
):
return trans.show_error_message("Cannot display history structure.")
# Resolve jobs and workflow invocations for the datasets in the history
# items is filled with items (hdas, jobs, or workflows) that go at the
# top level
items = []
# First go through and group hdas by job, if there is no job they get
# added directly to items
jobs = {}
for hda in history.active_datasets:
if hda.visible is False:
continue
# Follow "copied from ..." association until we get to the original
# instance of the dataset
original_hda = hda
# while original_hda.copied_from_history_dataset_association:
# original_hda = original_hda.copied_from_history_dataset_association
# Check if the job has a creating job, most should, datasets from
# before jobs were tracked, or from the upload tool before it
# created a job, may not
if not original_hda.creating_job_associations:
items.append((hda, None))
# Attach hda to correct job
# -- there should only be one creating_job_association, so this
# loop body should only be hit once
for assoc in original_hda.creating_job_associations:
job = assoc.job
if job in jobs:
jobs[job].append((hda, None))
else:
jobs[job] = [(hda, None)]
# Second, go through the jobs and connect to workflows
wf_invocations = {}
for job, hdas in jobs.items():
# Job is attached to a workflow step, follow it to the
# workflow_invocation and group
if job.workflow_invocation_step:
wf_invocation = job.workflow_invocation_step.workflow_invocation
if wf_invocation in wf_invocations:
wf_invocations[wf_invocation].append((job, hdas))
else:
wf_invocations[wf_invocation] = [(job, hdas)]
# Not attached to a workflow, add to items
else:
items.append((job, hdas))
# Finally, add workflow invocations to items, which should now
# contain all hdas with some level of grouping
items.extend(wf_invocations.items())
# Sort items by age
items.sort(key=(lambda x: x[0].create_time), reverse=True)
# logic taken from mako files
from galaxy.managers import hdas
hda_serializer = hdas.HDASerializer(trans.app)
hda_dicts = []
id_hda_dict_map = {}
for hda in history.active_datasets:
hda_dict = hda_serializer.serialize_to_view(hda, user=trans.user, trans=trans, view="detailed")
id_hda_dict_map[hda_dict["id"]] = hda_dict
hda_dicts.append(hda_dict)
html_template = ""
for entity, children in items:
html_template += render_item(trans, entity, children)
return {"name": history.name, "history_json": hda_dicts, "template": html_template}
@expose_api_anonymous
def view(self, trans, id=None, show_deleted=False, show_hidden=False, use_panels=True):
"""
@@ -1,41 +0,0 @@
from .framework import (
managed_history,
selenium_test,
SeleniumTestCase,
)
class HistoryStructureTestCase(SeleniumTestCase):
ensure_registered = True
@selenium_test
@managed_history
def test_history_structure(self):
def assert_details(expected_to_be_visible):
error_message = "details are visible!"
if expected_to_be_visible:
error_message = "details are not visible!"
assert expected_to_be_visible == self.components.history_structure.details.is_displayed, error_message
self.perform_upload(self.get_filename("1.fasta"))
self.wait_for_history()
self.components.history_structure.header.assert_absent_or_hidden()
self.components.history_structure.dataset.assert_absent_or_hidden()
self.history_panel_show_structure()
# assert details are not visible before expand
self.components.history_structure.details.assert_absent_or_hidden()
self.components.history_structure.dataset.wait_for_and_click()
self.sleep_for(self.wait_types.UX_RENDER)
assert_details(True)
self.components.history_structure.header.wait_for_and_click()
self.sleep_for(self.wait_types.UX_RENDER)
assert_details(False)
self.components.history_structure.dataset.assert_absent_or_hidden()