[go: nahoru, domu]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor display application handling #15076

Draft
wants to merge 10 commits into
base: dev
Choose a base branch
from
56 changes: 56 additions & 0 deletions client/src/components/Visualizations/DisplayApplication.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<script setup>
import { urlData } from "utils/url";
import { DatasetProvider } from "components/providers";
import { computed, defineProps, onMounted, ref } from "vue";
const props = defineProps({
appName: {
type: String,
required: true,
},
datasetId: {
type: String,
required: true,
},
linkName: {
type: String,
required: true,
},
});
const applicationData = ref({});
const hasData = computed(() => !!applicationData.value);
async function getCreateLink() {
const params = new URLSearchParams({
app_name: props.appName,
dataset_id: props.datasetId,
link_name: props.linkName,
});
const buildUrl = `/api/display_applications/create_link?${params.toString()}`;
applicationData.value = await urlData({ url: buildUrl });
console.log(applicationData.value);
}
onMounted(() => {
getCreateLink();
});
</script>
<template>
<div v-if="hasData">
<div v-for="(message, messageIndex) in applicationData.msg" :key="messageIndex">
<b-alert :variant="message[1]" show>{{ message[0] }}</b-alert>
</div>
<div v-if="applicationData.preparable_steps">
<h2>Preparation Status</h2>
<table class="colored">
<tr>
<th>Step Name</th>
<th>Ready</th>
<th>Dataset Status</th>
</tr>
<tr v-for="(step, stepIndex) in applicationData.preparable_steps" :key="stepIndex">
<td>{{ step.name }}</td>
<td>{{ step.ready }}</td>
<td>{{ step.state }}</td>
</tr>
</table>
</div>
</div>
</template>
34 changes: 17 additions & 17 deletions client/src/components/Visualizations/DisplayApplications.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
<script setup>
import { safePath } from "utils/redirect";
import { DatasetProvider } from "components/providers";
import { defineProps } from "vue";
const props = defineProps({
datasetId: {
type: String,
required: true,
},
});
function getUrl(link) {
return safePath(`/display_applications/${props.datasetId}/${link.app_name}/${link.link_name}`);
}
</script>
<template>
<div>
<DatasetProvider :id="datasetId" v-slot="{ result: dataset }">
<DatasetProvider :id="props.datasetId" v-slot="{ result: dataset }">
<b-alert
v-if="
(dataset && dataset.display_apps && dataset.display_apps.length > 0) ||
Expand All @@ -15,7 +29,7 @@
<span class="font-weight-bold">{{ displayApp.label }}</span>
<span v-for="(link, linkKey) in displayApp.links" :key="linkKey">
<span v-if="linkKey == 0">(</span>
<b-link :href="link.href" :target="link.target">{{ link.text }}</b-link>
<router-link :to="getUrl(link)">{{ link.text }}</router-link>
<span v-if="linkKey != displayApp.links.length - 1">, </span>
<span v-else>)</span>
</span>
Expand All @@ -24,7 +38,7 @@
<span class="font-weight-bold">{{ displayType.label }}</span>
<span v-for="(link, linkKey) in displayType.links" :key="linkKey">
<span v-if="linkKey == 0">(</span>
<b-link :href="link.href" :target="link.target">{{ link.text }}</b-link>
<router-link :to="link.href">{{ link.text }}</router-link>
<span v-if="linkKey != displayType.links.length - 1">, </span>
<span v-else>)</span>
</span>
Expand All @@ -36,17 +50,3 @@
</DatasetProvider>
</div>
</template>
<script>
import { DatasetProvider } from "components/providers";
export default {
components: {
DatasetProvider,
},
props: {
datasetId: {
type: String,
required: true,
},
},
};
</script>
7 changes: 7 additions & 0 deletions client/src/entry/analysis/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import DatasetAttributes from "components/DatasetInformation/DatasetAttributes";
import DatasetDetails from "components/DatasetInformation/DatasetDetails";
import DatasetError from "components/DatasetInformation/DatasetError";
import DatasetList from "components/Dataset/DatasetList";
import DisplayApplication from "components/Visualizations/DisplayApplication";
import AvailableDatatypes from "components/AvailableDatatypes/AvailableDatatypes";
import FormGeneric from "components/Form/FormGeneric";
import Grid from "components/Grid/Grid";
Expand Down Expand Up @@ -178,6 +179,12 @@ export function getRouter(Galaxy) {
path: "datatypes",
component: AvailableDatatypes,
},
{
path: "display_applications/:datasetId/:appName/:linkName",
component: DisplayApplication,
props: true,
redirect: redirectAnon(),
},
{
path: "histories/import",
component: HistoryImport,
Expand Down
3 changes: 2 additions & 1 deletion lib/galaxy/datatypes/display_applications/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,8 @@ def get_prepare_steps(self, datasets_only=True):
if datasets_only and not isinstance(param, DisplayApplicationDataParameter):
continue
value = self.parameters.get(name, None)
rval.append({"name": name, "value": value, "param": param, "ready": param.ready(self.parameters)})
state = value.state if value is not None else None
rval.append({"name": name, "state": state, "ready": param.ready(self.parameters)})
return rval

def display_url(self):
Expand Down
121 changes: 121 additions & 0 deletions lib/galaxy/managers/display_applications.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import logging
import os
from typing import (
Any,
Dict,
List,
)
from urllib.parse import unquote_plus

from galaxy.exceptions import MessageException
from galaxy.datatypes.display_applications.util import (
decode_dataset_user,
encode_dataset_user,
)
from galaxy.datatypes.registry import Registry
from galaxy.structured_app import StructuredApp
from galaxy.util import unicodify

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -67,3 +75,116 @@ def reload(self, ids: List[str]) -> Dict[str, Any]:
else:
message = 'Reloaded %i requested display applications ("%s").' % (len(reloaded), '", "'.join(reloaded))
return {"message": message, "reloaded": reloaded, "failed": failed}


def create_link(
self,
trans,
dataset_id=None,
user_id=None,
app_name=None,
link_name=None,
**kwds,
):
"""Access to external display applications"""
if None in [app_name, link_name]:
raise MessageException("A display application name and link name must be provided.")
app_name = unquote_plus(app_name)
link_name = unquote_plus(link_name)
# Build list of parameters to pass in to display application logic (app_kwds)
app_kwds = {}
for name, value in dict(kwds).items(): # clone kwds because we remove stuff as we go.
if name.startswith("app_"):
app_kwds[name[len("app_") :]] = value
del kwds[name]
if kwds:
log.debug(f"Unexpected Keywords passed to display_application: {kwds}") # route memory?
# decode ids
data, user = decode_dataset_user(trans, dataset_id, user_id)
if not data:
raise MessageException(f"Invalid reference dataset id: {str(dataset_id)}.")
if user is None:
user = trans.user
if user:
user_roles = user.all_roles()
else:
user_roles = []
# Decode application name and link name
if self._can_access_dataset(trans, data, additional_roles=user_roles):
msg = []
preparable_steps = []
refresh = False
display_app = trans.app.datatypes_registry.display_applications.get(app_name)
if not display_app:
log.debug("Unknown display application has been requested: %s", app_name)
return MessageException(
f"The requested display application ({app_name}) is not available."
)
dataset_hash, user_hash = encode_dataset_user(trans, data, user)
try:
display_link = display_app.get_link(link_name, data, dataset_hash, user_hash, trans, app_kwds)
except Exception as e:
log.debug("Error generating display_link: %s", e)
# User can sometimes recover from, e.g. conversion errors by fixing input metadata, so use conflict
return MessageException(f"Error generating display_link: {e}")
if not display_link:
log.debug("Unknown display link has been requested: %s", link_name)
return MessageException(f"Unknown display link has been requested: {link_name}")
if data.state == data.states.ERROR:
msg.append(
(
"This dataset is in an error state, you cannot view it at an external display application.",
"error",
)
)
elif data.deleted:
msg.append(
("This dataset has been deleted, you cannot view it at an external display application.", "error")
)
elif data.state != data.states.OK:
msg.append(
(
"You must wait for this dataset to be created before you can view it at an external display application.",
"info",
)
)
refresh = True
else:
# We have permissions, dataset is not deleted and is in OK state, allow access
if display_link.display_ready():
# redirect user to url generated by display link
return dict(resource=display_link.display_url())
else:
if trans.history != data.history:
msg.append(
(
"You must import this dataset into your current history before you can view it at the desired display application.",
"error",
)
)
else:
refresh = True
msg.append(
(
"Launching this display application required additional datasets to be generated, you can view the status of these jobs below. ",
"info",
)
)
if not display_link.preparing_display():
display_link.prepare_display()
preparable_steps = display_link.get_prepare_steps()
return dict(
msg=msg,
refresh=refresh,
preparable_steps=preparable_steps,
)
raise MessageException("You do not have permission to view this dataset at an external display application.")


def _can_access_dataset(self, trans, dataset_association, allow_admin=True, additional_roles=None):
roles = trans.get_current_user_roles()
if additional_roles:
roles = roles + additional_roles
return (allow_admin and trans.user_is_admin) or trans.app.security_agent.can_access_dataset(
roles, dataset_association.dataset
)
6 changes: 4 additions & 2 deletions lib/galaxy/managers/hdas.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
List,
Optional,
)

from sqlalchemy.orm.session import object_session

from urllib.parse import quote_plus

from galaxy import (
datatypes,
exceptions,
Expand Down Expand Up @@ -480,14 +481,15 @@ def serialize_display_apps(self, item, key, trans=None, **context):
hda = item
display_apps: List[Dict[str, Any]] = []
for display_app in hda.get_display_applications(trans).values():

app_links = []
for link_app in display_app.links.values():
app_links.append(
{
"target": link_app.url.get("target_frame", "_blank"),
"href": link_app.get_display_url(hda, trans),
"text": gettext.gettext(link_app.name),
"app_name": quote_plus(link_app.display_application.id),
"link_name": quote_plus(link_app.id),
}
)
if app_links:
Expand Down
28 changes: 28 additions & 0 deletions lib/galaxy/webapps/galaxy/api/display_applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,31 @@ def reload(self, trans, payload=None, **kwd):
payload = payload or {}
ids = payload.get("ids", [])
return self.manager.reload(ids)

@expose_api
def create_link(self,
trans,
app_name=None,
dataset_id=None,
link_name=None,
user_id=None,
**kwd):
"""
POST /api/display_applications/create_link

Creates a link for display applications.

:param app_name: display application name
:type app_name: string
:param dataset_id: encoded dataset_id
:type dataset_id: string
:param link_name: link name
:type link_name: string
"""
return self.manager.create_link(
trans,
app_name=app_name,
dataset_id=dataset_id,
link_name=link_name,
**kwd,
)
9 changes: 9 additions & 0 deletions lib/galaxy/webapps/galaxy/buildapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ def app_pair(global_conf, load_app_kwds=None, wsgi_preflight=True, **kwargs):
webapp.add_client_route("/datasets/{dataset_id}/details")
webapp.add_client_route("/datasets/{dataset_id}/preview")
webapp.add_client_route("/datasets/{dataset_id}/show_params")
webapp.add_client_route("/display_applications/{path:.*?}")
webapp.add_client_route("/collection/{collection_id}/edit")
webapp.add_client_route("/jobs/{job_id}/view")
webapp.add_client_route("/workflows/list")
Expand Down Expand Up @@ -798,6 +799,14 @@ def connect_invocation_endpoint(endpoint_name, endpoint_suffix, action, conditio
conditions=dict(method=["GET"]),
)

webapp.mapper.connect(
"create_link",
"/api/display_applications/create_link",
controller="display_applications",
action="create_link",
conditions=dict(method=["GET"]),
)

webapp.mapper.connect(
"reload",
"/api/display_applications/reload",
Expand Down
Loading