From 27a6fdb3d777ca6c769268c1c6f69817267f4014 Mon Sep 17 00:00:00 2001 From: Laura Wrubel Date: Tue, 21 Nov 2023 09:49:38 -0500 Subject: [PATCH] schema.org markup for video objects --- Gemfile | 1 + Gemfile.lock | 2 + lib/metadata/schema_dot_org.rb | 153 +++- spec/lib/metadata/schema_dot_org_spec.rb | 957 ++++++++++++++++------- spec/model/purl_resource_spec.rb | 41 + 5 files changed, 846 insertions(+), 308 deletions(-) diff --git a/Gemfile b/Gemfile index c1db5f48..971edbe2 100644 --- a/Gemfile +++ b/Gemfile @@ -42,6 +42,7 @@ end group :development do # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. gem 'web-console', '>= 3.3.0' + gem 'byebug' end group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index 5d108ea5..a2afe5d2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -89,6 +89,7 @@ GEM bundler-audit (0.9.1) bundler (>= 1.2.0, < 3) thor (~> 1.0) + byebug (11.1.3) cancancan (3.5.0) capistrano (3.18.0) airbrussh (>= 1.0.0) @@ -442,6 +443,7 @@ PLATFORMS DEPENDENCIES addressable bootsnap (>= 1.1.0) + byebug cancancan capistrano (~> 3.0) capistrano-bundler diff --git a/lib/metadata/schema_dot_org.rb b/lib/metadata/schema_dot_org.rb index 10cfd2b8..7e0119c1 100644 --- a/lib/metadata/schema_dot_org.rb +++ b/lib/metadata/schema_dot_org.rb @@ -1,4 +1,5 @@ module Metadata + # rubocop:disable Metrics/ClassLength class SchemaDotOrg def self.call(cocina_json) new(cocina_json).call @@ -13,75 +14,96 @@ def initialize(cocina_json) end def call - { - "@context": 'http://schema.org', + { "@context": 'http://schema.org', "@type": schema_type, "name": title_name, - "identifier": identifier, - "description": description, - "isAccessibleForFree": access, - "license": license, - "url": url, - "creator": creators - }.compact + "description": description } + .merge(format_specific_fields) + .compact end def schema_type? - dataset? + dataset? || render_video_metadata? end private def schema_type - 'Dataset' if dataset? + return 'Dataset' if dataset? + + 'VideoObject' if render_video_metadata? end def dataset? # has a form with value of dataset and type of genre dataset = JsonPath.new("$.description.form[?(@['value'] == 'dataset' && @['type'] == 'genre')]").on(@cocina_json) - return true if dataset.any? + dataset.any? + end - false + def render_video_metadata? + # Only return video metadata if world-downloadable. + video = JsonPath.new("$.structural.contains[?(@['type'] == 'https://cocina.sul.stanford.edu/models/resources/video')]").on(@cocina_json) + video.any? && object_access? && video_access? end def title_name # title.value or concatenated title.structuredValue 1) for title with status "primary" if present 2) for first title - # required for Datasets + # required for Datasets and Videos titles = JsonPath.new("$.description.title[?(@['status' == 'primary'])].structuredValue[*].value").on(@cocina_json) - return titles.join('\n') unless titles.empty? + return titles.join(': ') unless titles.empty? JsonPath.new('$.description.title[0].value').first(@cocina_json) end + def format_specific_fields + if dataset? + return { "identifier": identifier, + "isAccessibleForFree": object_access?, + "license": license, + "url": url, + "creator": creators } + elsif render_video_metadata? + return { "thumbnailUrl": thumbnail, + "uploadDate": upload_date, + "embedUrl": embed_url } + end + {} + end + def description # description.note where type=summary or type=abstract, concatenating with \n if multiple # required for Datasets notes = JsonPath.new("$.description.note[?(@['type'] == 'summary' || @['type'] == 'abstract')].value").on(@cocina_json) - return notes.join('\n') unless notes.empty? - - # provide title (or other text?) in description if relevant note is missing - title_name + notes.join('\n') unless notes.empty? end def identifier - # identification.doi or identifier.uri or identifier.value with type "doi" (case-insensitive), made into URI if identifier only - identifier = JsonPath.new('$.identification.doi').first(@cocina_json) || - JsonPath.new('$.description.identifier..uri').first(@cocina_json) || - JsonPath.new("$.description.identifier[?(@['type'] == 'doi')].value").first(@cocina_json) - return unless identifier + # identification.doi or identifier.uri including doi.org or identifier.value with type "doi" (case-insensitive), made into URI if identifier only + doi_id = JsonPath.new('$.identification.doi').first(@cocina_json) || + JsonPath.new("$.description.identifier[?(@['type'] == 'doi')].value").first(@cocina_json) || + JsonPath.new("$.description.identifier[?(@['uri'] =~ /doi/)].uri").first(@cocina_json) + return unless doi_id - return [identifier] if identifier.start_with?('https://doi.org') + return [doi_id] if doi_id.start_with?('https://doi.org') - [URI.join('https://doi.org', identifier).to_s] + [URI.join('https://doi.org', doi_id).to_s] end - def access + def object_access? # true if access.download = "world" return true if JsonPath.new("$.access[?(@['download'] == 'world')]").first(@cocina_json) false end + def video_access? + video = JsonPath.new("$.structural.contains[*][?(@['type'] == 'https://cocina.sul.stanford.edu/models/resources/video')]").on(@cocina_json) + # need to find the file that is the one for the video (based on mime-type). Then get the access and download rights for that. + file_access = JsonPath.new('$[*].structural.contains[*][?(@.hasMimeType =~ /video/)].access.download').first(video) + + file_access == 'world' + end + def license JsonPath.new('$.access.license').first(@cocina_json) end @@ -130,8 +152,8 @@ def family_name(contributor) def orcid(contributor) # contributor.identifier.uri or contributor.identifier.value with type "orcid" (case-insensitive), made into URI if identifier only - id_uri = JsonPath.new('$.identifier.uri').first(contributor) - return id_uri if id_uri.present? + identifier = JsonPath.new('$.identifier.uri').first(contributor) + return identifier if identifier.present? orcid = JsonPath.new("$.identifier.[?(@['type'] == 'ORCID' || @['type'] == 'orcid')].value").first(contributor) return if orcid.blank? @@ -140,5 +162,78 @@ def orcid(contributor) URI.join('https://orcid.org/', orcid).to_s end + + def embed_url + iframe_url_template.expand(url: embeddable_url).to_s + end + + def iframe_url_template + Addressable::Template.new(Settings.embed.iframe.url_template) + end + + def embeddable_url + format(Settings.embed.url, druid: bare_druid) + end + + def bare_druid + druid.delete_prefix('druid:') + end + + def druid + JsonPath.new('$.externalIdentifier').first(@cocina_json) + end + + def thumbnail + # required for Videos + # structural.contains.filename with hasMimeType = "image/jp2" where structural.contains has type https://cocina.sul.stanford.edu/models/resources/video", + video = JsonPath.new("$.structural.contains[*][?(@['type'] == 'https://cocina.sul.stanford.edu/models/resources/video')]").on(@cocina_json) + filename = JsonPath.new("$[*].structural.contains[*][?(@['hasMimeType'] == 'image/jp2')].filename").first(video) + return if filename.blank? + + URI.join(Settings.stacks.url, "file/#{druid}/#{filename}").to_s + end + + def upload_date + # required for Videos + # event.date.value or event.date.structuredValue.value with event.date.type "publication" and event.date.status "primary" + # first event.date.value or event.date.structuredValue.value with event.date.type "publication" + events = JsonPath.new('$.description.event[*]').on(@cocina_json) + JsonPath.new("$[*].date[*][?(@['type'] == 'publication' && @['status'] == 'primary')].value").first(events) || + JsonPath.new("$[*].date[*][?(@['type'] == 'publication' && @['status'] == 'primary')].structuredValue[*].value").first(events) || + JsonPath.new("$[*].date[*][?(@['type'] == 'publication')].value").first(events) || + JsonPath.new("$[*].date[*][?(@['type'] == 'publication')].structuredValue[*].value").first(events) || + no_date_type(events) || no_event_type(events) + end + + def no_date_type(events) + # first event.date.value or event.date.structuredValue.value with event.type "publication" and event.date.type null + return unless events.any? + + dates = JsonPath.new("$.[?(@['type']) == 'publication')].date[*].[?(@['value'])]").on(events) + structured_dates = JsonPath.new("$.[?(@['type']) == 'publication')].date[*].structuredValue[*]").on(events) + dates.concat(structured_dates) + return unless dates.any? + + date_value(dates) + end + + def no_event_type(events) + # first event.date.value or event.date.structuredValue with event.type null and event.date.type null + return unless events.any? + + events.select! { |event| event.key?('type') == false } + dates = JsonPath.new('$[*].date[*]').on(events) + return unless dates.any? + + date_value(dates) + end + + def date_value(dates) + dates.select! { |date| date.key?('type') == false } + return unless dates.any? + + dates.first.fetch('value') + end end + # rubocop:enable Metrics/ClassLength end diff --git a/spec/lib/metadata/schema_dot_org_spec.rb b/spec/lib/metadata/schema_dot_org_spec.rb index 731fa939..65d75808 100644 --- a/spec/lib/metadata/schema_dot_org_spec.rb +++ b/spec/lib/metadata/schema_dot_org_spec.rb @@ -18,7 +18,7 @@ JSON end - it 'has type of Dataset' do + it 'has a type of Dataset' do expect(schema_dot_org).to include( "@context": 'http://schema.org', "@type": 'Dataset' @@ -26,24 +26,105 @@ end end - context 'without a dataset genre' do + context 'without a dataset or media form' do let(:cocina_json) do <<~JSON { "externalIdentifier": "druid:hj293cv5980", "label": "Not a dataset", "description": { - "form": [{ "value": "media", + "form": [{ "value": "audio", "type": "genre" }], "identifier": [] - } + }, + "structural": {"contains": [{"type": "https://cocina.sul.stanford.edu/models/resources/audio"}]} + } + JSON + end + + it 'does not have type' do + expect(schema_dot_org).not_to have_key('@type') + end + end + + context 'with a video resources type' do + let(:cocina_json) do + <<~JSON + { + "externalIdentifier": "druid:hj293cv5980", + "label": "A video about robots", + "description": {}, + "structural": {"contains": [{"type": "https://cocina.sul.stanford.edu/models/resources/video", + "structural": { "contains": [{ "type": "https://cocina.sul.stanford.edu/models/file", + "access": { "view": "world", + "download": "world", + "controlledDigitalLending": false }, + "hasMimeType": "video/mp4" }] } + }] + }, + "access": {"download": "world"} + } + JSON + end + + it 'has type of VideoObject' do + expect(schema_dot_org).to include( + "@context": 'http://schema.org', + "@type": 'VideoObject' + ) + end + end + + context 'without a world-downloadable video' do + let(:cocina_json) do + <<~JSON + { + "externalIdentifier": "druid:hj293cv5980", + "label": "A video about robots", + "description": {}, + "structural": {"contains": [{"type": "https://cocina.sul.stanford.edu/models/resources/video", + "structural": { "contains": [{ "type": "https://cocina.sul.stanford.edu/models/file", + "access": { "view": "world", + "download": "stanford", + "controlledDigitalLending": false }, + "hasMimeType": "video/mp4" }] } + }] + }, + "access": {"download": "world"} } JSON end - it 'does not have type of Dataset' do + it 'does not have type of VideoObject' do expect(schema_dot_org).not_to include( - "@type": 'Dataset' + "@type": 'VideoObject' + ) + end + end + + context 'without a video file' do + let(:cocina_json) do + <<~JSON + { + "externalIdentifier": "druid:hj293cv5980", + "label": "A video about robots", + "description": {}, + "structural": {"contains": [{"type": "https://cocina.sul.stanford.edu/models/resources/video", + "structural": { "contains": [{ "type": "https://cocina.sul.stanford.edu/models/file", + "access": { "view": "world", + "download": "world", + "controlledDigitalLending": false }, + "hasMimeType": "audio/mp4" }] } + }] + }, + "access": {"download": "world"} + } + JSON + end + + it 'does not have type of VideoObject' do + expect(schema_dot_org).not_to include( + "@type": 'VideoObject' ) end end @@ -82,6 +163,8 @@ ], "status": "primary"} ], + "form": [{ "value": "dataset", + "type": "genre" }], "identifier": [] } } @@ -90,7 +173,7 @@ it 'includes the title' do expect(schema_dot_org).to include( - "name": 'My Dataset\nMore title' + "name": 'My Dataset: More title' ) end end @@ -100,7 +183,9 @@ <<~JSON { "description": { - "note": [{"type": "abstract", "value": "About this dataset"}] + "note": [{"type": "abstract", "value": "About this item"}], + "form": [{ "value": "dataset", + "type": "genre" }] } } JSON @@ -108,7 +193,7 @@ it 'includes the description' do expect(schema_dot_org).to include( - "description": 'About this dataset' + "description": 'About this item' ) end end @@ -118,7 +203,9 @@ <<~JSON { "description": { - "note": [{"type": "summary", "value": "About this dataset"}] + "note": [{"type": "summary", "value": "About this dataset"}], + "form": [{"value": "dataset", + "type": "genre" }] } } JSON @@ -136,331 +223,643 @@ <<~JSON { "description": { - "title": [{"value": "My Dataset"}] + "title": [{"value": "My Dataset"}], + "form": [{"value": "dataset", + "type": "genre" }] } } JSON end - it 'uses the title instead' do - expect(schema_dot_org).to include( - "description": 'My Dataset' - ) + it 'does not include a description ' do + expect(schema_dot_org).not_to have_key('description') end end - context 'with DOI in identification' do - let(:cocina_json) do - <<~JSON - { - "description": { - "title": [{"value": "My Dataset"}] - }, - "identification": {"doi": "10.25740/hj293cv5980"} - } - JSON - end + context 'with a Dataset' do + context 'with DOI in identification' do + let(:cocina_json) do + <<~JSON + { + "description": { + "title": [{"value": "My Dataset"}], + "form": [{"value": "dataset", + "type": "genre" }] + }, + "identification": {"doi": "10.25740/hj293cv5980"} + } + JSON + end - it 'includes the DOI' do - expect(schema_dot_org).to include( - "identifier": ['https://doi.org/10.25740/hj293cv5980'] - ) + it 'includes the DOI' do + expect(schema_dot_org).to include( + "identifier": ['https://doi.org/10.25740/hj293cv5980'] + ) + end end - end - context 'with DOI in identifier uri' do - let(:cocina_json) do - <<~JSON - { - "description": { "form": [{ "value": "dataset", - "type": "genre" }], - "identifier": [ - { "uri": "https://doi.org/10.25740/hj293cv5980" } - ] - } - } - JSON - end + context 'with DOI in identifier uri' do + let(:cocina_json) do + <<~JSON + { + "description": { "form": [{ "value": "dataset", + "type": "genre" }], + "identifier": [ + { "uri": "https://doi.org/10.25740/hj293cv5980" } + ] + } + } + JSON + end - it 'includes the DOI' do - expect(schema_dot_org).to include( - "identifier": ['https://doi.org/10.25740/hj293cv5980'] - ) + it 'includes the DOI' do + expect(schema_dot_org).to include( + "identifier": ['https://doi.org/10.25740/hj293cv5980'] + ) + end end - end - context 'with DOI in identifier value' do - let(:cocina_json) do - <<~JSON - { - "description": { "form": [{ "value": "dataset", - "type": "genre" }], - "identifier": [ - { "value": "https://doi.org/10.25740/hj293cv5980", - "type": "doi" } + context 'with non-DOI in identifier uri' do + let(:cocina_json) do + <<~JSON + { + "description": { "form": [{ "value": "dataset", + "type": "genre" }], + "identifier": [ + { "uri": "https://identifier.example.com/123" } ] - } - } - JSON - end + } + } + JSON + end - it 'includes the DOI' do - expect(schema_dot_org).to include( - "identifier": ['https://doi.org/10.25740/hj293cv5980'] - ) + it 'does not includes the identifier' do + expect(schema_dot_org).not_to have_key('identifier') + end end - end - context 'with no identifiers' do - let(:cocina_json) do - <<~JSON - { - "description": { "form": [{ "value": "dataset", - "type": "genre" }], - "identifier": [] - } - } - JSON - end + context 'with DOI in identifier value' do + let(:cocina_json) do + <<~JSON + { + "description": { "form": [{ "value": "dataset", + "type": "genre" }], + "identifier": [ + { "value": "https://doi.org/10.25740/hj293cv5980", + "type": "doi" } + ] + } + } + JSON + end - it 'includes no identifier' do - expect(schema_dot_org).not_to have_key('identifier') + it 'includes the DOI' do + expect(schema_dot_org).to include( + "identifier": ['https://doi.org/10.25740/hj293cv5980'] + ) + end end - end - context 'when world downloadable' do - let(:cocina_json) do - <<~JSON - { - "description": { "form": [{ "value": "dataset", - "type": "genre" }] - }, - "access": {"download": "world"} - } - JSON - end + context 'with no identifiers' do + let(:cocina_json) do + <<~JSON + { + "description": { "form": [{ "value": "dataset", + "type": "genre" }], + "identifier": [] + } + } + JSON + end - it 'is accessibleForFree' do - expect(schema_dot_org).to include( - "isAccessibleForFree": true - ) + it 'includes no identifier' do + expect(schema_dot_org).not_to have_key('identifier') + end end - end - context 'when not world downloadable' do - let(:cocina_json) do - <<~JSON - { - "description": { "form": [{ "value": "dataset", - "type": "genre" }] - }, - "access": {"download": "stanford"} - } - JSON - end + context 'when dataset is world downloadable' do + let(:cocina_json) do + <<~JSON + { + "description": { "form": [{ "value": "dataset", + "type": "genre" }] + }, + "access": {"download": "world"} + } + JSON + end - it 'is not isAccessibleForFree' do - expect(schema_dot_org).to include( - "isAccessibleForFree": false - ) + it 'is accessibleForFree' do + expect(schema_dot_org).to include( + "isAccessibleForFree": true + ) + end end - end - context 'with a license' do - let(:cocina_json) do - <<~JSON - { - "description": { "form": [{ "value": "dataset", - "type": "genre" }] - }, - "access": {"license": "https://opendatacommons.org/licenses/by/1-0/"} - } - JSON - end + context 'when not world downloadable' do + let(:cocina_json) do + <<~JSON + { + "description": { "form": [{ "value": "dataset", + "type": "genre" }] + }, + "access": {"download": "stanford"} + } + JSON + end - it 'includes the license' do - expect(schema_dot_org).to include( - "license": 'https://opendatacommons.org/licenses/by/1-0/' - ) + it 'is not isAccessibleForFree' do + expect(schema_dot_org).to include( + "isAccessibleForFree": false + ) + end end - end - context 'with a purl' do - let(:cocina_json) do - <<~JSON - { - "description": { "form": [{ "value": "dataset", - "type": "genre" }], - "purl": "https://purl.stanford.edu/hj293cv5980" - } - } - JSON - end - - it 'includes a url' do - expect(schema_dot_org).to include( - "url": 'https://purl.stanford.edu/hj293cv5980' - ) - end - end + context 'with a license' do + let(:cocina_json) do + <<~JSON + { + "description": { "form": [{ "value": "dataset", + "type": "genre" }] + }, + "access": {"license": "https://opendatacommons.org/licenses/by/1-0/"} + } + JSON + end - context 'with contributors in a name' do - let(:cocina_json) do - <<~JSON - { - "description": { "form": [{ "value": "dataset", - "type": "genre" }], - "contributor": [{"name": {"value": "Doe, Jane"}}, - {"name": {"value": "Foo, John"}}] - } - } - JSON + it 'includes the license' do + expect(schema_dot_org).to include( + "license": 'https://opendatacommons.org/licenses/by/1-0/' + ) + end end - it 'includes creator' do - expect(schema_dot_org).to include( - "creator": [{ - "@type": 'Person', - "name": 'Doe, Jane' - }, { - "@type": 'Person', - "name": 'Foo, John' - }] - ) - end - end + context 'with a purl' do + let(:cocina_json) do + <<~JSON + { + "description": { "form": [{ "value": "dataset", + "type": "genre" }], + "purl": "https://purl.stanford.edu/hj293cv5980" + } + } + JSON + end - context 'with contributors in a structuredValue' do - let(:cocina_json) do - <<~JSON - { - "description": { "form": [{ "value": "dataset", - "type": "genre" }], - "contributor": [ - {"name": [ - { "structuredValue": [ - { "value": "Jane", - "type": "forename" }, - { "value": "Doe", - "type": "surname"}] - } - ]}, - {"name": [ - { "structuredValue": [ - { "value": "John", - "type": "forename"}, - { "value": "Foo", - "type": "surname"}] - } - ]} - ] - } - } - JSON + it 'includes a url' do + expect(schema_dot_org).to include( + "url": 'https://purl.stanford.edu/hj293cv5980' + ) + end end - it 'includes creator' do - expect(schema_dot_org).to include( - "creator": [ + context 'with contributors in a name' do + let(:cocina_json) do + <<~JSON { - "@type": 'Person', - "name": 'Jane Doe', - "givenName": 'Jane', - "familyName": 'Doe' - }, + "description": { "form": [{ "value": "dataset", + "type": "genre" }], + "contributor": [{"name": {"value": "Doe, Jane"}}, + {"name": {"value": "Foo, John"}}] + } + } + JSON + end + + it 'includes creator' do + expect(schema_dot_org).to include( + "creator": [{ "@type": 'Person', + "name": 'Doe, Jane' }, + { "@type": 'Person', + "name": 'Foo, John' }] + ) + end + end + + context 'with contributors in a structuredValue' do + let(:cocina_json) do + <<~JSON { - "@type": 'Person', - "name": 'John Foo', - "givenName": 'John', - "familyName": 'Foo' + "description": { "form": [{ "value": "dataset", + "type": "genre" }], + "contributor": [ + {"name": [ + { "structuredValue": [ + { "value": "Jane", + "type": "forename" }, + { "value": "Doe", + "type": "surname"}] + } + ]}, + {"name": [ + { "structuredValue": [ + { "value": "John", + "type": "forename"}, + { "value": "Foo", + "type": "surname"}] + } + ]} + ] + } } - ] - ) + JSON + end + + it 'includes creator' do + expect(schema_dot_org).to include( + "creator": [ + { + "@type": 'Person', + "name": 'Jane Doe', + "givenName": 'Jane', + "familyName": 'Doe' + }, + { + "@type": 'Person', + "name": 'John Foo', + "givenName": 'John', + "familyName": 'Foo' + } + ] + ) + end + end + + context 'with an ORCID identifier uri for the contributor' do + let(:cocina_json) do + <<~JSON + { + "description": { "form": [{ "value": "dataset", + "type": "genre" }], + "contributor": [{"name": {"value": "Doe, Jane"}, + "identifier": {"uri": "https://orcid.org/0000-0000-0000-0000"}}] + } + } + JSON + end + + it 'includes the ORCID' do + expect(schema_dot_org).to include( + "creator": [{ + "@type": 'Person', + "name": 'Doe, Jane', + "sameAs": 'https://orcid.org/0000-0000-0000-0000' + }] + ) + end end - end - context 'with an ORCID uri for the contributor' do - let(:cocina_json) do - <<~JSON - { - "description": { "form": [{ "value": "dataset", - "type": "genre" }], - "contributor": [{"name": {"value": "Doe, Jane"}, - "identifier": {"uri": "https://orcid.org/0000-0000-0000-0000"}}] - } - } - JSON + context 'with an identifier of ORCID type for the contributor' do + let(:cocina_json) do + <<~JSON + { + "description": { "form": [{ "value": "dataset", + "type": "genre" }], + "contributor": [{"name": {"value": "Doe, Jane"}, + "identifier": {"value": "0000-0000-0000-0000", + "type": "ORCID"} + }] + } + } + JSON + end + + it 'includes the ORCID' do + expect(schema_dot_org).to include( + "creator": [{ + "@type": 'Person', + "name": 'Doe, Jane', + "sameAs": 'https://orcid.org/0000-0000-0000-0000' + }] + ) + end end - it 'includes the ORCID' do - expect(schema_dot_org).to include( - "creator": [{ - "@type": 'Person', - "name": 'Doe, Jane', - "sameAs": 'https://orcid.org/0000-0000-0000-0000' - }] - ) + context 'with a structuredValue name and ORCID' do + let(:cocina_json) do + <<~JSON + { "description": { "form": [{ "value": "dataset", + "type": "genre" }], + "contributor": [ + { "name": [ + { "structuredValue": [ + { "value": "Jane", + "type": "forename" }, + { "value": "Doe", + "type": "surname"} + ] + } + ], + "identifier": {"uri": "https://orcid.org/0000-0000-0000-0000"} + }] + } + } + JSON + end + + it 'includes the ORCID' do + expect(schema_dot_org).to include( + 'creator': [{ + "@type": 'Person', + "name": 'Jane Doe', + "givenName": 'Jane', + "familyName": 'Doe', + "sameAs": 'https://orcid.org/0000-0000-0000-0000' + }] + ) + end end end - context 'with an identifier of ORCID type for the contributor' do + context 'with a Video' do let(:cocina_json) do <<~JSON { - "description": { "form": [{ "value": "dataset", - "type": "genre" }], - "contributor": [{"name": {"value": "Doe, Jane"}, - "identifier": {"value": "0000-0000-0000-0000", - "type": "ORCID"} - }] - } + "externalIdentifier": "druid:tn153br1253", + "description": { "event": [{ "date": [{ "value": "2000", "type": "publication" }, + { "value": "2014", "status": "primary", "type": "publication" }] + }] + }, + "access": {"download": "world"}, + "structural": {"contains": [{"type": "https://cocina.sul.stanford.edu/models/resources/video", + "structural": { + "contains": [{"filename": "tn153br1253_thumb.jp2", + "hasMimeType": "image/jp2"}, + {"filename": "tn153br1253_video_sl.mp4", + "access": { "view": "world", + "download": "world", + "controlledDigitalLending": false }, + "hasMimeType": "video/mp4"}] + } + }] + } } JSON end - it 'includes the ORCID' do - expect(schema_dot_org).to include( - "creator": [{ - "@type": 'Person', - "name": 'Doe, Jane', - "sameAs": 'https://orcid.org/0000-0000-0000-0000' - }] - ) + context 'with a thumbnail' do + it 'includes the thumbnail' do + expect(schema_dot_org).to include( + "thumbnailUrl": 'https://stacks.stanford.edu/file/druid:tn153br1253/tn153br1253_thumb.jp2' + ) + end end - end - context 'with a structuredValue name and ORCID' do - let(:cocina_json) do - <<~JSON - { "description": { "form": [{ "value": "dataset", - "type": "genre" }], - "contributor": [ - { "name": [ - { "structuredValue": [ - { "value": "Jane", - "type": "forename" }, - { "value": "Doe", - "type": "surname"} - ] - } - ], - "identifier": {"uri": "https://orcid.org/0000-0000-0000-0000"} - }] + context 'with no thumbnail' do + let(:cocina_json) do + <<~JSON + { + "externalIdentifier": "druid:tn153br1253", + "description": { }, + "access": {"download": "world"}, + "structural": {"contains": [{"type": "https://cocina.sul.stanford.edu/models/resources/video", + "structural": {"contains": [{"filename": "tn153br1253_history_sl.mp4", + "access": { "view": "world", + "download": "world", + "controlledDigitalLending": false }, + "hasMimeType": "video/mp4"}] + } + }] } - } - JSON - end - - it 'includes the ORCID' do - expect(schema_dot_org).to include( - "creator": [{ - "@type": 'Person', - "name": 'Jane Doe', - "givenName": 'Jane', - "familyName": 'Doe', - "sameAs": 'https://orcid.org/0000-0000-0000-0000' - }] - ) + } + JSON + end + + it 'does not include a thumbnail' do + expect(schema_dot_org).not_to have_key('thumbnailUrl') + end + end + + context 'with an embeddable video' do + it 'includes the embed_url' do + expect(schema_dot_org).to include( + 'embedUrl': 'https://embed.stanford.edu/iframe/?url=https%3A%2F%2Fpurl.stanford.edu%2Ftn153br1253' + ) + end + end + + context 'with an event date' do + context 'with value and status of primary' do + it 'includes the uploadDate' do + expect(schema_dot_org).to include( + 'uploadDate': '2014' + ) + end + end + + context 'with a structuredValue with status of primary' do + let(:cocina_json) do + <<~JSON + { + "externalIdentifier": "druid:tn153br1253", + "description": { "event": [{ "type": "publication", + "date": [{ "structuredValue": [{"value": "2000"}], + "type": "publication" }, + { "structuredValue": [{"value": "2014"}], + "status": "primary", + "type": "publication" }] + }] + }, + "structural": {"contains": [{"type": "https://cocina.sul.stanford.edu/models/resources/video", + "structural": { "contains": [{ "type": "https://cocina.sul.stanford.edu/models/file", + "access": { "view": "world", + "download": "world", + "controlledDigitalLending": false }, + "hasMimeType": "video/mp4" }] } + }] + }, + "access": {"download": "world"} + } + JSON + end + + it 'includes the primary uploadDate' do + expect(schema_dot_org).to include( + 'uploadDate': '2014' + ) + end + end + + context 'with no date having a status of primary' do + let(:cocina_json) do + <<~JSON + { + "externalIdentifier": "druid:tn153br1253", + "description": { "event": [{ "type": "publication", + "date": [{ "value": "2014", + "type": "publication" }, + { "value": "2000", + "type": "publication" }] + }] + }, + "structural": {"contains": [{"type": "https://cocina.sul.stanford.edu/models/resources/video", + "structural": { "contains": [{ "type": "https://cocina.sul.stanford.edu/models/file", + "access": { "view": "world", + "download": "world", + "controlledDigitalLending": false }, + "hasMimeType": "video/mp4" }] } + }] + }, + "access": {"download": "world"} + } + JSON + end + + it 'selects the first one for the uploadDate' do + expect(schema_dot_org).to include( + 'uploadDate': '2014' + ) + end + end + + context 'with no structuredValue date having a status of primary' do + let(:cocina_json) do + <<~JSON + { + "externalIdentifier": "druid:tn153br1253", + "description": { "event": [{ "type": "publication", + "date": [{ "structuredValue": [{"value": "2014"}], + "type": "publication" }, + { "structuredValue": [{"value": "2000"}], + "type": "publication" }] + }] + }, + "structural": {"contains": [{"type": "https://cocina.sul.stanford.edu/models/resources/video", + "structural": { "contains": [{ "type": "https://cocina.sul.stanford.edu/models/file", + "access": { "view": "world", + "download": "world", + "controlledDigitalLending": false }, + "hasMimeType": "video/mp4" }] } + }] + }, + "access": {"download": "world"} + } + JSON + end + + it 'selects the first one for the uploadDate' do + expect(schema_dot_org).to include( + 'uploadDate': '2014' + ) + end + end + + context 'with type publication and structuredValue having no type' do + let(:cocina_json) do + <<~JSON + { + "externalIdentifier": "druid:tn153br1253", + "description": { "event": [{ "type": "publication", + "date": [{ "structuredValue": [{"value": "2014"}]}, + { "structuredValue": [{"value": "2000"}]} + ] + }] + }, + "structural": {"contains": [{"type": "https://cocina.sul.stanford.edu/models/resources/video", + "structural": { "contains": [{ "type": "https://cocina.sul.stanford.edu/models/file", + "access": { "view": "world", + "download": "world", + "controlledDigitalLending": false }, + "hasMimeType": "video/mp4" }] } + }] + }, + "access": {"download": "world"} + } + JSON + end + + it 'selects the first one for the uploadDate' do + expect(schema_dot_org).to include( + 'uploadDate': '2014' + ) + end + end + + context 'with type publication and date having no type' do + let(:cocina_json) do + <<~JSON + { + "externalIdentifier": "druid:tn153br1253", + "description": { "event": [{ "type": "publication", + "date": [{ "value": "2000", "type": "secondary" }, + { "value": "2014" }] + }] + }, + "structural": {"contains": [{"type": "https://cocina.sul.stanford.edu/models/resources/video", + "structural": { "contains": [{ "type": "https://cocina.sul.stanford.edu/models/file", + "access": { "view": "world", + "download": "world", + "controlledDigitalLending": false }, + "hasMimeType": "video/mp4" }] } + }] + }, + "access": {"download": "world"} + } + JSON + end + + it 'selects the first one for the uploadDate' do + expect(schema_dot_org).to include( + 'uploadDate': '2014' + ) + end + end + + context 'with event type having no type' do + let(:cocina_json) do + <<~JSON + { + "externalIdentifier": "druid:tn153br1253", + "description": { "event": [{ "date": [{ "value": "2000", "type": "secondary" }, + { "value": "2014" }] + }] + }, + "structural": {"contains": [{"type": "https://cocina.sul.stanford.edu/models/resources/video", + "structural": { "contains": [{ "type": "https://cocina.sul.stanford.edu/models/file", + "access": { "view": "world", + "download": "world", + "controlledDigitalLending": false }, + "hasMimeType": "video/mp4" }] } + }] + }, + "access": {"download": "world"} + } + JSON + end + + it 'selects the first date for the uploadDate' do + expect(schema_dot_org).to include( + 'uploadDate': '2014' + ) + end + end + + context 'with no relevant event dates' do + let(:cocina_json) do + <<~JSON + { + "externalIdentifier": "druid:tn153br1253", + "description": {"event": [{ "date": [{ "value": "2000", "type": "secondary" }, + { "value": "2014", "type": "creation" }] + }] + }, + "structural": {"contains": [{"type": "https://cocina.sul.stanford.edu/models/resources/video", + "structural": { "contains": [{ "type": "https://cocina.sul.stanford.edu/models/file", + "access": { "view": "world", + "download": "world", + "controlledDigitalLending": false }, + "hasMimeType": "video/mp4" }] } + }] + }, + "access": {"download": "world"} + } + JSON + end + + it 'does not include an uploadDate' do + expect(schema_dot_org).not_to have_key('uploadDate') + end + end end end end diff --git a/spec/model/purl_resource_spec.rb b/spec/model/purl_resource_spec.rb index e099af51..333cae27 100644 --- a/spec/model/purl_resource_spec.rb +++ b/spec/model/purl_resource_spec.rb @@ -389,12 +389,53 @@ "@context": 'http://schema.org', "@type": 'Dataset', "name": 'AVOIDDS: A dataset for vision-based aircraft detection', + "isAccessibleForFree": false, + "creator": [], "identifier": ['https://doi.org/10.25740/hj293cv5980'], "description": 'About this dataset.' ) end end + context 'with a video' do + before do + allow(subject).to receive(:cocina_body).and_return <<~JSON + { + "externalIdentifier": "druid:tn153br1253", + "description": { "event": [{ "date": [{ "value": "2000", "type": "publication", "status": "primary"}] }], + "title": [{"value": "A Video Title"}], + "note": [{"type": "summary", "value": "What is in this video?"}] + }, + "access": {"download": "world"}, + "structural": {"contains": [{"type": "https://cocina.sul.stanford.edu/models/resources/video", + "structural": { + "contains": [{"filename": "tn153br1253_thumb.jp2", + "hasMimeType": "image/jp2"}, + {"filename": "tn153br1253_video_sl.mp4", + "access": { "view": "world", + "download": "world", + "controlledDigitalLending": false }, + "hasMimeType": "video/mp4"}] + } + }] + } + } + JSON + end + + it 'returns schema.org markup' do + expect(subject.schema_dot_org).to include( + "@context": 'http://schema.org', + "@type": 'VideoObject', + "name": 'A Video Title', + "description": 'What is in this video?', + "uploadDate": '2000', + "thumbnailUrl": 'https://stacks.stanford.edu/file/druid:tn153br1253/tn153br1253_thumb.jp2', + "embedUrl": 'https://embed.stanford.edu/iframe/?url=https%3A%2F%2Fpurl.stanford.edu%2Ftn153br1253' + ) + end + end + context 'with a format not relevant for schema.org' do before do allow(subject).to receive(:cocina_body).and_return <<~JSON