Path: blob/main/_plugins/jekyll-jsonld.rb
2607 views
# frozen_string_literal: true12require 'json'3require './_plugins/gtn'4require './_plugins/gtn/git'5require './_plugins/util'67module Jekyll8module Filters910# Generate JSON-LD metadata for the GTN.11module JsonldFilter12GTN = {13'@type': 'Organization',14'http://purl.org/dc/terms/conformsTo': {15# Bioschemas profile16'@id': 'https://bioschemas.org/profiles/Organization/0.3-DRAFT',17'@type': 'CreativeWork'18},19id: 'https://training.galaxyproject.org',20email: '[email protected]',21name: 'Galaxy Training Network',22description: 'An organization providing a collection of tutorials developed and maintained by the worldwide Galaxy community',23legalName: 'Galaxy Training Network',24alternateName: 'GTN',25url: 'https://training.galaxyproject.org',26logo: 'https://training.galaxyproject.org/training-material/assets/images/GTNLogo1000.png',27keywords: %w[galaxy bioinformatics training fair accessible],28status: 'active',29foundingDate: Gtn::Git.discover['founding_date'].to_s,30}.freeze3132A11Y = {33accessMode: %w[textual visual],34accessModeSufficient: %w[textual visual],35# "accessibilityAPI": ,36accessibilityControl: %w[fullKeyboardControl fullMouseControl],37accessibilityFeature: %w[alternativeText tableOfContents],38# "accessibilityHazard": [],39accessibilitySummary: 'The text aims to be as accessible as possible. Image descriptions will vary per ' \40'tutorial, from images being completely inaccessible, to images with good descriptions ' \41'for non-visual users.',42}.freeze4344EDU_ROLES = {45'use' => 'Students',46'admin-dev' => 'Galaxy Administrators',47'basics' => 'Students',48'data-science' => 'Data-Science Students',49'instructors' => 'Instructors',50}5152##53# Generate the Dublin Core metadata for a material.54# Parmaeters:55# +material+:: The material to generate the metadata for.56# +site+:: The site object.57# Returns:58# A string containing the metadata.59#60# Example:61# {{ material | generate_dublin_core: site }}62# => <meta name="DC.identifier" content="..." />63def generate_dublin_core(material, site)64return if material.key?('data') && material['data'].fetch('type', 'none') != 'tutorial_hands_on'6566attributes = [67['DC.identifier', site['github_repository']],68['DC.type', 'text'],69['DC.title', material['title']],70['DC.publisher', 'Galaxy Training Network'],71['DC.date', Gtn::ModificationTimes.obtain_time(material['path'])]72]7374attributes += Gtn::Contributors.get_authors(material).map do |user|75['DC.creator', Gtn::Contributors.fetch_name(site, user)]76end7778attributes.map { |a, b| "<meta name=\"#{a}\" content=\"#{b}\">" }.join("\n")79end8081##82# Generate the JSON-LD metadata for a person83# Parameters:84# +id+:: The id of the person.85# +contributor+:: The contributor object from CONTRIBUTORS.yaml.86# +site+:: The site object.87# Returns:88# +Hash+:: The JSON-LD metadata.89#90# Example:91# generate_person_jsonld("hexylena", site['data']['contributors']['hexylena'], site)92# => {93# "@context": "https://schema.org",94# "@type": "Person",95# "http://purl.org/dc/terms/conformsTo": {96# # Bioschemas profile97# "@id": "https://bioschemas.org/profiles/Person/0.2-DRAFT-2019_07_19",98# "@type": "Person"99# },100# "url": "https://training.galaxyproject.org/hall-of-fame/hexylena/",101# "mainEntityOfPage": "https://training.galaxyproject.org/hall-of-fame/hexylena/",102# "name": "hexylena",103# "image": "https://avatars.githubusercontent.com/hexylena",104# "description": "A contributor to the GTN project.",105# "memberOf": [...],106# "identifier": "https://orcid.org/0000-0002-6601-2165",107# "orcid": "https://orcid.org/0000-0002-6601-2165"108# }109#110def generate_person_jsonld(id, contributor, site)111member_of = Gtn::Contributors.fetch_contributor(site, id)['affiliations'] || []112member_of = member_of.map do |org_id|113org = Gtn::Contributors.fetch_contributor(site, org_id)114generate_org_jsonld(org_id, org, site)115end116117person = {118'@context': 'https://schema.org',119'@type': 'Person',120'http://purl.org/dc/terms/conformsTo': {121'@id': 'https://bioschemas.org/profiles/Person/0.3-DRAFT',122'@type': 'CreativeWork'123},124# I guess these are identical?125url: "#{site['url']}#{site['baseurl']}/hall-of-fame/#{id}/",126mainEntityOfPage: "#{site['url']}#{site['baseurl']}/hall-of-fame/#{id}/",127name: Gtn::Contributors.fetch_name(site, id),128image: "https://avatars.githubusercontent.com/#{id}",129# No clue what to put here it's a person.130description: if contributor.nil?131'A contributor to the GTN project.'132else133contributor.fetch('bio',134'A contributor to the GTN project.')135end,136memberOf: [GTN] + member_of,137}138if !contributor.nil? && contributor.key?('orcid') && contributor['orcid']139person['identifier'] = "https://orcid.org/#{contributor['orcid']}"140person['@id'] = "https://orcid.org/#{contributor['orcid']}"141end142143person144end145146##147# Generate the JSON-LD metadata for an organisation148# Parameters:149# +id+:: The id of the org.150# +contributor+:: The contributor object from ORGANISATIONS.yaml.151# +site+:: The site object.152# Returns:153# +Hash+:: The JSON-LD metadata.154def generate_org_jsonld(id, contributor, site)155organization = {156'@context': 'https://schema.org',157'@type': 'Organization',158'http://purl.org/dc/terms/conformsTo': {159'@id': 'https://bioschemas.org/profiles/Organization/0.3-DRAFT',160'@type': 'CreativeWork'161},162id: "#{site['url']}#{site['baseurl']}/hall-of-fame/#{id}/",163name: Gtn::Contributors.fetch_name(site, id),164description: 'An organization supporting the Galaxy Training Network',165}166167organization['url'] = contributor['url'] if contributor.key?('url') && contributor['url']168169organization170end171172##173# Generate the JSON-LD metadata for a funding organisation174# Parameters:175# +id+:: The id of the person.176# +contributor+:: The contributor object from ORGANISATIONS.yaml.177# +site+:: The site object.178# Returns:179# +Hash+:: The JSON-LD metadata.180def generate_funder_jsonld(id, contributor, site)181{182'@context': 'https://schema.org',183'@type': 'Organization',184'http://purl.org/dc/terms/conformsTo': {185'@id': 'https://bioschemas.org/profiles/Organization/0.3-DRAFT',186'@type': 'CreativeWork'187},188name: Gtn::Contributors.fetch_name(site, id),189description: contributor.fetch('funding_statement', 'An organization supporting the Galaxy Training Network'),190url: contributor.fetch('url', "https://training.galaxyproject.org/training-material/hall-of-fame/#{id}/"),191logo: contributor.fetch('avatar', "https://github.com/#{id}.png"),192}193end194195##196# Generate the JSON-LD metadata for a grant197# Parameters:198# +id+:: The id of the grant.199# +contributor+:: The contributor object from GRANTS.yaml.200# +site+:: The site object.201# Returns:202# +Hash+:: The JSON-LD metadata.203def generate_grant_jsonld(id, contributor, site)204organization = {205'@context': 'https://schema.org',206'@type': 'Grant',207identifier: contributor['funding_id'],208url: Gtn::Contributors.fetch_funding_url(contributor) || contributor['url'],209funder: generate_funder_jsonld(id, contributor, site)210}211212organization['startDate'] = contributor['start_date'] if contributor.key?('start_date')213organization['endDate'] = contributor['end_date'] if contributor.key?('end_date')214215organization216end217218##219# Generate the JSON-LD metadata for a person, funder, or organisation as JSON.220# Parameters:221# +id+:: The id of the person.222# +site+:: The site object.223# +json+:: Should the output be rendered as JSON (only really used in contributor page.)224# Returns:225# +String+:: The JSON-LD metadata.226def to_pfo_jsonld(id, site, json: true)227contributor = Gtn::Contributors.fetch_contributor(site, id)228d = if Gtn::Contributors.person?(site, id)229generate_person_jsonld(id, contributor, site)230elsif Gtn::Contributors.grant?(site, id)231generate_grant_jsonld(id, contributor, site)232else233generate_org_jsonld(id, contributor, site)234end235236if json237JSON.pretty_generate(d)238else239d240end241end242243##244# Generate the JSON-LD metadata for a news article (blog)245# Parameters:246# +page+:: The page object.247# +site+:: The +Jekyll::Site+ site object.248# Returns:249# +Hash+:: The JSON-LD metadata.250def generate_news_jsonld(page, site)251authors = Gtn::Contributors.get_authors(page.to_h).map do |x|252to_pfo_jsonld(x, site, json: false)253end254255data = {256'@context': 'https://schema.org',257'@type': 'BlogPosting',258url: "#{site['url']}#{site['baseurl']}#{page['url']}",259name: page['title'],260headline: page.excerpt[0..100].gsub(/\n/, ' '), # TODO: remove html tags.261keywords: page['tags'] || [],262description: page.excerpt[0..100].gsub(/\n/, ' '), # TODO: remove html tags263articleBody: page.content, # TODO: remove html tags264datePublished: page.date,265dateModified: Gtn::ModificationTimes.obtain_time(page.path),266author: authors,267publisher: GTN,268mainEntityOfPage: {269'@type': 'WebPage',270'@id': "#{site['url']}#{page['url']}"271},272image: {273'@type': 'ImageObject',274width: 60,275height: 60,276url: "#{site['baseurl']}/assets/images/GTN-60px.png"277}278}279data.update(A11Y)280281JSON.pretty_generate(data)282end283284##285# Generate the JSON-LD metadata for an event286# Parameters:287# +page+:: The page object.288# +site+:: The +Jekyll::Site+ site object.289# Returns:290# +Hash+:: The JSON-LD metadata.291#292# Examples:293# {{ page | generate_event_jsonld: site }}294def generate_event_jsonld(page, site)295organisers = Gtn::Contributors.get_organisers(page.to_h).map do |x|296to_pfo_jsonld(x, site, json: false)297end298instructors = Gtn::Contributors.get_instructors(page.to_h).map do |x|299to_pfo_jsonld(x, site, json: false)300end301funders = Gtn::Contributors.get_funders(site, page.to_h).map do |x|302to_pfo_jsonld(x, site, json: false)303end304funding = Gtn::Contributors.get_grants(site, page.to_h).map do |x|305to_pfo_jsonld(x, site, json: false)306end307308materials = []309if page['program']310page['program'].each do |section|311if !section.key? 'tutorials'312next313end314315section['tutorials'].each do |tutorial|316if tutorial.key?('custom')317next318end319320material = Gtn::TopicFilter.fetch_tutorial_material(site, tutorial['topic'], tutorial['name'])321materials.push(material)322end323end324end325materials.compact!326327# Extract EDAM terms from all materials328edam_terms = materials.map do |material|329material.fetch('edam_ontology', []).map do |term|330{331'@type': 'DefinedTerm',332'@id': "http://edamontology.org/#{term}",333inDefinedTermSet: 'http://edamontology.org',334termCode: term,335}336end337end.flatten.uniq338339learning_objectives = materials.map do |material|340material.fetch('objectives', [])341end.flatten.compact342343# TODO: add topic edam terms too? Not sure.344parts = []345materials.each do |material|346mat = generate_material_jsonld(material, site['data'][material['topic_name']], site)347if !mat.nil? && !mat.empty?348parts.push(mat)349end350end351352if page['program']353syllab = page['program'].reject { |s| s['section'].nil? }.map do |section|354{355'@type': 'Syllabus',356name: section['section'],357description: section.fetch('description', nil),358}359end360end361362data = {363'@context': 'https://schema.org',364'@type': 'Course',365url: "#{site['url']}#{site['baseurl']}#{page['url']}",366name: page['title'],367keywords: page['tags'] || [],368description: page['description'],369370about: edam_terms, # TeSS, "scientific topics".371audience: page['audience'], # TeSS: target audience372# If 'online' is present in the mode, the course is online.373# Will fail on "this is NOT an online course"374# Acceptable.375courseMode: page['mode'],376startDate: page['date_start'],377endDate: page['date_end'],378organizer: organisers, # TeSS only, US spelling, non-standard379380location: page['location'], # TODO, TeSS location381teaches: learning_objectives, # TeSS, "learning objectives"382# timeRequired: 'P1D', # TeSS, "duration", TODO: calculate from start/end date, not implemented in scraper currently.383384availableLanguage: ['en'], # TODO: support other languages385inLanguage: ['en'], # TODO: support other languages386# courseCode387# coursePrerequisites388# educationalCredentialAwarded389# financialAidEligible390# hasCourseInstance391# numberOfCredits392# occupationalCredentialAwarded393# syllabusSections394# totalHistoricalEnrollment395396# assesses397# competencyRequired398# educationalAlignment399# educationalLevel400# educationalUse401# learningResourceType402# teaches403404funder: funders, # Org or person405funding: funding, # Grant406publisher: GTN,407provider: GTN,408syllabusSections: syllab,409# Session materials410# TODO: not currently parsed by TeSS, google just complains about it, so we're leaving it out.411# hasPart: parts,412}413414begin415data['dateModified'] = Gtn::ModificationTimes.obtain_time(page.path)416data['datePublished'] = Gtn::PublicationTimes.obtain_time(page.path)417rescue StandardError418data['dateModified'] = Gtn::ModificationTimes.obtain_time(page['path'])419data['datePublished'] = Gtn::PublicationTimes.obtain_time(page['path'])420end421422if page['cover']423data['image'] = if page['cover'] =~ /^http/424[page['cover']]425else426["#{site['url']}#{site['baseurl']}#{page['cover']}"]427end428end429430# We CANNOT guarantee A11Y431# data.update(A11Y)432if page['cost'] and page['cost'].downcase == 'free'433data['isAccessibleForFree'] = true434offer = {435'@type': 'Offer',436price: 0,437priceCurrency: 'EUR',438category: 'Free',439isAccessibleForFree: true,440}441elsif page['cost']442data['isAccessibleForFree'] = false443offer = {444'@type': 'Offer',445price: page['cost'].split[0],446priceCurrency: page['cost'].split[1],447isAccessibleForFree: false,448category: 'Paid',449# TODO: this can be more advanced but we need to collect start/end times, and timezone.450}451end452453# TODO: this is wrong in a whole host of scenarios like incl weekends.454course_days = (page.fetch('date_end', page['date_start']) - page['date_start']).to_i455if course_days < 1456course_days = 1457end458data['hasCourseInstance'] = [459{460'@type': 'CourseInstance',461courseMode: page['mode'],462# courseWorkload: "A daily course running from #{page['date_start']} to #{page['date_end']}",463offers: offer,464instructor: instructors,465isAccessibleForFree: data['isAccessibleForFree'],466courseSchedule: {467'@type': 'Schedule',468startDate: page['date_start'],469endDate: page.fetch('date_end', page['date_start']),470repeatCount: course_days,471repeatFrequency: 'daily', # Contrary to schema.org spec, this is what Google wants.472},473courseWorkload: "P#{course_days}D",474}475]476477data['offers'] = [offer]478479if page.key?('location') && page['location'].keys.length > 1480data['location'] = {481'@type': 'Place',482name: page['location']['name'],483address: {484'@type': 'PostalAddress',485streetAddress: page['location'].fetch('address', nil),486addressLocality: page['location'].fetch('city', nil),487addressRegion: page['location'].fetch('region', nil),488postalCode: page['location'].fetch('postcode', nil),489addressCountry: page['location'].fetch('country', nil)490}491}492end493494JSON.pretty_generate(data)495end496497##498# Generate the JSON-LD metadata for a learning pathway499# Parameters:500# +page+:: The page object.501# +site+:: The +Jekyll::Site+ site object.502# Returns:503# +Hash+:: The JSON-LD metadata.504#505# Examples:506# {{ page | generate_learning_pathway_jsonld: site }}507def generate_learning_pathway_jsonld(page, site)508materials = []509page['pathway'].each do |section|510if !section.key? 'tutorials'511next512end513514section['tutorials'].each do |tutorial|515if tutorial.key?('custom')516next517end518519material = Gtn::TopicFilter.fetch_tutorial_material(site, tutorial['topic'], tutorial['name'])520materials.push(material)521end522end523materials.compact!524525# Extract EDAM terms from all materials526edam_terms = materials.map do |material|527material.fetch('edam_ontology', []).map do |term|528{529'@type': 'DefinedTerm',530'@id': "http://edamontology.org/#{term}",531inDefinedTermSet: 'http://edamontology.org',532termCode: term,533}534end535end.flatten.uniq536537learning_objectives = materials.map do |material|538material.fetch('objectives', [])539end.flatten.compact540541funders = materials.map do |material|542Gtn::Contributors.get_funders(site, material).map do |x|543to_pfo_jsonld(x, site, json: false)544end545end.flatten.uniq.compact546547funding = materials.map do |material|548Gtn::Contributors.get_grants(site, material).map do |x|549to_pfo_jsonld(x, site, json: false)550end551end.flatten.uniq.compact552553# TODO: add topic edam terms too? Not sure.554parts = []555materials.each do |material|556mat = generate_material_jsonld(material, site['data'][material['topic_name']], site)557if !mat.nil? && !mat.empty?558parts.push(mat)559end560end561562syllab = page['pathway'].reject { |s| s['section'].nil? }.map do |section|563{564'@type': 'Syllabus',565name: section['section'],566description: section.fetch('description', nil),567}568end569570data = {571'@context': 'https://schema.org',572'@type': 'Course',573url: "#{site['url']}#{site['baseurl']}#{page['url']}",574name: "Learning Pathway #{page['title']}",575keywords: page['tags'] || [],576description: page['description'],577about: edam_terms, # TeSS, "scientific topics".578audience: page['audience'], # TeSS: target audience579teaches: learning_objectives, # TeSS, "learning objectives"580availableLanguage: ['en'], # TODO: support other languages581inLanguage: ['en'], # TODO: support other languages582# courseCode583# coursePrerequisites584# educationalCredentialAwarded585# financialAidEligible586# hasCourseInstance587# numberOfCredits588# occupationalCredentialAwarded589# syllabusSections590# totalHistoricalEnrollment591592# assesses593# competencyRequired594# educationalAlignment595# educationalLevel596# educationalUse597# learningResourceType598# teaches599600funder: funders, # Org or person601funding: funding, # Grant602publisher: GTN,603provider: GTN,604syllabusSections: syllab,605# Session materials606# TODO: not currently parsed by TeSS, google just complains about it, so we're leaving it out.607# hasPart: parts,608}609610begin611data['dateModified'] = Gtn::ModificationTimes.obtain_time(page.path)612data['datePublished'] = Gtn::PublicationTimes.obtain_time(page.path)613rescue StandardError614data['dateModified'] = Gtn::ModificationTimes.obtain_time(page['path'])615data['datePublished'] = Gtn::PublicationTimes.obtain_time(page['path'])616end617618if page['cover']619data['image'] = if page['cover'] =~ /^http/620[page['cover']]621else622["#{site['url']}#{site['baseurl']}#{page['cover']}"]623end624end625626# We CANNOT guarantee A11Y627# data.update(A11Y)628data['isAccessibleForFree'] = true629offer = {630'@type': 'Offer',631price: 0,632priceCurrency: 'EUR',633category: 'Free',634isAccessibleForFree: true,635}636data['offers'] = [offer]637638# TODO: this is basically just wrong.639data['hasCourseInstance'] = [640{641'@type': 'CourseInstance',642courseMode: 'online',643offers: offer,644isAccessibleForFree: data['isAccessibleForFree'],645}646]647648JSON.pretty_generate(data)649end650651##652# Convert a material to JSON-LD, intended to be used in Jekyll Liquid templates.653# Parameters:654# +material+:: The material object.655# +topic+:: The topic object.656# +site+:: The +Jekyll::Site+ site object.657#658# Returns:659# +String+:: The JSON-LD metadata.660#661# Examples:662# {{ material | to_jsonld: topic, site }}663def to_jsonld(material, topic, site)664JSON.pretty_generate(generate_material_jsonld(material, topic, site))665end666667##668# Convert a material to JSON-LD. Intended to be used by the filters which you should call in templates.669#670# Parameters:671# +material+:: The material object.672# +topic+:: The topic object.673# +site+:: The +Jekyll::Site+ site object.674#675# Returns:676# +Hash+:: The JSON-LD metadata.677def generate_material_jsonld(material, topic, site)678langCodeMap = {679"en" => 'English',680"es" => 'Español',681"fr" => 'Français',682}683684eduLevel = {685'Introductory' => 'Beginner',686'Intermediate' => 'Intermediate',687'Advanced' => 'Advanced'688}689return '{}' if !topic690691topic_desc = {692'@type': 'CreativeWork',693name: (topic['title']).to_s,694description: (topic['summary']).to_s,695url: "#{site['url']}#{site['baseurl']}/topics/#{topic['name']}/"696}697698# aggregate everything699data = {700# Properties from Course701'@context': 'http://schema.org',702'@type': 'LearningResource',703704# Required for BioSchemas705'http://purl.org/dc/terms/conformsTo': {706'@id': 'https://bioschemas.org/profiles/TrainingMaterial/1.0-RELEASE',707'@type': 'CreativeWork'708},709710# Properties from CreativeWork711# "about" described below712#713# "accountablePerson":,714# "aggregateRating":,715# "alternativeHeadline":,716# "associatedMedia":,717audience: {718'@type': 'EducationalAudience',719educationalRole: EDU_ROLES[topic['type']]720},721# "audio":,722# "award":,723# "author" described below724# "character":,725citation: [726{727'@type': 'CreativeWork',728name: 'Galaxy Training: A Powerful Framework for Teaching!',729url: 'https://doi.org/10.1371/journal.pcbi.1010752'730},731{732'@type': 'CreativeWork',733name: 'Community-Driven Data Analysis Training for Biology',734url: 'https://doi.org/10.1016/j.cels.2018.05.012'735}736],737# "comment":,738# "commentCount":,739# "contentLocation":,740# "contentRating":,741# "contentReferenceTime":,742# "contributor" described below743# copyrightHolder: GTN,744# copyrightNotice: m745# "copyrightYear":,746# "correction":,747# "creator":,748# "dateCreated":,749# "datePublished":,750discussionUrl: site['gitter_url'],751# "editor":,752# "educationalAlignment":,753# "educationalUse":,754# "encoding":,755# "encodingFormat":,756# "exampleOfWork":,757# "expires":,758# "funder": funding,759# "genre":,760# "hasPart" described below761headline: (material['title']).to_s,762# "interactionStatistic":,763interactivityType: 'mixed',764isAccessibleForFree: true,765# "isBasedOn":,766isFamilyFriendly: true,767# "isPartOf" described below768# "keywords": described below769# "learningResourceType" described below770license: 'https://spdx.org/licenses/CC-BY-4.0.html',771# "locationCreated":,772# "mainEntity":,773# "material":,774# "mentions" described below775# "offers":,776# "position":,777producer: GTN,778provider: GTN,779# "publication":,780# "publisher":,781# "publisherImprint":,782# "publishingPrinciples":,783# "recordedAt":,784# "releasedEvent":,785# "review":,786# "schemaVersion":,787# "sdDatePublished":,788# "sdLicense":,789# "sdPublisher":,790sourceOrganization: GTN,791# "spatialCoverage":,792# "sponsor":,793# "temporalCoverage":,794# "text":,795# "thumbnailUrl":,796# "timeRequired" described below797# "translationOfWork":,798# "translator": Google Translate???,799# "typicalAgeRange":,800# "version":,801# "video":,802# "workExample":,803# "workTranslation":,804805# Properties from Thing806# "additionalType":,807# "alternateName":,808# "description" described below809# "disambiguatingDescription":,810# "image":,811# "mainEntityOfPage":,812# "name" described below813# "potentialAction":,814# "sameAs":,815# "subjectOf":,816# "url" described below817workTranslation: [],818creativeWorkStatus: material['draft'] ? 'Draft' : 'Active',819}820821if material.key?('pub_date')822data['dateModified'] = material['mod_date']823data['datePublished'] = material['pub_date']824else825begin826data['dateModified'] = Gtn::ModificationTimes.obtain_time(material.path)827data['datePublished'] = Gtn::PublicationTimes.obtain_time(material.path)828rescue StandardError829data['dateModified'] = Gtn::ModificationTimes.obtain_time(material['path'])830data['datePublished'] = Gtn::PublicationTimes.obtain_time(material['path'])831end832end833834if material.key?('copyright')835# copyrightHolder: GTN,836data['copyrightNotice'] = material['copyright']837else838# I'm not sure this is accurate.839data['copyrightHolder'] = GTN840end841842funders = Gtn::Contributors.get_funders(site, material).map do |x|843to_pfo_jsonld(x, site, json: false)844end845grants = Gtn::Contributors.get_grants(site, material).map do |x|846to_pfo_jsonld(x, site, json: false)847end848849data['funder'] = funders850data['funding'] = grants851852data['identifier'] = "https://gxy.io/GTN:#{material['short_id']}" if material.key?('short_id')853854data.update(A11Y)855856actual_material = Gtn::TopicFilter.fetch_tutorial_material(site, material['topic_name'], material['tutorial_name'])857858# info depending if tutorial, hands-on or slide level859# parts = []860# data['hasPart'] = parts861862mentions = []863description = []864865data['isPartOf'] = topic_desc866867data['abstract'] = material868.fetch('content', '')869.strip870.split("\n")871.first872873if ! data['abstract'].nil?874data['abstract'] = data['abstract']875.gsub(/\{\{\s*site.baseurl\s*\}\}/, url_prefix(site))876.gsub(/\[{{\s*site.url\s*}}/, '[' + url_prefix(site))877.gsub(/{% link (topics[^%]*).md %}/, url_prefix(site) + '\1.html')878.gsub(/{% link (topics[^%]*).html %}/, url_prefix(site) + '\1.html')879.gsub(/\s*\(?{%\s*cite [^}]+\s*%}\)?/, '')880.gsub('{{ site.github_repository }}', safe_site_config(site, 'github_repository', 'https://example.com'))881.gsub(/{% snippet ([^%]*) %}/, '')882.gsub(/{% include ([^%]*) %}/, '')883end884885description.push("## Abstract\n\n#{data['abstract']}\n\n")886887if (material['name'] == 'tutorial.md') || (material['name'] == 'slides.html')888889if material['name'] == 'tutorial.md'890data['learningResourceType'] = 'e-learning'891description.push("## About This Material\n\nThis is a Hands-on Tutorial from the GTN which is usable either for individual self-study, or as a teaching material in a classroom.\n\n")892else893data['learningResourceType'] = 'slides'894end895896data['name'] = material['title']897data['url'] = "#{site['url']}#{site['baseurl']}#{material['url']}"898899# Requires https://github.com/galaxyproject/training-material/pull/4271900data['version'] = Gtn::ModificationTimes.obtain_modification_count(material['path'])901902# Time required903if material.key?('time_estimation') && !material['time_estimation'].nil?904data['timeRequired'] = "PT#{material['time_estimation'].upcase}"905end906907# Description with questions, objectives and keypoints908if material.key?('questions') && !material['questions'].nil? && material['questions'].length.positive?909questions = material['questions'].join("\n - ")910description.push("## Questions this #{material['type']} will address\n\n - #{questions}\n\n")911end912if material.key?('objectives') && !material['objectives'].nil? && material['objectives'].length.positive?913objectives = material['objectives'].map{|x| "- #{x}"}.join("\n")914description.push("## Learning Objectives\n\n#{objectives}\n\n")915data['teaches'] = objectives916end917if material.key?('keypoints') && !material['keypoints'].nil? && material['keypoints'].length.positive?918keypoints = material['keypoints'].join("\n - ")919description.push("## Key Points\n\n - #{keypoints}\n\n")920end921922# Keywords923data['keywords'] = [topic['title']] + (material['tags'] || [])924# Zenodo links925end926927# Mentions are 'external resources' in TeSS.928# This could be expanded with929# - supported servers930# - tools and resources used (e.g. Galaxy) or tools linked to the TS.931# - slides (if tutorial) and tutorial (if slides)932# - other materials in the same topic?933if actual_material.key?('workflows')934mentions.push({935'@type': 'Thing',936url: "#{site['url']}#{site['baseurl']}#{material['dir']}workflows/",937name: "Associated Workflows"938})939end940941# Notebooks942if actual_material.key?('notebook')943if actual_material['notebook']['language'] != 'r'944# Python, Bash, SQL (all via jupyter)945url = "#{site['url']}#{site['baseurl']}#{material['dir']}#{material['topic_name']}-#{material['tutorial_name']}.ipynb"946mentions.push({947'@type': 'Thing',948url: url,949name: "Jupyter Notebook (with Solutions)"950})951mentions.push({952'@type': 'Thing',953url: url.gsub(/\.ipynb$/, '-course.ipynb'),954name: "Jupyter Notebook (without Solutions)"955})956elsif actual_material['notebook']['language'] == 'r' # Actual R957url = "#{site['url']}#{site['baseurl']}#{material['dir']}#{material['topic_name']}-#{material['tutorial_name']}.Rmd"958mentions.push({959'@type': 'Thing',960url: url,961name: "Quarto/RMarkdown Notebook"962})963mentions.push({964'@type': 'Thing',965url: "https://bio.tools/tool/rstudio",966name: "RStudio"967})968end969end970971# Tools972uses_tools = false973(actual_material['tools'] || []).each do |tool|974if site.data['tool-meta'].nil?975next976end977978toolmeta = site.data['tool-meta'][tool]979if toolmeta.nil?980next981end982983if toolmeta['bio.tools'].length.positive?984mentions.push({985'@type': 'Thing',986url: "https://bio.tools/tool/#{toolmeta['bio.tools']}",987name: toolmeta.fetch('bio.tools_name', toolmeta['name'])988})989end990uses_tools = true991end992if uses_tools993mentions.push({994'@type': 'Thing',995url: "https://bio.tools/tool/galaxy",996name: "Galaxy"997})998end9991000# Zenodo link out1001if actual_material.key?('zenodo_link') && ! actual_material['zenodo_link'].nil?1002if actual_material['zenodo_link'].length.positive?1003mentions.push({1004'@type': 'Thing',1005url: (actual_material['zenodo_link']).to_s,1006name: "Associated Training Datasets"1007})1008end1009end10101011if description.empty?1012description.push(material.fetch('content', '').strip.split("\n").first)1013end1014data['description'] = description.join("\n")10151016data['inLanguage'] = if material.key?('lang')1017{1018'@type': 'Language',1019name: langCodeMap[material['lang']],1020alternateName: material['lang']1021}1022else1023{1024'@type': 'Language',1025name: 'English',1026alternateName: 'en'1027}1028end10291030# Course requirements (material + topic)1031reqs = []1032reqs.push(*topic['requirements']) if topic.key?('requirements')1033reqs.push(*material['requirements']) if material.key?('requirements')1034if !reqs.empty?1035coursePrerequisites = []1036reqs.each do |req|1037if req['type'] == 'internal'1038if req.key?('tutorials')1039(req['tutorials']).each do |tuto|1040(site['pages']).each do |page|1041if ((page['name'] == 'tutorial.md') || (page['name'] == 'slides.html')) &&1042((page['topic_name'] == req['topic_name']) && (page['tutorial_name'] == tuto))1043# slides1044if page['name'] == 'slides.html'1045coursePrerequisites.push(1046{1047'@context': 'http://schema.org',1048'@type': 'LearningResource',1049url: "#{site['url']}#{site['baseurl']}/topics/#{req['topic_name']}/" \1050"tutorials/#{tuto}/slides.html",1051name: (page['title']).to_s,1052description: "Slides for '#{page['title']}' tutorial",1053learningResourceType: 'slides',1054interactivityType: 'expositive',1055provider: GTN1056}1057)1058if page['hands_on_url']1059coursePrerequisites.push(1060{1061'@context': 'http://schema.org',1062'@type': 'LearningResource',1063url: (page['hands_on_url']).to_s,1064learningResourceType: 'e-learning',1065interactivityType: 'expositive',1066}1067)1068end1069end1070# hands-on1071if page['name'] == 'tutorial.md'1072coursePrerequisites.push(1073{1074'@context': 'http://schema.org',1075'@type': 'LearningResource',1076url: "#{site['url']}#{site['baseurl']}/topics/#{req['topic_name']}/tutorials" \1077"/#{tuto}/tutorial.html",1078name: (page['title']).to_s,1079description: "Hands-on for '#{page['title']}' tutorial",1080learningResourceType: 'e-learning',1081interactivityType: 'expositive',1082provider: GTN1083}1084)1085end1086end1087end1088end1089else1090coursePrerequisites.push(1091{1092'@context': 'http://schema.org',1093'@type': 'LearningResource',1094url: "#{site['url']}#{site['baseurl']}/topics/#{req['topic_name']}/",1095name: (site['data'][req['topic_name']]['title']).to_s,1096description: (site['data'][req['topic_name']]['title']).to_s,1097provider: GTN1098}1099)1100end1101elsif req['type'] == 'external'1102coursePrerequisites.push({1103'@type': 'CreativeWork',1104url: (req['link']).to_s,1105name: (req['title']).to_s1106})1107else1108coursePrerequisites.push((req['title']).to_s)1109end1110end1111data['competencyRequired'] = coursePrerequisites.uniq1112end11131114# Add contributors/authors1115if material.key?('contributors') || material.key?('contributions')1116authors = Gtn::Contributors.get_authors(material).map do |x|1117generate_person_jsonld(x, Gtn::Contributors.fetch_contributor(site, x), site)1118end11191120data['author'] = authors1121end11221123# Add non-author contributors1124if material.key?('contributions')1125data['contributor'] = Gtn::Contributors.get_non_authors(material).map do |x|1126generate_person_jsonld(x, site['data']['contributors'][x], site)1127end1128end11291130about = []1131about.push(topic_desc)1132edam_terms = topic.fetch('edam_ontology', []) | material.fetch('edam_ontology', [])11331134about += edam_terms.map do |term|1135{1136'@type': 'DefinedTerm',1137'@id': "http://edamontology.org/#{term}",1138inDefinedTermSet: 'http://edamontology.org',1139termCode: term,1140# "name": ,1141url: 'https://bioportal.bioontology.org/ontologies/EDAM/?p=classes&conceptid=' \1142"http%3A%2F%2Fedamontology.org%2F#{term}"1143}1144end11451146data['about'] = about11471148data['educationalLevel'] = material.key?('level') ? eduLevel[material['level']] : 'Beginner'1149data['mentions'] = mentions11501151data1152end1153end1154end1155end11561157Liquid::Template.register_filter(Jekyll::Filters::JsonldFilter)115811591160