Skip to content

Commit

Permalink
Merge pull request seek4science#1770 from seek4science/galaxy-link-re…
Browse files Browse the repository at this point in the history
…based

Store link back to Galaxy instance for workflows
  • Loading branch information
fbacall authored Mar 5, 2024
2 parents 322076f + c7d822b commit 6493c70
Show file tree
Hide file tree
Showing 30 changed files with 365 additions and 66 deletions.
2 changes: 1 addition & 1 deletion app/controllers/workflows_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ def workflow_params
{ creator_ids: [] }, { assay_assets_attributes: [:assay_id] },
{ publication_ids: [] }, { presentation_ids: [] }, { document_ids: [] }, { data_file_ids: [] }, { sop_ids: [] },
{ workflow_data_files_attributes:[:id, :data_file_id, :workflow_data_file_relationship_id, :_destroy] },
:internals, :maturity_level, :source_link_url,
:internals, :maturity_level, :source_link_url, :execution_instance_url,
{ topic_annotations: [] }, { operation_annotations: [] },
{ discussion_links_attributes: [:id, :url, :label, :_destroy] },
{ git_version_attributes: [:name, :comment, :ref, :commit, :root_path,
Expand Down
6 changes: 0 additions & 6 deletions app/helpers/workflows_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,6 @@ def test_status_badge(resource)
end
end

def run_workflow_url(workflow_version)
if workflow_version.workflow_class_title == 'Galaxy'
"#{Seek::Config.galaxy_instance_trs_import_url}&trs_id=#{workflow_version.parent.id}&trs_version=#{workflow_version.version}"
end
end

def workflow_class_options_for_select(selected = nil)
opts = WorkflowClass.order(:title).map do |c|
extra = {}
Expand Down
1 change: 1 addition & 0 deletions app/models/asset_link.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class AssetLink < ApplicationRecord
DISCUSSION = 'discussion'.freeze
SOURCE = 'source'.freeze
EXECUTION_INSTANCE = 'execution_instance'.freeze
MISC_LINKS = 'misc'.freeze

scope :discussion, -> { where(link_type: AssetLink::DISCUSSION) }
Expand Down
18 changes: 17 additions & 1 deletion app/models/concerns/workflow_extraction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,23 @@ def structure
delegate :inputs, :outputs, :steps, to: :structure

def can_run?
can_download?(nil) && workflow_class_title == 'Galaxy' && Seek::Config.galaxy_instance_trs_import_url.present?
can_download?(nil) && workflow_class&.executable? && run_url.present?
end

def run_url
if workflow_class&.key == 'galaxy'
base = execution_instance_url || Seek::Config.galaxy_instance_default
return if base.nil?

parent_id = is_a_version? ? parent.id : id
url = URI(base) + 'workflows/trs_import'
params = {
trs_url: Seek::Util.routes.ga4gh_trs_v2_tool_version_url(parent_id, version_id: version),
run_form: true
}
url.query = URI.encode_www_form(params)
url.to_s
end
end

def diagram_exists?
Expand Down
16 changes: 12 additions & 4 deletions app/models/git/blob.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ class Blob
delegate :read, :rewind, to: :file

attr_reader :git_version, :path
# Flag to decide if `read`ing this blob should eagerly fetch any remote content pointed to by this blob's `url`.
attr_accessor :fetch_remote

alias_method :original_filename, :path

def initialize(git_version, blob, path)
@git_version = git_version
@blob = blob
@path = path
@fetch_remote = false
end

def annotations
Expand All @@ -26,15 +30,15 @@ def url
git_version.remote_sources[path]
end

def file(fetch_remote: false)
def file(fetch_remote: @fetch_remote)
@file ||= to_tempfile(fetch_remote: fetch_remote)
end

def binread
file_contents
end

def file_contents(as_text: false, fetch_remote: false, &block)
def file_contents(as_text: false, fetch_remote: @fetch_remote, &block)
if fetch_remote && remote? && !fetched?
if block_given?
block.call(remote_content)
Expand Down Expand Up @@ -92,13 +96,17 @@ def text_contents_for_search

def remote_content
return unless remote?
handler = ContentBlob.remote_content_handler_for(url)
handler = remote_content_handler
return unless handler
io = handler.fetch
io.rewind
io
end

def remote_content_handler
ContentBlob.remote_content_handler_for(url)
end

def cache_key
"#{git_repository.cache_key}/blobs/#{oid}"
end
Expand Down Expand Up @@ -134,7 +142,7 @@ def is_text?

private

def to_tempfile(fetch_remote: false)
def to_tempfile(fetch_remote: @fetch_remote)
f = Tempfile.new(path)
f.binmode if binary?
f << file_contents(as_text: !binary?, fetch_remote: fetch_remote)
Expand Down
19 changes: 19 additions & 0 deletions app/models/workflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class Workflow < ApplicationRecord

accepts_nested_attributes_for :workflow_data_files

has_one :execution_instance, -> { where(link_type: AssetLink::EXECUTION_INSTANCE) },
class_name: 'AssetLink', as: :asset, dependent: :destroy, inverse_of: :asset, autosave: true

def initialize(*args)
@extraction_errors = []
@extraction_warnings = []
Expand Down Expand Up @@ -131,6 +134,10 @@ def source_link_url
parent&.source_link&.url
end

def execution_instance_url
parent&.execution_instance&.url
end

def submit_to_life_monitor
LifeMonitorSubmissionJob.perform_later(self)
end
Expand Down Expand Up @@ -237,6 +244,18 @@ def update_test_status(status, ver = version)
v.save!
end

def execution_instance_url= url
(execution_instance || build_execution_instance).assign_attributes(url: url)

execution_instance.mark_for_destruction if url.blank?

url
end

def execution_instance_url
execution_instance&.url
end

has_filter maturity: Seek::Filtering::Filter.new(
value_field: 'maturity_level',
label_mapping: ->(values) {
Expand Down
4 changes: 4 additions & 0 deletions app/models/workflow_class.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ def extractable?
extractor.present?
end

def executable?
key == 'galaxy'
end

def self.extractable
where.not(extractor: nil)
end
Expand Down
2 changes: 1 addition & 1 deletion app/views/assets/_asset_buttons.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<%= button_link_to('Download RO Crate', 'ro_crate_file', ro_crate_workflow_path(asset, version: version, code: params[:code]),
'data-tooltip' => tooltip("The Workflow RO-Crate is a package containing the workflow definition, its metadata and supporting resources like test data")) %>
<% if asset.can_run? %>
<%= button_link_to("Run on #{Seek::Config.galaxy_instance_name || 'Galaxy'}", 'run_galaxy', run_workflow_url(display_asset)) %>
<%= button_link_to("Run on Galaxy", 'run_galaxy', display_asset.run_url) %>
<% end %>
<% else %>
<%= button_link_to('Download RO-Crate', 'ro_crate_file', nil,
Expand Down
8 changes: 8 additions & 0 deletions app/views/workflows/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@
<%= f.text_area :description, rows: 5, class: 'form-control rich-text-edit' -%>
</div>

<% if @workflow.workflow_class&.executable? %>
<div class="form-group">
<label>Galaxy Instance</label>
<%= f.text_field :execution_instance_url, placeholder: 'https://usegalaxy.eu/', class: 'form-control' -%>
<p class="help-block">The root URL of the Galaxy instance where this workflow originated from.</p>
</div>
<% end %>

<div class="form-group">
<label>Source</label>
<%= f.text_field :source_link_url, placeholder: 'https://...', class: 'form-control' -%>
Expand Down
8 changes: 8 additions & 0 deletions app/views/workflows/provide_metadata.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@
<%= text_area_tag 'workflow[description]', @workflow.description, class: "form-control rich-text-edit" -%>
</div>

<% if @workflow.workflow_class&.executable? %>
<div class="form-group">
<label>Galaxy Instance</label>
<%= text_field_tag 'workflow[execution_instance_url]', @workflow.execution_instance_url, placeholder: 'https://usegalaxy.eu/', class: 'form-control' -%>
<p class="help-block">The root URL of the Galaxy instance where this workflow originated from.</p>
</div>
<% end %>

<div class="form-group">
<label>Source</label>
<%= text_field_tag 'workflow[source_link_url]', @workflow.source_link_url, placeholder: 'https://...', class: "form-control" -%>
Expand Down
1 change: 1 addition & 0 deletions config/initializers/seek_testing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,5 +139,6 @@ def load_seek_testing_defaults!
Settings.defaults[:git_support_enabled] = true
Settings.defaults[:fair_signposting_enabled] = true
Settings.defaults[:bio_tools_enabled] = true
Settings.defaults[:galaxy_instance_default] = 'https://usegalaxy.eu'
end
end
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
get 'tools' => 'tools#index'
get 'tools/:id' => 'tools#show'
get 'tools/:id/versions' => 'tool_versions#index'
get 'tools/:id/versions/:version_id' => 'tool_versions#show'
get 'tools/:id/versions/:version_id' => 'tool_versions#show', as: :tool_version
get 'tools/:id/versions/:version_id/containerfile' => 'tool_versions#containerfile'
get 'tools/:id/versions/:version_id/:type/descriptor(/*relative_path)' => 'tool_versions#descriptor', constraints: { relative_path: /.+/ }, format: false, as: :tool_versions_descriptor
get 'tools/:id/versions/:version_id/:type/files' => 'tool_versions#files', format: false
Expand Down
4 changes: 2 additions & 2 deletions lib/seek/config_setting_attributes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,8 @@ life_monitor_url:
life_monitor_client_id:
life_monitor_client_secret:
life_monitor_ui_url:
galaxy_instance_name:
galaxy_instance_trs_import_url:
galaxy_instance_default:
galaxy_instance_trs_server:
# Controlled vocabs
cv_dropdown_limit:
convert: :to_i
Expand Down
30 changes: 21 additions & 9 deletions lib/seek/download_handling/galaxy_http_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,20 @@ module DownloadHandling
class GalaxyHTTPHandler < Seek::DownloadHandling::HTTPHandler
attr_reader :galaxy_host, :workflow_id

def initialize(url, fallback_to_get: true)
uri = URI(url)
URL_PATTERNS = [
/(.+)\/api\/workflows\/([^\/]+)\/download\?format=json-download/, # Download
/(.+)\/workflows\/run\?id=([^&]+)/, # Run
/(.+)\/published\/workflow\?id=([^&]+)/, # View
].freeze

@galaxy_host = URI(url.split(/\/workflows?\//).first + '/')
@workflow_id = CGI.parse(uri.query)['id'].first
def initialize(url, fallback_to_get: true)
URL_PATTERNS.each do |pattern|
matches = url.match(pattern)
if matches
@galaxy_host = matches[1].chomp('/') + '/'
@workflow_id = matches[2]
end
end

super(download_url, fallback_to_get: fallback_to_get)
end
Expand All @@ -26,21 +35,24 @@ def info
end

def display_url
URI.join(galaxy_host, "workflow/display_by_id?id=#{workflow_id}").to_s
URI.join(galaxy_host, "published/workflow?id=#{workflow_id}").to_s
end

def download_url
URI.join(galaxy_host, "workflow/export_to_file?id=#{workflow_id}").to_s
URI.join(galaxy_host, "api/workflows/#{workflow_id}/download?format=json-download").to_s
end

# Note that the path is `/workflows/` (plural) here for some reason.
def run_url
URI.join(galaxy_host, "workflows/run?id=#{workflow_id}").to_s
end

def execution_instance_url
galaxy_host.to_s
end

def self.is_galaxy_workflow_url?(uri)
uri.hostname.include?('galaxy') && (uri.path.include?('/workflow/') || uri.path.include?('/workflows/')) &&
uri.query.present? && CGI.parse(uri.query)&.key?('id')
string_uri = uri.to_s
uri.hostname.include?('galaxy') && URL_PATTERNS.any? { |pattern| string_uri.match?(pattern) }
end
end
end
Expand Down
26 changes: 25 additions & 1 deletion lib/seek/workflow_extractors/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ def initialize(io)
end

def metadata
{ }
m = {}

if @io.respond_to?(:remote_content_handler)
m.merge!(extract_source_metadata(@io.remote_content_handler))
end

m
end

def has_tests?
Expand Down Expand Up @@ -84,6 +90,24 @@ def extract_author(obj)

author
end

def extract_source_metadata(handler)
m = {}
return m unless handler
source_url = nil
if handler.respond_to?(:repository_url)
source_url = handler.repository_url
elsif handler.respond_to?(:display_url)
source_url = handler.display_url
end
m[:source_link_url] = source_url

if handler.respond_to?(:execution_instance_url)
m[:execution_instance_url] = handler.execution_instance_url
end

m
end
end
end
end
2 changes: 1 addition & 1 deletion lib/seek/workflow_extractors/cff.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class CFF
FILENAME = 'CITATION.cff'

def initialize(io)
if io.respond_to?(:path)
if io.respond_to?(:path) && !io.is_a?(Git::Blob)
@path = io.path
else
f = Tempfile.new('cff')
Expand Down
5 changes: 3 additions & 2 deletions lib/seek/workflow_extractors/galaxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ def self.file_extensions
end

def metadata
metadata = super
galaxy_string = @io.read
f = Tempfile.new('ga')
f.binmode
Expand All @@ -21,13 +22,13 @@ def metadata
end
cf.rewind
if status.success?
metadata = Seek::WorkflowExtractors::CWL.new(cf).metadata
metadata.merge!(Seek::WorkflowExtractors::CWL.new(cf).metadata)
else
metadata = super
metadata[:warnings] ||= []
metadata[:warnings] << 'Unable to convert workflow to CWL, some metadata may be missing.'
Rails.logger.error("Galaxy -> CWL conversion failed. Error was: #{err}")
end

galaxy = JSON.parse(galaxy_string)

if galaxy.has_key?('name')
Expand Down
2 changes: 1 addition & 1 deletion lib/seek/workflow_extractors/git_repo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def file_exists?(path)
end

def file(path)
@obj.get_blob(path)&.file(fetch_remote: true)
@obj.get_blob(path)&.tap { |blob| blob.fetch_remote = true }
end

def licensee_project
Expand Down
2 changes: 1 addition & 1 deletion lib/seek/workflow_extractors/knime.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def self.file_extensions
def metadata
metadata = super

metadata.merge(parse_internal_workflow(extract_workflow))
metadata.merge!(parse_internal_workflow(extract_workflow))

metadata
end
Expand Down
Loading

0 comments on commit 6493c70

Please sign in to comment.