Skip to content

Commit

Permalink
Merge pull request #19495 from mvdbeek/use_visualizations_api_in_trac…
Browse files Browse the repository at this point in the history
…kster

[24.2] Use visualizations api in trackster
  • Loading branch information
dannon authored Feb 4, 2025
2 parents 2c81589 + 09eafef commit 0019941
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 147 deletions.
27 changes: 17 additions & 10 deletions client/src/viz/trackster.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,23 +74,30 @@ export class TracksterUI extends Backbone.Model {
},
bookmarks: bookmarks,
};

// Make call to save visualization.
return $.ajax({
url: `${getAppRoot()}visualization/save`,
type: "POST",
const request = {
dataType: "json",
data: {
contentType: "application/json; charset=utf-8",
data: JSON.stringify({
id: this.view.vis_id,
title: this.view.config.get_value("name"),
dbkey: this.view.dbkey,
type: "trackster",
vis_json: JSON.stringify(viz_config),
},
})
config: viz_config,
}),
};
if (!this.view.vis_id) {
request.url = `${getAppRoot()}api/visualizations`;
request.type = "POST";
} else {
request.url = `${getAppRoot()}api/visualizations/${this.view.vis_id}`;
request.type = "PUT";
}

// Make call to save visualization.
return $.ajax(request)
.success((vis_info) => {
Galaxy.modal.hide();
this.view.vis_id = vis_info.vis_id;
this.view.vis_id = vis_info.id;
this.view.has_changes = false;

// Needed to set URL when first saving a visualization.
Expand Down
91 changes: 7 additions & 84 deletions lib/galaxy/webapps/base/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,87 +613,6 @@ class UsesVisualizationMixin(UsesLibraryMixinItems):

slug_builder = SlugBuilder()

def save_visualization(self, trans, config, type, id=None, title=None, dbkey=None, slug=None, annotation=None):
session = trans.sa_session

# Create/get visualization.
if not id:
# Create new visualization.
vis = self._create_visualization(trans, title, type, dbkey, slug, annotation)
else:
decoded_id = trans.security.decode_id(id)
vis = session.get(model.Visualization, decoded_id)
# TODO: security check?

# Create new VisualizationRevision that will be attached to the viz
vis_rev = trans.model.VisualizationRevision()
vis_rev.visualization = vis
# do NOT alter the dbkey
vis_rev.dbkey = vis.dbkey
# do alter the title and config
vis_rev.title = title

# -- Validate config. --

if vis.type == "trackster":

def unpack_track(track_dict):
"""Unpack a track from its json."""
dataset_dict = track_dict["dataset"]
return {
"dataset_id": trans.security.decode_id(dataset_dict["id"]),
"hda_ldda": dataset_dict.get("hda_ldda", "hda"),
"track_type": track_dict["track_type"],
"prefs": track_dict["prefs"],
"mode": track_dict["mode"],
"filters": track_dict["filters"],
"tool_state": track_dict["tool_state"],
}

def unpack_collection(collection_json):
"""Unpack a collection from its json."""
unpacked_drawables = []
drawables = collection_json["drawables"]
for drawable_json in drawables:
if "track_type" in drawable_json:
drawable = unpack_track(drawable_json)
else:
drawable = unpack_collection(drawable_json)
unpacked_drawables.append(drawable)
return {
"obj_type": collection_json["obj_type"],
"drawables": unpacked_drawables,
"prefs": collection_json.get("prefs", []),
"filters": collection_json.get("filters", None),
}

# TODO: unpack and validate bookmarks:
def unpack_bookmarks(bookmarks_json):
return bookmarks_json

# Unpack and validate view content.
view_content = unpack_collection(config["view"])
bookmarks = unpack_bookmarks(config["bookmarks"])
vis_rev.config = {"view": view_content, "bookmarks": bookmarks}
# Viewport from payload
viewport = config.get("viewport")
if viewport:
chrom = viewport["chrom"]
start = viewport["start"]
end = viewport["end"]
overview = viewport["overview"]
vis_rev.config["viewport"] = {"chrom": chrom, "start": start, "end": end, "overview": overview}
else:
# Default action is to save the config as is with no validation.
vis_rev.config = config

vis.latest_revision = vis_rev
session.add(vis_rev)
with transaction(session):
session.commit()
encoded_id = trans.security.encode_id(vis.id)
return {"vis_id": encoded_id, "url": url_for(controller="visualization", action=vis.type, id=encoded_id)}

def get_tool_def(self, trans, hda):
"""Returns definition of an interactive tool for an HDA."""

Expand Down Expand Up @@ -741,10 +660,14 @@ def get_visualization_config(self, trans, visualization):
bookmarks = latest_revision.config.get("bookmarks", [])

def pack_track(track_dict):
dataset_id = track_dict["dataset_id"]
unencoded_id = track_dict.get("dataset_id")
if unencoded_id:
encoded_id = trans.security.encode_id(unencoded_id)
else:
encoded_id = track_dict["dataset"]["id"]
hda_ldda = track_dict.get("hda_ldda", "hda")
dataset_id = trans.security.encode_id(dataset_id)
dataset = self.get_hda_or_ldda(trans, hda_ldda, dataset_id)

dataset = self.get_hda_or_ldda(trans, hda_ldda, encoded_id)
try:
prefs = track_dict["prefs"]
except KeyError:
Expand Down
1 change: 1 addition & 0 deletions lib/galaxy/webapps/galaxy/buildapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@ def populate_api_routes(webapp, app):
"/plugins/visualizations/{visualization_name}/saved",
controller="visualization",
action="saved",
conditions={"method": ["POST"]},
)
# Deprecated in favor of POST /api/workflows with 'workflow' in payload.
webapp.mapper.connect(
Expand Down
54 changes: 1 addition & 53 deletions lib/galaxy/webapps/galaxy/controllers/visualization.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging
from json import loads

from markupsafe import escape
from paste.httpexceptions import (
Expand Down Expand Up @@ -148,21 +147,6 @@ def _display_by_username_and_slug(self, trans, username, slug, **kwargs):
)
)

@web.json
def save(self, trans, vis_json=None, type=None, id=None, title=None, dbkey=None, annotation=None, **kwargs):
"""
Save a visualization; if visualization does not have an ID, a new
visualization is created. Returns JSON of visualization.
"""
# Get visualization attributes from kwargs or from config.
vis_config = loads(vis_json)
vis_type = type or vis_config["type"]
vis_id = id or vis_config.get("id", None)
vis_title = title or vis_config.get("title", None)
vis_dbkey = dbkey or vis_config.get("dbkey", None)
vis_annotation = annotation or vis_config.get("annotation", None)
return self.save_visualization(trans, vis_config, vis_type, vis_id, vis_title, vis_dbkey, vis_annotation)

@web.legacy_expose_api
@web.require_login("edit visualizations")
def edit(self, trans, payload=None, **kwd):
Expand Down Expand Up @@ -284,12 +268,8 @@ def _handle_plugin_error(self, trans, visualization_name, exception):
@web.require_login("use Galaxy visualizations", use_panels=True)
def saved(self, trans, id=None, revision=None, type=None, config=None, title=None, **kwargs):
"""
Save (on POST) or load (on GET) a visualization then render.
Load a visualization and render it.
"""
# TODO: consider merging saved and render at this point (could break saved URLs, tho)
if trans.request.method == "POST":
self._POST_to_saved(trans, id=id, revision=revision, type=type, config=config, title=title, **kwargs)

# check the id and load the saved visualization
if id is None:
return HTTPBadRequest("A valid visualization id is required to load a visualization")
Expand All @@ -304,38 +284,6 @@ def saved(self, trans, id=None, revision=None, type=None, config=None, title=Non
except Exception as exception:
self._handle_plugin_error(trans, visualization.type, exception)

def _POST_to_saved(self, trans, id=None, revision=None, type=None, config=None, title=None, **kwargs):
"""
Save the visualiztion info (revision, type, config, title, etc.) to
the Visualization at `id` or to a new Visualization if `id` is None.
Uses POST/redirect/GET after a successful save, redirecting to GET.
"""
DEFAULT_VISUALIZATION_NAME = "Unnamed Visualization"

# post to saved in order to save a visualization
if type is None or config is None:
return HTTPBadRequest("A visualization type and config are required to save a visualization")
if isinstance(config, str):
config = loads(config)
title = title or DEFAULT_VISUALIZATION_NAME

# TODO: allow saving to (updating) a specific revision - should be part of UsesVisualization
# TODO: would be easier if this returned the visualization directly
# check security if posting to existing visualization
if id is not None:
self.get_visualization(trans, id, check_ownership=True, check_accessible=False)
# ??: on not owner: error raised, but not returned (status = 200)
# TODO: there's no security check in save visualization (if passed an id)
returned = self.save_visualization(trans, config, type, id, title)

# redirect to GET to prevent annoying 'Do you want to post again?' dialog on page reload
render_url = web.url_for(controller="visualization", action="saved", id=returned.get("vis_id"))
return trans.response.send_redirect(render_url)

#
# Visualizations.
#
@web.expose
@web.require_login()
def trackster(self, trans, **kwargs):
Expand Down

0 comments on commit 0019941

Please sign in to comment.