mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge pull request #11863 from guerler/import_history
Allow importing histories through pages / reports
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-link v-if="showLink" @click="onClick">Click to Import History: {{ name }}.</b-link>
|
||||
<div v-if="imported" class="text-success">
|
||||
<font-awesome-icon icon="check" class="mr-1" />
|
||||
<span>Successfully Imported History: {{ name }}!</span>
|
||||
</div>
|
||||
<div v-if="!!error" class="text-danger">
|
||||
<font-awesome-icon icon="exclamation-triangle" class="mr-1" />
|
||||
<span>Failed to Import History: {{ name }}!</span>
|
||||
<span>{{ error }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import Vue from "vue";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { errorMessageAsString } from "utils/simple-error";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FontAwesomeIcon,
|
||||
},
|
||||
props: {
|
||||
args: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
histories: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
imported: false,
|
||||
error: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
name() {
|
||||
return this.histories[this.args.history_id].name;
|
||||
},
|
||||
showLink() {
|
||||
return !this.imported && !this.error;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
axios
|
||||
.post(`${getAppRoot()}api/histories`, { history_id: this.args.history_id })
|
||||
.then(() => {
|
||||
this.imported = true;
|
||||
})
|
||||
.catch((e) => {
|
||||
this.error = errorMessageAsString(e);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -44,6 +44,7 @@
|
||||
<div v-else-if="obj.name == 'generate_time'" class="galaxy-time">
|
||||
<pre><code>{{ getTime }}</code></pre>
|
||||
</div>
|
||||
<HistoryLink v-else-if="obj.name == 'history_link'" :args="obj.args" :histories="histories" />
|
||||
<HistoryDatasetAsImage v-else-if="obj.name == 'history_dataset_as_image'" :args="obj.args" />
|
||||
<HistoryDatasetLink v-else-if="obj.name == 'history_dataset_link'" :args="obj.args" />
|
||||
<HistoryDatasetIndex v-else-if="obj.name == 'history_dataset_index'" :args="obj.args" />
|
||||
@@ -105,6 +106,7 @@ import HistoryDatasetLink from "./Elements/HistoryDatasetLink";
|
||||
import HistoryDatasetIndex from "./Elements/HistoryDatasetIndex";
|
||||
import HistoryDatasetCollectionDisplay from "./Elements/HistoryDatasetCollection/CollectionDisplay";
|
||||
import HistoryDatasetDetails from "./Elements/HistoryDatasetDetails";
|
||||
import HistoryLink from "./Elements/HistoryLink";
|
||||
import InvocationTime from "./Elements/InvocationTime";
|
||||
import JobMetrics from "./Elements/JobMetrics";
|
||||
import JobParameters from "./Elements/JobParameters";
|
||||
@@ -137,6 +139,7 @@ export default {
|
||||
HistoryDatasetDisplay,
|
||||
HistoryDatasetIndex,
|
||||
HistoryDatasetLink,
|
||||
HistoryLink,
|
||||
JobMetrics,
|
||||
JobParameters,
|
||||
LoadingSpan,
|
||||
@@ -165,6 +168,7 @@ export default {
|
||||
markdownObjects: [],
|
||||
markdownErrors: [],
|
||||
historyDatasets: {},
|
||||
histories: {},
|
||||
historyDatasetCollections: {},
|
||||
workflows: {},
|
||||
jobs: {},
|
||||
@@ -201,6 +205,7 @@ export default {
|
||||
this.markdownErrors = config.errors || [];
|
||||
this.markdownObjects = this.splitMarkdown(markdown);
|
||||
this.historyDatasets = config.history_datasets || {};
|
||||
this.histories = config.histories || {};
|
||||
this.historyDatasetCollections = config.history_dataset_collections || {};
|
||||
this.workflows = config.workflows || {};
|
||||
this.jobs = config.jobs || {};
|
||||
|
||||
@@ -54,6 +54,14 @@
|
||||
@onOk="onWorkflow"
|
||||
@onCancel="onCancel"
|
||||
/>
|
||||
<BasicSelectionDialog
|
||||
v-else-if="historyShow"
|
||||
:get-data="getHistories"
|
||||
title="History"
|
||||
label-key="name"
|
||||
@onOk="onHistory"
|
||||
@onCancel="onCancel"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -120,9 +128,11 @@ export default {
|
||||
jobsUrl: `${getAppRoot()}api/jobs`,
|
||||
workflowsUrl: `${getAppRoot()}api/workflows`,
|
||||
invocationsUrl: `${getAppRoot()}api/invocations`,
|
||||
historiesUrl: `${getAppRoot()}api/histories?view=detailed&q=published&qv=True`,
|
||||
selectedShow: false,
|
||||
visualizationShow: false,
|
||||
workflowShow: false,
|
||||
historyShow: false,
|
||||
jobShow: false,
|
||||
invocationShow: false,
|
||||
dataShow: false,
|
||||
@@ -148,6 +158,9 @@ export default {
|
||||
getWorkflows() {
|
||||
return axios.get(this.workflowsUrl);
|
||||
},
|
||||
getHistories() {
|
||||
return axios.get(this.historiesUrl);
|
||||
},
|
||||
onData(response) {
|
||||
this.dataShow = false;
|
||||
this.$emit("onInsert", `${this.argumentName}(history_dataset_id=${response})`);
|
||||
@@ -164,6 +177,10 @@ export default {
|
||||
this.invocationShow = false;
|
||||
this.$emit("onInsert", `${this.argumentName}(invocation_id=${response.id})`);
|
||||
},
|
||||
onHistory(response) {
|
||||
this.historyShow = false;
|
||||
this.$emit("onInsert", `history_link(history_id=${response.id})`);
|
||||
},
|
||||
onWorkflow(response) {
|
||||
this.workflowShow = false;
|
||||
this.$emit("onInsert", `workflow_display(workflow_id=${response.id})`);
|
||||
@@ -175,6 +192,8 @@ export default {
|
||||
onCreate() {
|
||||
if (this.argumentType == "workflow_id") {
|
||||
this.workflowShow = true;
|
||||
} else if (this.argumentType == "history_id") {
|
||||
this.historyShow = true;
|
||||
} else if (this.argumentType == "history_dataset_id") {
|
||||
if (this.useLabels) {
|
||||
this.selectedShow = true;
|
||||
|
||||
@@ -10,7 +10,13 @@
|
||||
<b-alert v-if="error" variant="danger" class="my-2 mx-3 px-2 py-1" show>
|
||||
{{ error }}
|
||||
</b-alert>
|
||||
<tool-section :category="historySection" @onClick="onClick" :expanded="true" />
|
||||
<tool-section
|
||||
v-if="isWorkflow"
|
||||
:category="historyInEditorSection"
|
||||
@onClick="onClick"
|
||||
:expanded="true"
|
||||
/>
|
||||
<tool-section v-else :category="historySection" @onClick="onClick" :expanded="true" />
|
||||
<tool-section :category="jobSection" @onClick="onClick" :expanded="true" />
|
||||
<tool-section
|
||||
v-if="isWorkflow"
|
||||
@@ -52,6 +58,59 @@ import { getAppRoot } from "onload/loadConfig";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
const historySharedElements = [
|
||||
{
|
||||
id: "history_dataset_display",
|
||||
name: "Dataset",
|
||||
emitter: "onHistoryDatasetId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_collection_display",
|
||||
name: "Collection",
|
||||
emitter: "onHistoryCollectionId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_as_image",
|
||||
name: "Image",
|
||||
emitter: "onHistoryDatasetId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_index",
|
||||
name: "Dataset Index",
|
||||
emitter: "onHistoryDatasetId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_embedded",
|
||||
name: "Embedded Dataset",
|
||||
emitter: "onHistoryDatasetId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_type",
|
||||
name: "Dataset Type",
|
||||
emitter: "onHistoryDatasetId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_link",
|
||||
name: "Link to Dataset",
|
||||
emitter: "onHistoryDatasetId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_name",
|
||||
name: "Name of Dataset",
|
||||
emitter: "onHistoryDatasetId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_peek",
|
||||
name: "Peek into Dataset",
|
||||
emitter: "onHistoryDatasetId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_info",
|
||||
name: "Dataset Details",
|
||||
emitter: "onHistoryDatasetId",
|
||||
},
|
||||
];
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MarkdownDialog,
|
||||
@@ -69,61 +128,29 @@ export default {
|
||||
selectedType: null,
|
||||
selectedLabels: null,
|
||||
selectedShow: false,
|
||||
selectedPayload: null,
|
||||
visualizationIndex: {},
|
||||
error: null,
|
||||
historySection: {
|
||||
title: "History",
|
||||
name: "history",
|
||||
elems: [
|
||||
...historySharedElements,
|
||||
{
|
||||
id: "history_dataset_display",
|
||||
name: "Dataset",
|
||||
id: "history_link",
|
||||
name: "Link to Import",
|
||||
emitter: "onHistoryId",
|
||||
},
|
||||
],
|
||||
},
|
||||
historyInEditorSection: {
|
||||
title: "History",
|
||||
name: "history",
|
||||
elems: [
|
||||
...historySharedElements,
|
||||
{
|
||||
id: "history_dataset_collection_display",
|
||||
name: "Collection",
|
||||
emitter: "onHistoryCollectionId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_as_image",
|
||||
name: "Image",
|
||||
emitter: "onHistoryId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_index",
|
||||
name: "Dataset Index",
|
||||
emitter: "onHistoryId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_embedded",
|
||||
name: "Embedded Dataset",
|
||||
emitter: "onHistoryId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_type",
|
||||
name: "Dataset Type",
|
||||
emitter: "onHistoryId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_link",
|
||||
name: "Link to Dataset",
|
||||
emitter: "onHistoryId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_name",
|
||||
name: "Name of Dataset",
|
||||
emitter: "onHistoryId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_peek",
|
||||
name: "Peek into Dataset",
|
||||
emitter: "onHistoryId",
|
||||
},
|
||||
{
|
||||
id: "history_dataset_info",
|
||||
name: "Dataset Details",
|
||||
emitter: "onHistoryId",
|
||||
id: "history_link",
|
||||
name: "Link to Import",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -264,6 +291,9 @@ export default {
|
||||
},
|
||||
onClick(item) {
|
||||
switch (item.emitter) {
|
||||
case "onHistoryDatasetId":
|
||||
this.onHistoryDatasetId(item.id);
|
||||
break;
|
||||
case "onHistoryId":
|
||||
this.onHistoryId(item.id);
|
||||
break;
|
||||
@@ -304,6 +334,11 @@ export default {
|
||||
this.selectedShow = true;
|
||||
},
|
||||
onHistoryId(argumentName) {
|
||||
this.selectedArgumentName = argumentName;
|
||||
this.selectedType = "history_id";
|
||||
this.selectedShow = true;
|
||||
},
|
||||
onHistoryDatasetId(argumentName) {
|
||||
this.selectedArgumentName = argumentName;
|
||||
this.selectedType = "history_dataset_id";
|
||||
this.selectedLabels = this.getOutputs();
|
||||
|
||||
@@ -15,6 +15,7 @@ GALAXY_FLAVORED_MARKDOWN_CONTAINER_LINE_PATTERN = re.compile(
|
||||
VALID_CONTAINER_END_PATTERN = re.compile(r"^```\s*$")
|
||||
DYNAMIC_ARGUMENTS = object()
|
||||
VALID_ARGUMENTS = {
|
||||
"history_link": ["history_id"],
|
||||
"history_dataset_display": ["input", "output", "history_dataset_id"],
|
||||
"history_dataset_embedded": ["input", "output", "history_dataset_id"],
|
||||
"history_dataset_as_image": ["input", "output", "history_dataset_id", "path"],
|
||||
|
||||
@@ -49,8 +49,8 @@ INPUT_LABEL_PATTERN = re.compile(r'input=\s*%s\s*' % ARG_VAL_CAPTURED_REGEX)
|
||||
STEP_LABEL_PATTERN = re.compile(r'step=\s*%s\s*' % ARG_VAL_CAPTURED_REGEX)
|
||||
PATH_LABEL_PATTERN = re.compile(r'path=\s*%s\s*' % ARG_VAL_CAPTURED_REGEX)
|
||||
# STEP_OUTPUT_LABEL_PATTERN = re.compile(r'step_output=([\w_\-]+)/([\w_\-]+)')
|
||||
UNENCODED_ID_PATTERN = re.compile(r'(workflow_id|history_dataset_id|history_dataset_collection_id|job_id|invocation_id)=([\d]+)')
|
||||
ENCODED_ID_PATTERN = re.compile(r'(workflow_id|history_dataset_id|history_dataset_collection_id|job_id|invocation_id)=([a-z0-9]+)')
|
||||
UNENCODED_ID_PATTERN = re.compile(r'(history_id|workflow_id|history_dataset_id|history_dataset_collection_id|job_id|invocation_id)=([\d]+)')
|
||||
ENCODED_ID_PATTERN = re.compile(r'(history_id|workflow_id|history_dataset_id|history_dataset_collection_id|job_id|invocation_id)=([a-z0-9]+)')
|
||||
INVOCATION_SECTION_MARKDOWN_CONTAINER_LINE_PATTERN = re.compile(
|
||||
r"```\s*galaxy\s*"
|
||||
)
|
||||
@@ -80,6 +80,7 @@ class GalaxyInternalMarkdownDirectiveHandler(metaclass=abc.ABCMeta):
|
||||
|
||||
def walk(self, trans, internal_galaxy_markdown):
|
||||
hda_manager = trans.app.hda_manager
|
||||
history_manager = trans.app.history_manager
|
||||
workflow_manager = trans.app.workflow_manager
|
||||
job_manager = JobManager(trans.app)
|
||||
collection_manager = trans.app.dataset_collections_service
|
||||
@@ -96,7 +97,11 @@ class GalaxyInternalMarkdownDirectiveHandler(metaclass=abc.ABCMeta):
|
||||
object_id = int(id_match.group(2))
|
||||
encoded_id = trans.security.encode_id(object_id)
|
||||
line = line.replace(id_match.group(), "{}={}".format(id_match.group(1), encoded_id))
|
||||
if container == "history_dataset_display":
|
||||
if container == "history_link":
|
||||
_check_object(object_id, line)
|
||||
history = history_manager.get_accessible(object_id, trans.user)
|
||||
rval = self.handle_history_link(line, history)
|
||||
elif container == "history_dataset_display":
|
||||
_check_object(object_id, line)
|
||||
hda = hda_manager.get_accessible(object_id, trans.user)
|
||||
rval = self.handle_dataset_display(line, hda)
|
||||
@@ -176,6 +181,10 @@ class GalaxyInternalMarkdownDirectiveHandler(metaclass=abc.ABCMeta):
|
||||
export_markdown = _remap_galaxy_markdown_calls(_remap_container, internal_galaxy_markdown)
|
||||
return export_markdown
|
||||
|
||||
@abc.abstractmethod
|
||||
def handle_history_link(self, line, history):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def handle_dataset_display(self, line, hda):
|
||||
pass
|
||||
@@ -288,6 +297,9 @@ class ReadyForExportMarkdownDirectiveHandler(GalaxyInternalMarkdownDirectiveHand
|
||||
def handle_tool_stderr(self, line, job):
|
||||
self.ensure_rendering_data_for("jobs", job)["tool_stderr"] = job.tool_stderr or "*No Standard Error Available*"
|
||||
|
||||
def handle_history_link(self, line, history):
|
||||
self.ensure_rendering_data_for("histories", history)["name"] = history.name
|
||||
|
||||
# Following three cases - the client side widgets have everything they need
|
||||
# from the encoded ID. Don't implement a default on the base class though because
|
||||
# it is good to force both Client and PDF/HTML export to deal with each new directive
|
||||
@@ -396,6 +408,13 @@ class ToBasicMarkdownDirectiveHandler(GalaxyInternalMarkdownDirectiveHandler):
|
||||
rval = (f"", True)
|
||||
return rval
|
||||
|
||||
def handle_history_link(self, line, history):
|
||||
if history:
|
||||
content = self.markdown_formatting_helpers.literal_via_fence(history.name)
|
||||
else:
|
||||
content = "*No History available*"
|
||||
return (content, True)
|
||||
|
||||
def handle_dataset_peek(self, line, hda):
|
||||
if hda.peek:
|
||||
content = self.markdown_formatting_helpers.literal_via_fence(hda.peek)
|
||||
@@ -664,6 +683,8 @@ history_dataset_collection_display(input={})
|
||||
# TODO: this really should be workflow id not stored workflow id but the API
|
||||
# it consumes wants the stored id.
|
||||
return ("workflow_display(workflow_id=%s)\n" % invocation.workflow.stored_workflow.id, False)
|
||||
if container == "history_link":
|
||||
return ("history_link(history_id=%s)\n" % invocation.history.id, False)
|
||||
if container == "invocation_date":
|
||||
return ("invocation_date(invocation_id=%s)\n" % invocation.id, False)
|
||||
ref_object_type = None
|
||||
|
||||
@@ -22,6 +22,12 @@ class BaseExportTestCase(BaseTestCase):
|
||||
self.app.history_manager = mock.MagicMock()
|
||||
self.app.dataset_collections_service = mock.MagicMock()
|
||||
|
||||
def _new_history(self):
|
||||
history = model.History()
|
||||
history.id = 1
|
||||
history.name = "New History"
|
||||
return history
|
||||
|
||||
def _new_hda(self, contents=None):
|
||||
hda = model.HistoryDatasetAssociation()
|
||||
hda.id = 1
|
||||
@@ -39,6 +45,12 @@ class BaseExportTestCase(BaseTestCase):
|
||||
invocation.create_time = now()
|
||||
return invocation
|
||||
|
||||
@contextmanager
|
||||
def _expect_get_history(self, history):
|
||||
self.app.history_manager.get_accessible.return_value = history
|
||||
yield
|
||||
self.app.history_manager.get_accessible.assert_called_once_with(history.id, self.trans.user)
|
||||
|
||||
@contextmanager
|
||||
def _expect_get_hda(self, hda, hda_id=1):
|
||||
self.app.hda_manager.get_accessible.return_value = hda
|
||||
@@ -128,6 +140,17 @@ history_dataset_peek(history_dataset_id=1)
|
||||
result = self._to_basic(example)
|
||||
assert '\n*No Dataset Peek Available*\n' in result
|
||||
|
||||
def test_history_link(self):
|
||||
history = self._new_history()
|
||||
example = """# Example
|
||||
```galaxy
|
||||
history_link(history_id=1)
|
||||
```
|
||||
"""
|
||||
with self._expect_get_history(history):
|
||||
result = self._to_basic(example)
|
||||
assert '\n New History\n\n' in result
|
||||
|
||||
def test_history_display_binary(self):
|
||||
hda = self._new_hda()
|
||||
hda.extension = 'ab1'
|
||||
|
||||
Reference in New Issue
Block a user