Skip to content

Commit

Permalink
Merge pull request #524 from seek4science/rocrate-jsonld
Browse files Browse the repository at this point in the history
Rocrate jsonld
  • Loading branch information
alaninmcr authored Apr 14, 2021
2 parents cb2359b + 5155faa commit 5dbfd93
Show file tree
Hide file tree
Showing 15 changed files with 236 additions and 76 deletions.
55 changes: 44 additions & 11 deletions app/models/concerns/workflow_extraction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def populate_ro_crate(crate)
wf = crate.main_workflow || ROCrate::Workflow.new(crate, c.filepath, c.original_filename)
wf.content_size = c.file_size
crate.main_workflow = wf
crate.main_workflow.programming_language = ROCrate::ContextualEntity.new(crate, nil, workflow_class&.ro_crate_metadata || Seek::WorkflowExtractors::Base::NULL_CLASS_METADATA)
# crate.main_workflow.programming_language = ROCrate::ContextualEntity.new(crate, nil, extractor_class.ro_crate_metadata)

begin
d = diagram
Expand All @@ -88,22 +88,55 @@ def populate_ro_crate(crate)
rescue WorkflowDiagram::UnsupportedFormat
end

authors = creators.map { |person| crate.add_person(nil, person.ro_crate_metadata) }
others = other_creators&.split(',')&.collect(&:strip)&.compact || []
authors += others.map.with_index { |name, i| crate.add_person("creator-#{i + 1}", name: name) }
crate.author = authors
crate['provider'] = projects.map { |project| crate.add_organization(nil, project.ro_crate_metadata).reference }
crate.license = license
crate.identifier = ro_crate_identifier
crate.url = ro_crate_url('ro_crate')
crate['isBasedOn'] = source_link_url if source_link_url
crate['sdPublisher'] = crate.add_person(nil, contributor.ro_crate_metadata).reference
crate['sdDatePublished'] = Time.now

workflow = is_a_version? ? self.parent : self
merge_entities(crate, workflow) if workflow

crate['isBasedOn'] = source_link_url if source_link_url && !crate['isBasedOn']
crate['sdDatePublished'] = Time.now unless crate['sdDatePublished']
crate['creativeWorkStatus'] = I18n.t("maturity_level.#{maturity_level}") if maturity_level

crate.preview.template = PREVIEW_TEMPLATE
# brute force deletion as I cannot track down where it comes from
crate.contextual_entities.delete_if { |c| c['@id'] == '#ro-crate-preview.html' }
crate
end

def merge_entities(crate, workflow)
workflow_struct = Seek::BioSchema::Serializer.new(workflow).json_representation

context = {
'@vocab' => 'https://somewhere.com/'
}
workflow_struct['@context'] = context
crate['name'] = "Research Object Crate for #{workflow_struct['name']}"
crate['description'] = workflow_struct['description']

workflow_struct.except!('encodingFormat')

flattened = JSON::LD::API.flatten(workflow_struct, context)
flattened.except!('@context')

flattened['@graph'].each do |elem|
type = elem['@type']
type = [type] unless type.is_a?(Array)
if type.include?('ComputationalWorkflow')
merge_fields(crate.main_workflow, elem)
else
entity_class = ROCrate::ContextualEntity.specialize(elem)
entity = entity_class.new(crate, elem['@id'], elem)
crate.add_contextual_entity(entity)
end
end
end

def merge_fields(crate_workflow, bioschemas_workflow)
bioschemas_workflow.each do |key, value|
crate_workflow[key] = value unless crate_workflow[key]
end
end

def ro_crate
inner = proc do |crate|
populate_ro_crate(crate) if should_generate_crate?
Expand Down
2 changes: 1 addition & 1 deletion app/models/workflow_crate_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def build
crate.main_workflow = ROCrate::Workflow.new(crate, workflow[:data], get_unique_filename(crate, workflow))
crate.main_workflow.programming_language = crate.add_contextual_entity(ROCrate::ContextualEntity.new(crate, nil, workflow_class&.ro_crate_metadata || Seek::WorkflowExtractors::Base::NULL_CLASS_METADATA))
crate.main_workflow['url'] = workflow[:data_url] if workflow[:data_url].present?
if diagram[:data].present?
if diagram && diagram[:data].present?
crate.main_workflow.diagram = ROCrate::WorkflowDiagram.new(crate, diagram[:data], get_unique_filename(crate, diagram))
crate.main_workflow.diagram['url'] = diagram[:data_url] if diagram[:data_url].present?
end
Expand Down
3 changes: 2 additions & 1 deletion lib/seek/bio_schema/data_catalog_mock_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ def provider
{
'@type' => 'Organization',
'name' => Seek::Config.dm_project_name,
'url' => Seek::Config.dm_project_link
'url' => Seek::Config.dm_project_link,
'@id' => Seek::Config.dm_project_link
}
end

Expand Down
3 changes: 2 additions & 1 deletion lib/seek/bio_schema/resource_decorators/base_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module ResourceDecorators
# The Decorator is an extension to the resource that provided or alters the properties of that resource
# for Schema.org (Bioschemas.org)
class BaseDecorator

include ActionView::Helpers::SanitizeHelper
include Rails.application.routes.url_helpers

Expand All @@ -26,7 +27,7 @@ def attributes

# The @context to be used for the JSON-LD
def context
'http://schema.org'
Seek::BioSchema::Serializer::SCHEMA_ORG
end

# The schema.org @type .
Expand Down
6 changes: 4 additions & 2 deletions lib/seek/bio_schema/resource_decorators/creative_work.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module BioSchema
module ResourceDecorators
# Decorator that provides extensions for a Event
class CreativeWork < Thing

associated_items producer: :projects

schema_mappings license: :license,
Expand All @@ -11,7 +12,8 @@ class CreativeWork < Thing
created_at: :dateCreated,
updated_at: :dateModified,
content_type: :encodingFormat,
subject_of: :subjectOf
subject_of: :subjectOf,
provider: :sdPublisher

def content_type
return unless resource.respond_to?(:content_blob) && resource.content_blob
Expand All @@ -25,7 +27,7 @@ def license

def all_creators
others = other_creators&.split(',')&.collect(&:strip)&.compact || []
others = others.collect { |name| { "@type": 'Person', "name": name } }
others = others.collect { |name| { "@type": 'Person', "@id": "##{ROCrate::Entity.format_id(name)}", "name": name } }
all = (mini_definitions(creators) || []) + others
return if all.empty?
all
Expand Down
10 changes: 9 additions & 1 deletion lib/seek/bio_schema/resource_decorators/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,23 @@ module BioSchema
module ResourceDecorators
# Decorator that provides extensions for a Event
class Event < Thing

EVENT_PROFILE = 'https://bioschemas.org/profiles/Event/0.2-DRAFT-2019_06_14/'

associated_items contact: :contributors,
host_institution: :projects
schema_mappings contact: :contact,
start_date: :startDate,
end_date: :endDate,
event_type: :eventType,
location: :location,
host_institution: :hostInstitution
host_institution: :hostInstitution,
conformsTo: "dct:conformsTo"

def conformsTo
EVENT_PROFILE
end

def contributors
[contributor]
end
Expand Down
9 changes: 8 additions & 1 deletion lib/seek/bio_schema/resource_decorators/person.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@ module BioSchema
module ResourceDecorators
# Decorator that provides extensions for a Person
class Person < Thing
PERSON_PROFILE = 'https://bioschemas.org/profiles/Person/0.2-DRAFT-2019_07_19/'

associated_items member_of: :projects

schema_mappings first_name: :givenName,
last_name: :familyName,
image: :image,
member_of: :memberOf,
orcid: :orcid
orcid: :orcid,
conformsTo: "dct:conformsTo"

def conformsTo
PERSON_PROFILE
end

def url
web_page.blank? ? identifier : web_page
end
Expand Down
9 changes: 9 additions & 0 deletions lib/seek/bio_schema/resource_decorators/thing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ def keywords
tags_as_text_array.join(', ') if resource.respond_to?(:tags_as_text_array)
end

def provider
{
'@type' => 'Organization',
'@id' => Seek::Config.site_base_host,
'name' => Seek::Config.dm_project_name,
'url' => Seek::Config.site_base_host
}
end

def date_created
resource.date_created.try(:utc) if resource.respond_to?(:date_created)
end
Expand Down
40 changes: 30 additions & 10 deletions lib/seek/bio_schema/resource_decorators/workflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,67 @@ module BioSchema
module ResourceDecorators
# Decorator that provides extensions for a Workflow
class Workflow < CreativeWork
associated_items sd_publisher: :contributors

schema_mappings sd_publisher: :sdPublisher,
version: :version,
WORKFLOW_PROFILE = 'https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE/'

FORMALPARAMETER_PROFILE = 'https://bioschemas.org/profiles/FormalParameter/1.0-RELEASE/'

schema_mappings version: :version,
image: :image,
programming_language: :programmingLanguage,
producer: :producer,
inputs: :input,
outputs: :output
outputs: :output,
license: :license,
conformsTo: "dct:conformsTo"

def contributors
[contributor]
end

def conformsTo
WORKFLOW_PROFILE
end

def image
return unless resource.diagram_exists?
diagram_workflow_url(resource, version: resource.version, host: Seek::Config.site_base_host)
end

def schema_type
'ComputationalWorkflow'
['File', 'SoftwareSourceCode', 'ComputationalWorkflow']
end

def programming_language
resource.workflow_class&.title
resource.workflow_class&.extractor_class.ro_crate_metadata
end

def inputs
formal_parameters(resource.inputs)
formal_parameters(resource.inputs, 'inputs')
end

def outputs
formal_parameters(resource.outputs)
formal_parameters(resource.outputs, 'outputs')
end

def license
Seek::License.find(resource.license)&.url
end

private

def formal_parameters(properties)
def formal_parameters(properties, group_name)
if self.title
wf_name = self.title.downcase.gsub(/[^0-9a-z]/i, '_')
else
wf_name = 'dummy'
end
properties.collect do |property|
{
"@type": 'FormalParameter',
name: property.id
"@id": "##{wf_name}-#{group_name}-#{property.id}",
name: property.name || property.id,
"dct:conformsTo": FORMALPARAMETER_PROFILE
}
end
end
Expand Down
16 changes: 11 additions & 5 deletions lib/seek/bio_schema/serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,28 @@ class Serializer
include ActionView::Helpers::SanitizeHelper
attr_reader :resource

SCHEMA_ORG = 'https://schema.org'

# initialise with a resource
def initialize(resource)
@resource = resource
end

def json_representation
repr = {
'@context' => resource_decorator.context,
'@type' => resource_decorator.schema_type
}.merge(attributes_json)
repr.deep_stringify_keys
end

# returns the JSON-LD as a String, for the resource
def json_ld
unless supported?
raise UnsupportedTypeException, "Bioschema not supported for #{resource.class.name}"
end
json = {
'@context' => resource_decorator.context,
'@type' => resource_decorator.schema_type
}.merge(attributes_json)

JSON.pretty_generate(json)
JSON.pretty_generate(json_representation)
end

# whether the resource BioSchema was initialized with is supported
Expand Down
18 changes: 10 additions & 8 deletions test/functional/workflows_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ def setup
post :create_ro_crate, params: {
ro_crate: {
workflow: { data: fixture_file_upload('files/checksums.txt') },
diagram: { data: fixture_file_upload('files/file_picture.png') },
diagram: { data: fixture_file_upload('files/file_picture.png') },
abstract_cwl: { data: fixture_file_upload('files/workflows/rp2-to-rp2path-packed.cwl') }
},
workflow_class_id: cwl.id
Expand Down Expand Up @@ -584,7 +584,8 @@ def setup
end
assert_response :success
assert wf = assigns(:workflow)
crate_workflow = wf.ro_crate.main_workflow
workflow_crate = ROCrate::WorkflowCrateReader.read_zip(wf.content_blob.path)
crate_workflow = workflow_crate.main_workflow
assert crate_workflow
assert_equal 'file%20with%20spaces%20in%20name.txt', crate_workflow.id
end
Expand Down Expand Up @@ -639,16 +640,16 @@ def setup
post :create_ro_crate, params: {
ro_crate: {
workflow: { data: fixture_file_upload('files/workflows/rp2-to-rp2path-packed.cwl') },
diagram: { data: fixture_file_upload('files/file_picture.png') },
diagram: { data: fixture_file_upload('files/file_picture.png') },
abstract_cwl: { data: fixture_file_upload('files/workflows/rp2-to-rp2path-packed.cwl') }
},
workflow_class_id: cwl.id
}
end
assert_response :success
assert wf = assigns(:workflow)
crate_workflow = wf.ro_crate.main_workflow
crate_cwl = wf.ro_crate.main_workflow_cwl
workflow_crate = ROCrate::WorkflowCrateReader.read_zip(assigns(:workflow).content_blob.path)
crate_workflow = workflow_crate.main_workflow
crate_cwl = workflow_crate.main_workflow_cwl
assert_not_equal crate_workflow.id, crate_cwl.id
end

Expand Down Expand Up @@ -760,15 +761,16 @@ def setup
post :create_ro_crate, params: {
ro_crate: {
workflow: { data_url: 'https://github.com/bob/workflow/blob/master/workflow.txt' },
diagram: { data_url: 'https://github.com/bob/workflow/blob/master/diagram.png' },
diagram: { data_url: 'https://github.com/bob/workflow/blob/master/diagram.png' },
abstract_cwl: { data_url: 'https://github.com/bob/workflow/blob/master/abstract.cwl' }
},
workflow_class_id: cwl.id
}
end
assert_response :success
assert wf = assigns(:workflow)
crate_workflow = wf.ro_crate.main_workflow
workflow_crate = ROCrate::WorkflowCrateReader.read_zip(wf.content_blob.path)
crate_workflow = workflow_crate.main_workflow
assert crate_workflow
assert_equal 'workflow.txt', crate_workflow.id
end
Expand Down
7 changes: 4 additions & 3 deletions test/unit/bio_schema/data_catalog_mock_model_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ def setup
with_config_value(:dm_project_name, 'WIBBLE') do
with_config_value(:dm_project_link, 'http://wibble.eu') do
expected = {
'@type' => 'Organization',
'name' => 'WIBBLE',
'url' => 'http://wibble.eu'
"@type"=>"Organization",
"@id"=>"http://wibble.eu",
"name"=>"WIBBLE",
"url"=>"http://wibble.eu"
}
assert_equal expected, @data_catalogue.provider
end
Expand Down
Loading

0 comments on commit 5dbfd93

Please sign in to comment.