Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3c149c3
Merge pull request #185 from ontoportal-lirmm/development
syphax-bouazzouni Feb 14, 2025
905f76e
Feature: add mod-api SemanticArtefactCatalogRecord model (#202)
imadbourouche Apr 8, 2025
068c0b8
Feature: add hydra pagination to mod models (#204)
imadbourouche Apr 22, 2025
f67735f
Fix: add catalog attributes and fix distribution attributes (#207)
imadbourouche Apr 24, 2025
913ff00
handle non existing uploadFilePath in distribution bytesize (#208)
imadbourouche Apr 25, 2025
a4633fb
index embedded agents as json (#209)
maboukerfa May 7, 2025
905eb1c
exclude serialized methods from nested (embedded) resources (#211)
maboukerfa May 16, 2025
821b389
limit agents embedded affiliation attributes (#212)
maboukerfa May 16, 2025
a7fba21
Fix: change links from global to local && change catalog links to use…
imadbourouche May 20, 2025
28d4083
Revert "exclude serialized methods from nested (embedded) resources (…
imadbourouche May 21, 2025
6b976ec
exclude serialized methods from nested (embedded) agents (#214)
maboukerfa May 21, 2025
766c6b0
Feature: add keywords loading during agent serialization (#205)
maboukerfa May 22, 2025
4c4d4e0
add :groups, :categories, :relatedAgents, :affiliatedAgents attributs…
imadbourouche May 28, 2025
eb4b482
Remove `groups` from agents serialized attributes (#216)
maboukerfa Jun 25, 2025
abcb926
add subjects loading during agent serialization
maboukerfa Jun 26, 2025
8ce21d0
add agents subjects attribute
maboukerfa Jun 27, 2025
2014407
Hotfix: embed agents attributes in catalog
imadbourouche Jun 27, 2025
6a8e779
fix: test artefact catalog
imadbourouche Jun 27, 2025
b110083
fix test test_goo_attrs_to_load catalog
imadbourouche Jun 27, 2025
505fbb9
QA fixes MOD-API (#218)
imadbourouche Jul 2, 2025
3d6024b
Merge pull request #210 from ontoportal-lirmm/development
imadbourouche Jul 3, 2025
8348628
Merge branch 'master' of https://github.com/ontoportal-lirmm/ontologi…
galviset Jul 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 0 additions & 32 deletions config/config.rb.sample
Original file line number Diff line number Diff line change
Expand Up @@ -92,38 +92,6 @@ begin
link: 'https://www.googleapis.com/oauth2/v3/userinfo'
}
}

config.ui_name = 'Bioportal'
config.title = 'NCBO BioPortal'
config.description = "The world's most comprehensive repository of biomedical ontologies"
config.color = '#234979'
config.logo = ''
config.fundedBy = [
{
img_src: 'https://identity.stanford.edu/wp-content/uploads/sites/3/2020/07/block-s-right.png',
url: 'https://www.stanford.edu',

},
{
img_src: 'https://ontoportal.org/images/logo.png',
url: 'https://ontoportal.org/',
}
]
config.federated_portals = {
'agroportal' => {
api: 'http://data.agroportal.lirmm.fr',
ui: 'http://agroportal.lirmm.fr',
apikey: '1cfae05f-9e67-486f-820b-b393dec5764b',
color: '#1e2251'
},
'bioportal' => {
api: 'http://data.bioontology.org',
ui: 'http://bioportal.bioontology.org',
apikey: '4a5011ea-75fa-4be6-8e89-f45c8c84844e',
color: '#234979'
},

}
end
rescue NameError => e
binding.pry
Expand Down
17 changes: 15 additions & 2 deletions config/schemes/semantic_artefact_catalog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,28 @@ fundedBy:
helpText: "Founder of the catalog"
example: ''
enforcedValues: [img_src: "", url: "" ]
default: []
default: [
{
img_src: 'https://ontoportal.org/images/logo.png',
url: 'https://ontoportal.org/'
}
]

federated_portals:
display: "general"
label: "Federated Portals"
helpText: "The Federated portal"
example: ''
enforcedValues: [name: "", api: "", ui: "", apikey: "", color: "" ]
default: []
default: [
{
name: 'agroportal',
api: 'http://data.agroportal.lirmm.fr',
ui: 'http://agroportal.lirmm.fr',
apikey: "DUMMY_API_KEY_123456",
color: '#3cb371'
}
]

homepage:
display: "general"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,25 @@ def bring(*attributes)
hash[model][attr] = mapped_attr
end

populate_from_self(grouped_attributes[self.class]) if grouped_attributes[self.class].any?
fetch_from_ontology(grouped_attributes[:ontology]) if grouped_attributes[:ontology].any?
fetch_from_submission(grouped_attributes[:ontology_submission]) if grouped_attributes[:ontology_submission].any?
fetch_from_metrics(grouped_attributes[:metric]) if grouped_attributes[:metric].any?
end

private

def populate_from_self(attributes)
attributes.each_key do |attr|
if self.class.handler?(attr)
send(attr)
else
value = self.class.default(attr)
value = value.call(self) if value.is_a?(Proc)
send("#{attr}=", value || (respond_to?(attr) ? send(attr) : nil))
end
end
end

def fetch_from_ontology(attributes)
return if attributes.empty?
Expand All @@ -30,21 +43,21 @@ def fetch_from_ontology(attributes)

def fetch_from_submission(attributes)
return if attributes.empty?
@latest ||= defined?(@ontology) ? @ontology.latest_submission(status: :ready) : @submission
return unless @latest
@latest.bring(*attributes.values)
@submission_to_fetch_from ||= defined?(@submission) ? @submission : defined?(@ontology) ? @ontology.latest_submission(status: :ready) : nil
return unless @submission_to_fetch_from
@submission_to_fetch_from.bring(*attributes.values)
attributes.each do |attr, mapped_attr|
self.send("#{attr}=", @latest.send(mapped_attr)) if @latest.respond_to?(mapped_attr)
self.send("#{attr}=", @submission_to_fetch_from.send(mapped_attr)) if @submission_to_fetch_from.respond_to?(mapped_attr)
end
end

def fetch_from_metrics(attributes)
return if attributes.empty?
@latest ||= defined?(@ontology) ? @ontology.latest_submission(status: :ready) : @submission
return unless @latest
@latest.bring(metrics: [attributes.values])
@submission_to_fetch_from ||= defined?(@submission) ? @submission : defined?(@ontology) ? @ontology.latest_submission(status: :ready) : nil
return unless @submission_to_fetch_from
@submission_to_fetch_from.bring(metrics: [attributes.values])
attributes.each do |attr, mapped_attr|
metric_value = @latest.metrics&.respond_to?(mapped_attr) ? @latest.metrics.send(mapped_attr) || 0 : 0
metric_value = @submission_to_fetch_from.metrics&.respond_to?(mapped_attr) ? @submission_to_fetch_from.metrics.send(mapped_attr) || 0 : 0
self.send("#{attr}=", metric_value)
end
end
Expand Down
158 changes: 155 additions & 3 deletions lib/ontologies_linked_data/models/agents/agent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,31 @@ class Agent < LinkedData::Models::Base
attribute :affiliations, enforce: %i[Agent list is_organization], namespace: :org, property: :memberOf
attribute :creator, type: :user, enforce: [:existence]
embed :identifiers, :affiliations
embed_values affiliations: LinkedData::Models::Agent.goo_attrs_to_load + [identifiers: LinkedData::Models::AgentIdentifier.goo_attrs_to_load]
serialize_methods :usages
serialize_methods :usages, :keywords, :groups, :categories, :subjects, :relatedAgents, :affiliatedAgents
embed_values affiliations: [:name, :agentType, :homepage, :acronym, :email, :identifiers]

prevent_serialize_when_nested :usages, :affiliations, :keywords, :groups, :categories, :subjects, :relatedAgents, :affiliatedAgents

write_access :creator
access_control_load :creator

enable_indexing(:agents_metadata)

def embedded_doc
"#{self.name} #{self.acronym} #{self.email} #{self.agentType}"
{
"id": "#{self.id}",
"name": "#{self.name}",
"acronym": "#{self.acronym}",
"email": "#{self.email}",
"agentType": "#{self.agentType}"
}.to_json
end

def self.load_agents_usages(agents = [], agent_attributes = OntologySubmission.agents_attr_uris)
q = Goo.sparql_query_client.select(:id, :property, :agent, :status).distinct.from(LinkedData::Models::OntologySubmission.uri_type).where([:id,LinkedData::Models::OntologySubmission.attribute_uri(:submissionStatus),:status], [:id, :property, :agent])
q = q.filter("?status = <#{RDF::URI.new(LinkedData::Models::SubmissionStatus.id_prefix + 'RDF')}> || ?status = <#{RDF::URI.new(LinkedData::Models::SubmissionStatus.id_prefix + 'UPLOADED')}>")
q = q.filter(agent_attributes.map{|attr| "?property = <#{attr}>"}.join(' || '))
q = q.values(:agent, *agents.map { |agent| RDF::URI(agent.id.to_s)})

data = q.each_solution.group_by{|x| x[:agent]}

Expand All @@ -57,6 +66,116 @@ def usages(force_update: false)
@usages
end

def self.load_agents_keywords(agent)
if agent.usages.empty?
keywords = []
else
q = Goo.sparql_query_client.select(:keywords).distinct.from(LinkedData::Models::OntologySubmission.uri_type).where([:id, :property, :agent], [:id, LinkedData::Models::OntologySubmission.attribute_uri(:keywords), :keywords])
q = q.filter("?agent = <#{agent.id}>")
q = q.values(:id, *agent.usages.keys.map { |uri| RDF::URI(uri.to_s)})


keywords = q.solutions.map { |solution| solution[:keywords].to_s }
end
agent.instance_variable_set("@keywords", keywords)
agent.loaded_attributes.add(:keywords)
end
def keywords(force_update: false)
self.class.load_agents_keywords(self) if !instance_variable_defined?("@keywords") || force_update
@keywords
end

def self.load_agents_categories(agent)
if agent.usages.empty?
categories = []
else
uris = agent.class.strip_submission_id_from_uris(agent.usages.keys)

q = Goo.sparql_query_client.select(:categories).distinct.from(LinkedData::Models::Ontology.uri_type)
q = q.optional([:id, LinkedData::Models::Ontology.attribute_uri(:hasDomain), :categories])
q = q.values(:id, *uris)

categories = q.solutions.map { |solution| solution[:categories] || solution["categories"] }.compact.uniq.reject(&:empty?)
end
agent.instance_variable_set("@categories", categories)
agent.loaded_attributes.add("categories")
end

def categories
self.class.load_agents_categories(self)
@categories
end

def self.load_agents_subjects(agent)
if agent.usages.empty?
subjects = []
else
uris = agent.usages.keys
q = Goo.sparql_query_client.select(:subjects).distinct.from(LinkedData::Models::OntologySubmission.uri_type)
q = q.optional([:id, LinkedData::Models::OntologySubmission.attribute_uri(:hasDomain), :subjects])
q = q.values(:id, *uris)

subjects = q.solutions
.map { |solution| solution[:subjects] || solution["subjects"] }
.compact
.map(&:to_s)
.reject(&:empty?)
.uniq
end
agent.instance_variable_set("@subjects", subjects)
agent.loaded_attributes.add("subjects")
end

def subjects
self.class.load_agents_subjects(self)
@subjects
end

def self.load_related_agents(agent)
if agent.usages.empty?
relatedAgents = []
else
q = Goo.sparql_query_client.select(:id, :agent).distinct.from(LinkedData::Models::OntologySubmission.uri_type).where([:id, :property, :agent])
q = q.filter(OntologySubmission.agents_attr_uris.map{|attr| "?property = <#{attr}>"}.join(' || '))
q = q.values(:id, *agent.usages.keys.map { |uri| RDF::URI(uri.to_s)})
relatedAgentsIds = q.each_solution.group_by{|x| x[:agent].to_s}
.reject { |agent_id, _| agent_id == agent.id.to_s }
.transform_values { |solutions| solutions.map { |s| s[:id] } }
# map the previously fetched usages
relatedAgents = self.fetch_agents_data(relatedAgentsIds.keys)
.reject { |ag| ag.id == agent.id }
.each { |agent| agent.usages = relatedAgentsIds[agent.id.to_s].map(&:to_s).uniq }
.map { |agent| agent.to_h.reject { |k, _| [:klass, :aggregates, :unmapped].include?(k) }}
end
agent.instance_variable_set("@relatedAgents", relatedAgents)
agent.loaded_attributes.add(:relatedAgents)
end

def relatedAgents
self.class.load_related_agents(self) if !instance_variable_defined?("@relatedAgents")
@relatedAgents
end

def self.load_affiliated_agents(agent)
return nil unless agent.agentType == 'organization'
q = Goo.sparql_query_client.select(:id).distinct.from(LinkedData::Models::Agent.uri_type)
q = q.where([:id, LinkedData::Models::Agent.attribute_uri(:affiliations), :agent])
q = q.values(:agent, *agent.id)

affiliatedAgentsIds = q.solutions.map { |solution| solution[:id].to_s }.uniq
affiliatedAgents = self.fetch_agents_data(affiliatedAgentsIds).map { |agent| agent.to_h.reject { |k, _| [:klass, :aggregates, :unmapped].include?(k) }}


agent.instance_variable_set("@affiliatedAgents", affiliatedAgents)
agent.loaded_attributes.add(:affiliatedAgents)

end

def affiliatedAgents
self.class.load_affiliated_agents(self) if !instance_variable_defined?("@affiliatedAgents")
@affiliatedAgents
end

def unique_identifiers(inst, attr)
inst.bring(attr) if inst.bring?(attr)
identifiers = inst.send(attr)
Expand All @@ -83,6 +202,39 @@ def is_organization(inst, attr)

[]
end
def self.fetch_agents_data(affiliated_agents_ids)
return [] if affiliated_agents_ids.empty?

agent_ids = affiliated_agents_ids.map(&:to_s).uniq

q = Goo.sparql_query_client
.select(:id, :name, :acronym, :agentType)
.distinct
.from(LinkedData::Models::Agent.uri_type)
.where(
[:id, LinkedData::Models::Agent.attribute_uri(:name), :name],
[:id, LinkedData::Models::Agent.attribute_uri(:agentType), :agentType]
)
.optional([:id, LinkedData::Models::Agent.attribute_uri(:acronym), :acronym])
.values(:id, *agent_ids.map { |uri| RDF::URI(uri.to_s) })

q.solutions.map do |agent|
LinkedData::Models::Agent.read_only(
id: agent[:id].to_s,
name: agent[:name].to_s,
acronym: agent[:acronym].to_s,
agentType: agent[:agentType].to_s,
usages: nil
)
end
end

def self.strip_submission_id_from_uris(uris)
uris.map do |uri|
cleaned_uri = uri.to_s.sub(%r{/submissions/\d+$}, '')
RDF::URI(cleaned_uri)
end
end
end
end
end
59 changes: 59 additions & 0 deletions lib/ontologies_linked_data/models/mod/hydra_page.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
module LinkedData
module Models
class HydraPage < Goo::Base::Page

def convert_hydra_page(options, &block)
{
'@id': get_request_path(options),
'@type': 'hydra:Collection',
totalItems: self.aggregate,
itemsPerPage: self.size,
view: generate_hydra_page_view(options, self.page_number, self.total_pages),
member: map { |item| item.to_flex_hash(options, &block) }
}
end

def self.generate_hydra_context
{
'hydra': 'http://www.w3.org/ns/hydra/core#',
'Collection': 'hydra:Collection',
'member': 'hydra:member',
'totalItems': 'hydra:totalItems',
'itemsPerPage': 'hydra:itemsPerPage',
'view': 'hydra:view',
'firstPage': 'hydra:first',
'lastPage': 'hydra:last',
'previousPage': 'hydra:previous',
'nextPage': 'hydra:next',
}
end

private

def generate_hydra_page_view(options, page, page_count)
request_path = get_request_path(options)
params = options[:request] ? options[:request].params.dup : {}

build_url = ->(page_number) {
query = Rack::Utils.build_nested_query(params.merge("page" => page_number.to_s))
request_path ? "#{request_path}?#{query}" : "?#{query}"
}

{
"@id": build_url.call(page),
"@type": "hydra:PartialCollectionView",
firstPage: build_url.call(1),
previousPage: page > 1 ? build_url.call(page - 1) : nil,
nextPage: page < page_count ? build_url.call(page + 1) : nil,
lastPage: page_count != 0 ? build_url.call(page_count) : build_url.call(1)
}
end

def get_request_path(options)
request_path = options[:request] ? "#{LinkedData.settings.rest_url_prefix.chomp("/")}#{options[:request].path}" : nil
request_path
end

end
end
end
6 changes: 6 additions & 0 deletions lib/ontologies_linked_data/models/mod/mod_base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module LinkedData
module Models
class ModBase < LinkedData::Models::Base
end
end
end
Loading
Loading