Path: blob/main/_plugins/jekyll-jsonld.rb
1677 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.2-DRAFT-2019_07_19',17'@type': 'Organization'18},19id: 'https://training.galaxyproject.org',20email: '[email protected]',21name: 'Galaxy Training Network',22legalName: 'Galaxy Training Network',23alternateName: 'GTN',24url: 'https://training.galaxyproject.org',25logo: 'https://training.galaxyproject.org/training-material/assets/images/GTNLogo1000.png',26keywords: %w[galaxy bioinformatics training fair accessible],27status: 'active',28foundingDate: Gtn::Git.discover['founding_date'].to_s,29}.freeze3031A11Y = {32accessMode: %w[textual visual],33accessModeSufficient: %w[textual visual],34# "accessibilityAPI": ,35accessibilityControl: %w[fullKeyboardControl fullMouseControl],36accessibilityFeature: %w[alternativeText tableOfContents],37# "accessibilityHazard": [],38accessibilitySummary: 'The text aims to be as accessible as possible. Image descriptions will vary per ' \39'tutorial, from images being completely inaccessible, to images with good descriptions ' \40'for non-visual users.',41}.freeze4243EDU_ROLES = {44'use' => 'Students',45'admin-dev' => 'Galaxy Administrators',46'basics' => 'Students',47'data-science' => 'Data-Science Students',48'instructors' => 'Instructors',49}5051##52# Generate the Dublin Core metadata for a material.53# Parmaeters:54# +material+:: The material to generate the metadata for.55# +site+:: The site object.56# Returns:57# A string containing the metadata.58#59# Example:60# {{ material | generate_dublin_core: site }}61# => <meta name="DC.identifier" content="..." />62def generate_dublin_core(material, site)63return if material.key?('data') && material['data'].fetch('type', 'none') != 'tutorial_hands_on'6465attributes = [66['DC.identifier', site['github_repository']],67['DC.type', 'text'],68['DC.title', material['title']],69['DC.publisher', 'Galaxy Training Network'],70['DC.date', Gtn::ModificationTimes.obtain_time(material['path'])]71]7273attributes += Gtn::Contributors.get_authors(material).map do |user|74['DC.creator', Gtn::Contributors.fetch_name(site, user)]75end7677attributes.map { |a, b| "<meta name=\"#{a}\" content=\"#{b}\">" }.join("\n")78end7980##81# Generate the JSON-LD metadata for a person82# Parameters:83# +id+:: The id of the person.84# +contributor+:: The contributor object from CONTRIBUTORS.yaml.85# +site+:: The site object.86# Returns:87# +Hash+:: The JSON-LD metadata.88#89# Example:90# generate_person_jsonld("hexylena", site['data']['contributors']['hexylena'], site)91# => {92# "@context": "https://schema.org",93# "@type": "Person",94# "http://purl.org/dc/terms/conformsTo": {95# # Bioschemas profile96# "@id": "https://bioschemas.org/profiles/Person/0.2-DRAFT-2019_07_19",97# "@type": "Person"98# },99# "url": "https://training.galaxyproject.org/hall-of-fame/hexylena/",100# "mainEntityOfPage": "https://training.galaxyproject.org/hall-of-fame/hexylena/",101# "name": "hexylena",102# "image": "https://avatars.githubusercontent.com/hexylena",103# "description": "A contributor to the GTN project.",104# "memberOf": [...],105# "identifier": "https://orcid.org/0000-0002-6601-2165",106# "orcid": "https://orcid.org/0000-0002-6601-2165"107# }108#109def generate_person_jsonld(id, contributor, site)110member_of = Gtn::Contributors.fetch_contributor(site, id)['affiliations'] || []111member_of = member_of.map do |org_id|112org = Gtn::Contributors.fetch_contributor(site, org_id)113generate_org_jsonld(org_id, org, site)114end115116person = {117'@context': 'https://schema.org',118'@type': 'Person',119'http://purl.org/dc/terms/conformsTo': {120'@id': 'https://bioschemas.org/profiles/Person/0.3-DRAFT',121'@type': 'CreativeWork'122},123# I guess these are identical?124url: "#{site['url']}#{site['baseurl']}/hall-of-fame/#{id}/",125mainEntityOfPage: "#{site['url']}#{site['baseurl']}/hall-of-fame/#{id}/",126name: Gtn::Contributors.fetch_name(site, id),127image: "https://avatars.githubusercontent.com/#{id}",128# No clue what to put here it's a person.129description: if contributor.nil?130'A contributor to the GTN project.'131else132contributor.fetch('bio',133'A contributor to the GTN project.')134end,135memberOf: [GTN] + member_of,136}137if !contributor.nil? && contributor.key?('orcid') && contributor['orcid']138person['identifier'] = "https://orcid.org/#{contributor['orcid']}"139end140141person142end143144##145# Generate the JSON-LD metadata for an organisation146# Parameters:147# +id+:: The id of the org.148# +contributor+:: The contributor object from ORGANISATIONS.yaml.149# +site+:: The site object.150# Returns:151# +Hash+:: The JSON-LD metadata.152def generate_org_jsonld(id, contributor, site)153organization = {154'@context': 'https://schema.org',155'@type': 'Organization',156'http://purl.org/dc/terms/conformsTo': {157'@id': 'https://bioschemas.org/profiles/Organization/0.3-DRAFT',158'@type': 'CreativeWork'159},160id: "#{site['url']}#{site['baseurl']}/hall-of-fame/#{id}/",161name: Gtn::Contributors.fetch_name(site, id),162description: 'An organization supporting the Galaxy Training Network',163}164165organization['url'] = contributor['url'] if contributor.key?('url') && contributor['url']166167organization168end169170##171# Generate the JSON-LD metadata for a funding organisation172# Parameters:173# +id+:: The id of the person.174# +contributor+:: The contributor object from ORGANISATIONS.yaml.175# +site+:: The site object.176# Returns:177# +Hash+:: The JSON-LD metadata.178def generate_funder_jsonld(id, contributor, site)179{180'@context': 'https://schema.org',181'@type': 'Organization',182'http://purl.org/dc/terms/conformsTo': {183'@id': 'https://bioschemas.org/profiles/Organization/0.3-DRAFT',184'@type': 'CreativeWork'185},186name: Gtn::Contributors.fetch_name(site, id),187description: contributor.fetch('funding_statement', 'An organization supporting the Galaxy Training Network'),188url: contributor.fetch('url', "https://training.galaxyproject.org/training-material/hall-of-fame/#{id}/"),189logo: contributor.fetch('avatar', "https://github.com/#{id}.png"),190}191end192193##194# Generate the JSON-LD metadata for a grant195# Parameters:196# +id+:: The id of the grant.197# +contributor+:: The contributor object from GRANTS.yaml.198# +site+:: The site object.199# Returns:200# +Hash+:: The JSON-LD metadata.201def generate_grant_jsonld(id, contributor, site)202organization = {203'@context': 'https://schema.org',204'@type': 'Grant',205identifier: contributor['funding_id'],206url: Gtn::Contributors.fetch_funding_url(contributor) || contributor['url'],207funder: generate_funder_jsonld(id, contributor, site)208}209210organization['startDate'] = contributor['start_date'] if contributor.key?('start_date')211organization['endDate'] = contributor['end_date'] if contributor.key?('end_date')212213organization214end215216##217# Generate the JSON-LD metadata for a person, funder, or organisation as JSON.218# Parameters:219# +id+:: The id of the person.220# +site+:: The site object.221# +json+:: Should the output be rendered as JSON (only really used in contributor page.)222# Returns:223# +String+:: The JSON-LD metadata.224def to_pfo_jsonld(id, site, json: true)225contributor = Gtn::Contributors.fetch_contributor(site, id)226d = if Gtn::Contributors.person?(site, id)227generate_person_jsonld(id, contributor, site)228elsif Gtn::Contributors.grant?(site, id)229generate_grant_jsonld(id, contributor, site)230else231generate_org_jsonld(id, contributor, site)232end233234if json235JSON.pretty_generate(d)236else237d238end239end240241##242# Generate the JSON-LD metadata for a news article (blog)243# Parameters:244# +page+:: The page object.245# +site+:: The +Jekyll::Site+ site object.246# Returns:247# +Hash+:: The JSON-LD metadata.248def generate_news_jsonld(page, site)249authors = Gtn::Contributors.get_authors(page.to_h).map do |x|250to_pfo_jsonld(x, site, json: false)251end252253data = {254'@context': 'https://schema.org',255'@type': 'BlogPosting',256url: "#{site['url']}#{site['baseurl']}#{page['url']}",257name: page['title'],258headline: page.excerpt[0..100].gsub(/\n/, ' '), # TODO: remove html tags.259keywords: page['tags'] || [],260description: page.excerpt[0..100].gsub(/\n/, ' '), # TODO: remove html tags261articleBody: page.content, # TODO: remove html tags262datePublished: page.date,263dateModified: Gtn::ModificationTimes.obtain_time(page.path),264author: authors,265publisher: GTN,266mainEntityOfPage: {267'@type': 'WebPage',268'@id': "#{site['url']}#{page['url']}"269},270image: {271'@type': 'ImageObject',272width: 60,273height: 60,274url: "#{site['baseurl']}/assets/images/GTN-60px.png"275}276}277data.update(A11Y)278279JSON.pretty_generate(data)280end281282##283# Generate the JSON-LD metadata for an event284# Parameters:285# +page+:: The page object.286# +site+:: The +Jekyll::Site+ site object.287# Returns:288# +Hash+:: The JSON-LD metadata.289#290# Examples:291# {{ page | generate_event_jsonld: site }}292def generate_event_jsonld(page, site)293organisers = Gtn::Contributors.get_organisers(page.to_h).map do |x|294to_pfo_jsonld(x, site, json: false)295end296instructors = Gtn::Contributors.get_instructors(page.to_h).map do |x|297to_pfo_jsonld(x, site, json: false)298end299funders = Gtn::Contributors.get_funders(site, page.to_h).map do |x|300to_pfo_jsonld(x, site, json: false)301end302funding = Gtn::Contributors.get_grants(site, page.to_h).map do |x|303to_pfo_jsonld(x, site, json: false)304end305306materials = []307if page['program']308page['program'].each do |section|309if !section.key? 'tutorials'310next311end312313section['tutorials'].each do |tutorial|314if tutorial.key?('custom')315next316end317318material = Gtn::TopicFilter.fetch_tutorial_material(site, tutorial['topic'], tutorial['name'])319materials.push(material)320end321end322end323materials.compact!324325# Extract EDAM terms from all materials326edam_terms = materials.map do |material|327material.fetch('edam_ontology', []).map do |term|328{329'@type': 'DefinedTerm',330'@id': "http://edamontology.org/#{term}",331inDefinedTermSet: 'http://edamontology.org',332termCode: term,333}334end335end.flatten.uniq336337learning_objectives = materials.map do |material|338material.fetch('objectives', [])339end.flatten.compact340341# TODO: add topic edam terms too? Not sure.342parts = []343materials.each do |material|344mat = generate_material_jsonld(material, site['data'][material['topic_name']], site)345if !mat.nil? && !mat.empty?346parts.push(mat)347end348end349350if page['program']351syllab = page['program'].reject { |s| s['section'].nil? }.map do |section|352{353'@type': 'Syllabus',354name: section['section'],355description: section.fetch('description', nil),356}357end358end359360data = {361'@context': 'https://schema.org',362'@type': 'Course',363url: "#{site['url']}#{site['baseurl']}#{page['url']}",364name: page['title'],365keywords: page['tags'] || [],366description: page['description'],367368about: edam_terms, # TeSS, "scientific topics".369audience: page['audience'], # TeSS: target audience370# If 'online' is present in the mode, the course is online.371# Will fail on "this is NOT an online course"372# Acceptable.373courseMode: page['mode'],374startDate: page['date_start'],375endDate: page['date_end'],376organizer: organisers, # TeSS only, US spelling, non-standard377378location: page['location'], # TODO, TeSS location379teaches: learning_objectives, # TeSS, "learning objectives"380# timeRequired: 'P1D', # TeSS, "duration", TODO: calculate from start/end date, not implemented in scraper currently.381382availableLanguage: ['en'], # TODO: support other languages383inLanguage: ['en'], # TODO: support other languages384# courseCode385# coursePrerequisites386# educationalCredentialAwarded387# financialAidEligible388# hasCourseInstance389# numberOfCredits390# occupationalCredentialAwarded391# syllabusSections392# totalHistoricalEnrollment393394# assesses395# competencyRequired396# educationalAlignment397# educationalLevel398# educationalUse399# learningResourceType400# teaches401402funder: funders, # Org or person403funding: funding, # Grant404publisher: GTN,405provider: GTN,406syllabusSections: syllab,407# Session materials408# TODO: not currently parsed by TeSS, google just complains about it, so we're leaving it out.409# hasPart: parts,410}411412begin413data['dateModified'] = Gtn::ModificationTimes.obtain_time(page.path)414data['datePublished'] = Gtn::PublicationTimes.obtain_time(page.path)415rescue StandardError416data['dateModified'] = Gtn::ModificationTimes.obtain_time(page['path'])417data['datePublished'] = Gtn::PublicationTimes.obtain_time(page['path'])418end419420if page['cover']421data['image'] = if page['cover'] =~ /^http/422[page['cover']]423else424["#{site['url']}#{site['baseurl']}#{page['cover']}"]425end426end427428# We CANNOT guarantee A11Y429# data.update(A11Y)430if page['cost'] and page['cost'].downcase == 'free'431data['isAccessibleForFree'] = true432offer = {433'@type': 'Offer',434price: 0,435priceCurrency: 'EUR',436category: 'Free',437isAccessibleForFree: true,438}439elsif page['cost']440data['isAccessibleForFree'] = false441offer = {442'@type': 'Offer',443price: page['cost'].split[0],444priceCurrency: page['cost'].split[1],445isAccessibleForFree: false,446category: 'Paid',447# TODO: this can be more advanced but we need to collect start/end times, and timezone.448}449end450451# TODO: this is wrong in a whole host of scenarios like incl weekends.452course_days = (page.fetch('date_end', page['date_start']) - page['date_start']).to_i453if course_days < 1454course_days = 1455end456data['hasCourseInstance'] = [457{458'@type': 'CourseInstance',459courseMode: page['mode'],460# courseWorkload: "A daily course running from #{page['date_start']} to #{page['date_end']}",461offers: offer,462instructor: instructors,463isAccessibleForFree: data['isAccessibleForFree'],464courseSchedule: {465'@type': 'Schedule',466startDate: page['date_start'],467endDate: page.fetch('date_end', page['date_start']),468repeatCount: course_days,469repeatFrequency: 'daily', # Contrary to schema.org spec, this is what Google wants.470},471courseWorkload: "P#{course_days}D",472}473]474475data['offers'] = [offer]476477if page.key?('location') && page['location'].keys.length > 1478data['location'] = {479'@type': 'Place',480name: page['location']['name'],481address: {482'@type': 'PostalAddress',483streetAddress: page['location'].fetch('address', nil),484addressLocality: page['location'].fetch('city', nil),485addressRegion: page['location'].fetch('region', nil),486postalCode: page['location'].fetch('postcode', nil),487addressCountry: page['location'].fetch('country', nil)488}489}490end491492JSON.pretty_generate(data)493end494495##496# Generate the JSON-LD metadata for a learning pathway497# Parameters:498# +page+:: The page object.499# +site+:: The +Jekyll::Site+ site object.500# Returns:501# +Hash+:: The JSON-LD metadata.502#503# Examples:504# {{ page | generate_learning_pathway_jsonld: site }}505def generate_learning_pathway_jsonld(page, site)506materials = []507page['pathway'].each do |section|508if !section.key? 'tutorials'509next510end511512section['tutorials'].each do |tutorial|513if tutorial.key?('custom')514next515end516517material = Gtn::TopicFilter.fetch_tutorial_material(site, tutorial['topic'], tutorial['name'])518materials.push(material)519end520end521materials.compact!522523# Extract EDAM terms from all materials524edam_terms = materials.map do |material|525material.fetch('edam_ontology', []).map do |term|526{527'@type': 'DefinedTerm',528'@id': "http://edamontology.org/#{term}",529inDefinedTermSet: 'http://edamontology.org',530termCode: term,531}532end533end.flatten.uniq534535learning_objectives = materials.map do |material|536material.fetch('objectives', [])537end.flatten.compact538539funders = materials.map do |material|540Gtn::Contributors.get_funders(site, material).map do |x|541to_pfo_jsonld(x, site, json: false)542end543end.flatten.uniq.compact544545funding = materials.map do |material|546Gtn::Contributors.get_grants(site, material).map do |x|547to_pfo_jsonld(x, site, json: false)548end549end.flatten.uniq.compact550551# TODO: add topic edam terms too? Not sure.552parts = []553materials.each do |material|554mat = generate_material_jsonld(material, site['data'][material['topic_name']], site)555if !mat.nil? && !mat.empty?556parts.push(mat)557end558end559560syllab = page['pathway'].reject { |s| s['section'].nil? }.map do |section|561{562'@type': 'Syllabus',563name: section['section'],564description: section.fetch('description', nil),565}566end567568data = {569'@context': 'https://schema.org',570'@type': 'Course',571url: "#{site['url']}#{site['baseurl']}#{page['url']}",572name: "Learning Pathway #{page['title']}",573keywords: page['tags'] || [],574description: page['description'],575about: edam_terms, # TeSS, "scientific topics".576audience: page['audience'], # TeSS: target audience577teaches: learning_objectives, # TeSS, "learning objectives"578availableLanguage: ['en'], # TODO: support other languages579inLanguage: ['en'], # TODO: support other languages580# courseCode581# coursePrerequisites582# educationalCredentialAwarded583# financialAidEligible584# hasCourseInstance585# numberOfCredits586# occupationalCredentialAwarded587# syllabusSections588# totalHistoricalEnrollment589590# assesses591# competencyRequired592# educationalAlignment593# educationalLevel594# educationalUse595# learningResourceType596# teaches597598funder: funders, # Org or person599funding: funding, # Grant600publisher: GTN,601provider: GTN,602syllabusSections: syllab,603# Session materials604# TODO: not currently parsed by TeSS, google just complains about it, so we're leaving it out.605# hasPart: parts,606}607608begin609data['dateModified'] = Gtn::ModificationTimes.obtain_time(page.path)610data['datePublished'] = Gtn::PublicationTimes.obtain_time(page.path)611rescue StandardError612data['dateModified'] = Gtn::ModificationTimes.obtain_time(page['path'])613data['datePublished'] = Gtn::PublicationTimes.obtain_time(page['path'])614end615616if page['cover']617data['image'] = if page['cover'] =~ /^http/618[page['cover']]619else620["#{site['url']}#{site['baseurl']}#{page['cover']}"]621end622end623624# We CANNOT guarantee A11Y625# data.update(A11Y)626data['isAccessibleForFree'] = true627offer = {628'@type': 'Offer',629price: 0,630priceCurrency: 'EUR',631category: 'Free',632isAccessibleForFree: true,633}634data['offers'] = [offer]635636# TODO: this is basically just wrong.637data['hasCourseInstance'] = [638{639'@type': 'CourseInstance',640courseMode: 'online',641offers: offer,642isAccessibleForFree: data['isAccessibleForFree'],643}644]645646JSON.pretty_generate(data)647end648649##650# Convert a material to JSON-LD, intended to be used in Jekyll Liquid templates.651# Parameters:652# +material+:: The material object.653# +topic+:: The topic object.654# +site+:: The +Jekyll::Site+ site object.655#656# Returns:657# +String+:: The JSON-LD metadata.658#659# Examples:660# {{ material | to_jsonld: topic, site }}661def to_jsonld(material, topic, site)662JSON.pretty_generate(generate_material_jsonld(material, topic, site))663end664665##666# Convert a material to JSON-LD. Intended to be used by the filters which you should call in templates.667#668# Parameters:669# +material+:: The material object.670# +topic+:: The topic object.671# +site+:: The +Jekyll::Site+ site object.672#673# Returns:674# +Hash+:: The JSON-LD metadata.675def generate_material_jsonld(material, topic, site)676langCodeMap = {677"en" => 'English',678"es" => 'Español',679"fr" => 'Français',680}681682eduLevel = {683'Introductory' => 'Beginner',684'Intermediate' => 'Intermediate',685'Advanced' => 'Advanced'686}687return '{}' if !topic688689topic_desc = {690'@type': 'CreativeWork',691name: (topic['title']).to_s,692description: (topic['summary']).to_s,693url: "#{site['url']}#{site['baseurl']}/topics/#{topic['name']}/"694}695696# aggregate everything697data = {698# Properties from Course699'@context': 'http://schema.org',700'@type': 'LearningResource',701702# Required for BioSchemas703'http://purl.org/dc/terms/conformsTo': {704'@id': 'https://bioschemas.org/profiles/TrainingMaterial/1.0-RELEASE',705'@type': 'CreativeWork'706},707708# Properties from CreativeWork709# "about" described below710#711# "accountablePerson":,712# "aggregateRating":,713# "alternativeHeadline":,714# "associatedMedia":,715audience: {716'@type': 'EducationalAudience',717educationalRole: EDU_ROLES[topic['type']]718},719# "audio":,720# "award":,721# "author" described below722# "character":,723citation: [724{725'@type': 'CreativeWork',726name: 'Galaxy Training: A Powerful Framework for Teaching!',727url: 'https://doi.org/10.1371/journal.pcbi.1010752'728},729{730'@type': 'CreativeWork',731name: 'Community-Driven Data Analysis Training for Biology',732url: 'https://doi.org/10.1016/j.cels.2018.05.012'733}734],735# "comment":,736# "commentCount":,737# "contentLocation":,738# "contentRating":,739# "contentReferenceTime":,740# "contributor" described below741# copyrightHolder: GTN,742# copyrightNotice: m743# "copyrightYear":,744# "correction":,745# "creator":,746# "dateCreated":,747# "datePublished":,748discussionUrl: site['gitter_url'],749# "editor":,750# "educationalAlignment":,751# "educationalUse":,752# "encoding":,753# "encodingFormat":,754# "exampleOfWork":,755# "expires":,756# "funder": funding,757# "genre":,758# "hasPart" described below759headline: (material['title']).to_s,760# "interactionStatistic":,761interactivityType: 'mixed',762isAccessibleForFree: true,763# "isBasedOn":,764isFamilyFriendly: true,765# "isPartOf" described below766# "keywords": described below767# "learningResourceType" described below768license: 'https://spdx.org/licenses/CC-BY-4.0.html',769# "locationCreated":,770# "mainEntity":,771# "material":,772# "mentions" described below773# "offers":,774# "position":,775producer: GTN,776provider: GTN,777# "publication":,778# "publisher":,779# "publisherImprint":,780# "publishingPrinciples":,781# "recordedAt":,782# "releasedEvent":,783# "review":,784# "schemaVersion":,785# "sdDatePublished":,786# "sdLicense":,787# "sdPublisher":,788sourceOrganization: GTN,789# "spatialCoverage":,790# "sponsor":,791# "temporalCoverage":,792# "text":,793# "thumbnailUrl":,794# "timeRequired" described below795# "translationOfWork":,796# "translator": Google Translate???,797# "typicalAgeRange":,798# "version":,799# "video":,800# "workExample":,801# "workTranslation":,802803# Properties from Thing804# "additionalType":,805# "alternateName":,806# "description" described below807# "disambiguatingDescription":,808# "image":,809# "mainEntityOfPage":,810# "name" described below811# "potentialAction":,812# "sameAs":,813# "subjectOf":,814# "url" described below815workTranslation: [],816creativeWorkStatus: material['draft'] ? 'Draft' : 'Active',817}818819if material.key?('pub_date')820data['dateModified'] = material['mod_date']821data['datePublished'] = material['pub_date']822else823begin824data['dateModified'] = Gtn::ModificationTimes.obtain_time(material.path)825data['datePublished'] = Gtn::PublicationTimes.obtain_time(material.path)826rescue StandardError827data['dateModified'] = Gtn::ModificationTimes.obtain_time(material['path'])828data['datePublished'] = Gtn::PublicationTimes.obtain_time(material['path'])829end830end831832if material.key?('copyright')833# copyrightHolder: GTN,834data['copyrightNotice'] = material['copyright']835else836# I'm not sure this is accurate.837data['copyrightHolder'] = GTN838end839840funders = Gtn::Contributors.get_funders(site, material).map do |x|841to_pfo_jsonld(x, site, json: false)842end843grants = Gtn::Contributors.get_grants(site, material).map do |x|844to_pfo_jsonld(x, site, json: false)845end846847data['funder'] = funders848data['funding'] = grants849850data['identifier'] = "https://gxy.io/GTN:#{material['short_id']}" if material.key?('short_id')851852data.update(A11Y)853854actual_material = Gtn::TopicFilter.fetch_tutorial_material(site, material['topic_name'], material['tutorial_name'])855856# info depending if tutorial, hands-on or slide level857# parts = []858# data['hasPart'] = parts859860mentions = []861description = []862863data['isPartOf'] = topic_desc864865data['abstract'] = material866.fetch('content', '')867.strip868.split("\n")869.first870871if ! data['abstract'].nil?872data['abstract'] = data['abstract']873.gsub(/\{\{\s*site.baseurl\s*\}\}/, url_prefix(site))874.gsub(/\[{{\s*site.url\s*}}/, '[' + url_prefix(site))875.gsub(/{% link (topics[^%]*).md %}/, url_prefix(site) + '\1.html')876.gsub(/{% link (topics[^%]*).html %}/, url_prefix(site) + '\1.html')877.gsub(/\s*\(?{%\s*cite [^}]+\s*%}\)?/, '')878.gsub('{{ site.github_repository }}', safe_site_config(site, 'github_repository', 'https://example.com'))879.gsub(/{% snippet ([^%]*) %}/, '')880.gsub(/{% include ([^%]*) %}/, '')881end882883description.push("## Abstract\n\n#{data['abstract']}\n\n")884885if (material['name'] == 'tutorial.md') || (material['name'] == 'slides.html')886887if material['name'] == 'tutorial.md'888data['learningResourceType'] = 'e-learning'889description.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")890else891data['learningResourceType'] = 'slides'892end893894data['name'] = material['title']895data['url'] = "#{site['url']}#{site['baseurl']}#{material['url']}"896897# Requires https://github.com/galaxyproject/training-material/pull/4271898data['version'] = Gtn::ModificationTimes.obtain_modification_count(material['path'])899900# Time required901if material.key?('time_estimation') && !material['time_estimation'].nil?902data['timeRequired'] = "PT#{material['time_estimation'].upcase}"903end904905# Description with questions, objectives and keypoints906if material.key?('questions') && !material['questions'].nil? && material['questions'].length.positive?907questions = material['questions'].join("\n - ")908description.push("## Questions this #{material['type']} will address\n\n - #{questions}\n\n")909end910if material.key?('objectives') && !material['objectives'].nil? && material['objectives'].length.positive?911objectives = material['objectives'].map{|x| "- #{x}"}.join("\n")912description.push("## Learning Objectives\n\n#{objectives}\n\n")913data['teaches'] = objectives914end915if material.key?('keypoints') && !material['keypoints'].nil? && material['keypoints'].length.positive?916keypoints = material['keypoints'].join("\n - ")917description.push("## Key Points\n\n - #{keypoints}\n\n")918end919920# Keywords921data['keywords'] = [topic['title']] + (material['tags'] || [])922# Zenodo links923end924925# Mentions are 'external resources' in TeSS.926# This could be expanded with927# - supported servers928# - tools and resources used (e.g. Galaxy) or tools linked to the TS.929# - slides (if tutorial) and tutorial (if slides)930# - other materials in the same topic?931if actual_material.key?('workflows')932mentions.push({933'@type': 'Thing',934url: "#{site['url']}#{site['baseurl']}#{material['dir']}workflows/",935name: "Associated Workflows"936})937end938939# Notebooks940if actual_material.key?('notebook')941if actual_material['notebook']['language'] != 'r'942# Python, Bash, SQL (all via jupyter)943url = "#{site['url']}#{site['baseurl']}#{material['dir']}#{material['topic_name']}-#{material['tutorial_name']}.ipynb"944mentions.push({945'@type': 'Thing',946url: url,947name: "Jupyter Notebook (with Solutions)"948})949mentions.push({950'@type': 'Thing',951url: url.gsub(/\.ipynb$/, '-course.ipynb'),952name: "Jupyter Notebook (without Solutions)"953})954elsif actual_material['notebook']['language'] == 'r' # Actual R955url = "#{site['url']}#{site['baseurl']}#{material['dir']}#{material['topic_name']}-#{material['tutorial_name']}.Rmd"956mentions.push({957'@type': 'Thing',958url: url,959name: "Quarto/RMarkdown Notebook"960})961mentions.push({962'@type': 'Thing',963url: "https://bio.tools/tool/rstudio",964name: "RStudio"965})966end967end968969# Tools970uses_tools = false971(actual_material['tools'] || []).each do |tool|972if site.data['tool-meta'].nil?973next974end975976toolmeta = site.data['tool-meta'][tool]977if toolmeta.nil?978next979end980981if toolmeta['bio.tools'].length.positive?982mentions.push({983'@type': 'Thing',984url: "https://bio.tools/tool/#{toolmeta['bio.tools']}",985name: toolmeta.fetch('bio.tools_name', toolmeta['name'])986})987end988uses_tools = true989end990if uses_tools991mentions.push({992'@type': 'Thing',993url: "https://bio.tools/tool/galaxy",994name: "Galaxy"995})996end997998# Zenodo link out999if actual_material.key?('zenodo_link') && ! actual_material['zenodo_link'].nil?1000if actual_material['zenodo_link'].length.positive?1001mentions.push({1002'@type': 'Thing',1003url: (actual_material['zenodo_link']).to_s,1004name: "Associated Training Datasets"1005})1006end1007end10081009if description.empty?1010description.push(material.fetch('content', '').strip.split("\n").first)1011end1012data['description'] = description.join("\n")10131014data['inLanguage'] = if material.key?('lang')1015{1016'@type': 'Language',1017name: langCodeMap[material['lang']],1018alternateName: material['lang']1019}1020else1021{1022'@type': 'Language',1023name: 'English',1024alternateName: 'en'1025}1026end10271028# Course requirements (material + topic)1029reqs = []1030reqs.push(*topic['requirements']) if topic.key?('requirements')1031reqs.push(*material['requirements']) if material.key?('requirements')1032if !reqs.empty?1033coursePrerequisites = []1034reqs.each do |req|1035if req['type'] == 'internal'1036if req.key?('tutorials')1037(req['tutorials']).each do |tuto|1038(site['pages']).each do |page|1039if ((page['name'] == 'tutorial.md') || (page['name'] == 'slides.html')) &&1040((page['topic_name'] == req['topic_name']) && (page['tutorial_name'] == tuto))1041# slides1042if page['name'] == 'slides.html'1043coursePrerequisites.push(1044{1045'@context': 'http://schema.org',1046'@type': 'LearningResource',1047url: "#{site['url']}#{site['baseurl']}/topics/#{req['topic_name']}/" \1048"tutorials/#{tuto}/slides.html",1049name: (page['title']).to_s,1050description: "Slides for '#{page['title']}' tutorial",1051learningResourceType: 'slides',1052interactivityType: 'expositive',1053provider: GTN1054}1055)1056if page['hands_on_url']1057coursePrerequisites.push(1058{1059'@context': 'http://schema.org',1060'@type': 'LearningResource',1061url: (page['hands_on_url']).to_s,1062learningResourceType: 'e-learning',1063interactivityType: 'expositive',1064}1065)1066end1067end1068# hands-on1069if page['name'] == 'tutorial.md'1070coursePrerequisites.push(1071{1072'@context': 'http://schema.org',1073'@type': 'LearningResource',1074url: "#{site['url']}#{site['baseurl']}/topics/#{req['topic_name']}/tutorials" \1075"/#{tuto}/tutorial.html",1076name: (page['title']).to_s,1077description: "Hands-on for '#{page['title']}' tutorial",1078learningResourceType: 'e-learning',1079interactivityType: 'expositive',1080provider: GTN1081}1082)1083end1084end1085end1086end1087else1088coursePrerequisites.push(1089{1090'@context': 'http://schema.org',1091'@type': 'LearningResource',1092url: "#{site['url']}#{site['baseurl']}/topics/#{req['topic_name']}/",1093name: (site['data'][req['topic_name']]['title']).to_s,1094description: (site['data'][req['topic_name']]['title']).to_s,1095provider: GTN1096}1097)1098end1099elsif req['type'] == 'external'1100coursePrerequisites.push({1101'@type': 'CreativeWork',1102url: (req['link']).to_s,1103name: (req['title']).to_s1104})1105else1106coursePrerequisites.push((req['title']).to_s)1107end1108end1109data['competencyRequired'] = coursePrerequisites.uniq1110end11111112# Add contributors/authors1113if material.key?('contributors') || material.key?('contributions')1114authors = Gtn::Contributors.get_authors(material).map do |x|1115generate_person_jsonld(x, Gtn::Contributors.fetch_contributor(site, x), site)1116end11171118data['author'] = authors1119end11201121# Add non-author contributors1122if material.key?('contributions')1123data['contributor'] = Gtn::Contributors.get_non_authors(material).map do |x|1124generate_person_jsonld(x, site['data']['contributors'][x], site)1125end1126end11271128about = []1129about.push(topic_desc)1130edam_terms = topic.fetch('edam_ontology', []) | material.fetch('edam_ontology', [])11311132about += edam_terms.map do |term|1133{1134'@type': 'DefinedTerm',1135'@id': "http://edamontology.org/#{term}",1136inDefinedTermSet: 'http://edamontology.org',1137termCode: term,1138# "name": ,1139url: 'https://bioportal.bioontology.org/ontologies/EDAM/?p=classes&conceptid=' \1140"http%3A%2F%2Fedamontology.org%2F#{term}"1141}1142end11431144data['about'] = about11451146data['educationalLevel'] = material.key?('level') ? eduLevel[material['level']] : 'Beginner'1147data['mentions'] = mentions11481149data1150end1151end1152end1153end11541155Liquid::Template.register_filter(Jekyll::Filters::JsonldFilter)115611571158