Skip to content

Commit

Permalink
Merge pull request #585 from ndlib/DLTP-721-Support-Versions-for-OSF-…
Browse files Browse the repository at this point in the history
…Archiv

Support Versions for OSF Archive
  • Loading branch information
banurekha committed Jan 11, 2017
2 parents 5b45475 + 6ee1b13 commit 96a9c59
Show file tree
Hide file tree
Showing 14 changed files with 216 additions and 12 deletions.
1 change: 0 additions & 1 deletion .started-issues
@@ -1,4 +1,3 @@
478
DLTP-638
DLTP-717
DLTP-764
10 changes: 10 additions & 0 deletions app/assets/stylesheets/modules/attributes.css.scss
Expand Up @@ -48,3 +48,13 @@
}
}
}

.table.related-to-works {
.selected-row {
border: 2px solid $blue-light;
}

.modifier.current {
width: 3em;
}
}
3 changes: 2 additions & 1 deletion app/mappers/as_jsonld_mapper.rb
Expand Up @@ -31,6 +31,7 @@ def self.call(curation_concern, **keywords)
'vracore'.to_sym => 'http://purl.org/vra/',
'frels'.to_sym => 'info:fedora/fedora-system:def/relations-external#',
'ms'.to_sym => 'http://www.ndltd.org/standards/metadata/etdms/1.1/',
'pav'.to_sym => 'http://purl.org/pav/',
"fedora-model".to_sym => "info:fedora/fedora-system:def/model#",
"hydra".to_sym => "http://projecthydra.org/ns/relations#",
"hasModel".to_sym => {"@id" => "fedora-model:hasModel", "@type" => "@id" },
Expand All @@ -40,7 +41,7 @@ def self.call(curation_concern, **keywords)
"isMemberOfCollection".to_sym => {"@id" => "frels:isMemberOfCollection", "@type" => "@id" },
"isEditorOf".to_sym => {"@id" => "hydra:isEditorOf", "@type" => "@id" },
"hasMember".to_sym => {"@id" => "frels:hasMember", "@type" => "@id" },
"previousVersion".to_sym => "http://purl.org/pav/previousVersion"
'previousVersion'.to_sym => 'http://purl.org/pav/previousVersion'
}

DEFAULT_ATTRIBUTES = {
Expand Down
8 changes: 7 additions & 1 deletion app/repository_datastreams/osf_archive_datastream.rb
Expand Up @@ -10,7 +10,13 @@ class OsfArchiveDatastream < ActiveFedora::NtriplesRDFDatastream
index.as :stored_searchable
end

map.source(to: 'source', in: RDF::DC)
map.source(to: 'source', in: RDF::DC) do |index|
index.as :stored_searchable
end

map.osf_project_identifier(to: 'osfProjectIdentifier', in: RDF::ND) do |index|
index.as :stored_searchable
end

map.subject(to: 'subject', in: RDF::DC) do |index|
index.as :stored_searchable
Expand Down
66 changes: 62 additions & 4 deletions app/repository_models/osf_archive.rb
@@ -1,3 +1,16 @@
# Models two "types" of OSF Archives:
#
# * OSF Project
# * OSF Registration
#
# The OSF Registration is a "snapshot" of an OSF Project. The Registration has a URL
# separate from the OSF Project; It points back to the OSF Project. In theory, if
# we ingest the same OSF Registration we will have the same information; In practice,
# some of the data for the registration may point to external sources that have changed
# between ingests of that OSF Registration.
#
# The OSF Project is a living/mutable source. It represents the current state of the project.
# When we ingest an OSF Project, that current state is captured.
class OsfArchive < ActiveFedora::Base
include ActiveModel::Validations
include CurationConcern::Work
Expand All @@ -8,23 +21,64 @@ class OsfArchive < ActiveFedora::Base
include ActiveFedora::RegisteredAttributes
include CurationConcern::RemotelyIdentifiedByDoi::Attributes
include CurationConcern::WithJsonMapper

before_validation :set_initial_values, on: :create

belongs_to :previousVersion, property: :previousVersion, class_name: "OsfArchive"

class_attribute :human_readable_short_description
self.human_readable_short_description = "Change me."

def self.human_readable_type
'OSF Archive'
end

# These are included as convenience for developers.
SOLR_KEY_OSF_PROJECT_IDENTIFIER = 'desc_metadata__osf_project_identifier_tesim'.freeze
SOLR_KEY_ARCHIVED_DATE = 'desc_metadata__date_archived_dtsi'.freeze
SOLR_KEY_SOURCE = 'desc_metadata__source_tesim'.freeze

# Retrieve all archived versions of the source project (including registrations)
# in date_archived descending order.
#
# @return [Array<OsfArchive>]
# @see ./spec/repository_models/osf_archive_spec.rb
def archived_versions_of_source_project
@archived_versions_of_source_project ||= begin
conditions = { SOLR_KEY_OSF_PROJECT_IDENTIFIER => osf_project_identifier }
options = { sort: "#{SOLR_KEY_ARCHIVED_DATE} desc" }
solr_results = self.class.find_with_conditions(conditions, options)
ActiveFedora::SolrService.reify_solr_results(solr_results, load_from_solr: true)
end
end

def set_initial_values
self.date_modified = Date.today
self.date_archived = Date.today
self.type = human_readable_type
self.date_archived ||= Date.today
determine_type_and_osf_project_identifier
end
private :set_initial_values

def determine_type_and_osf_project_identifier
if osf_project_identifier.present?
if osf_project_identifier == osf_source_slug
self.type = "OSF Project"
else
self.type = "OSF Registration"
end
else
if osf_source_slug.present?
self.osf_project_identifier = osf_source_slug
self.type = "OSF Project"
end
end
end
private :determine_type_and_osf_project_identifier

def osf_source_slug
self.source.to_s.split('/').last
end

has_metadata 'descMetadata', type: OsfArchiveDatastream

attribute :affiliation,
Expand All @@ -41,14 +95,18 @@ def set_initial_values

attribute :source,
datastream: :descMetadata, multiple: false,
label: 'Original OSF Project',
label: 'Original OSF URL',
validates: {
format: {
with: URI::regexp(%w(http https ftp)),
message: 'must be a valid URL.'
}
}

attribute :osf_project_identifier,
datastream: :descMetadata, multiple: false,
label: 'OSF Project Identifier'

attribute :subject,
datastream: :descMetadata, multiple: true

Expand Down
29 changes: 28 additions & 1 deletion app/views/curation_concern/osf_archives/_attributes.html.erb
Expand Up @@ -9,7 +9,8 @@
<%= curation_concern_attribute_to_html(curation_concern, :date_created, 'Date Created') %>
<%= curation_concern_attribute_to_html(curation_concern, :subject, "Subject") %>
<%= curation_concern_attribute_to_html(curation_concern, :date_archived, "Archive Date") %>
<%= curation_concern_attribute_to_html(curation_concern, :source, "Original OSF Project") %>
<%= curation_concern_attribute_to_html(curation_concern, :source, "Original OSF URL") %>
<%= curation_concern_attribute_to_html(curation_concern, :osf_project_identifier, "OSF Project Identifier") %>
<%= curation_concern_attribute_to_html(curation_concern, :language, "Language") %>
<%= decode_administrative_unit(curation_concern, :administrative_unit, "Departments and Units") %>
<%= curation_concern_attribute_to_html(curation_concern, :library_collections, "Member of") %>
Expand All @@ -31,3 +32,29 @@
<% end %>
</tbody>
</table>

<h2>Version History</h2>
<table class="table table-striped <%= dom_class(curation_concern) %> related-to-works with-headroom">
<caption class="table-heading">
<p>These are different snapshots of the same OSF Project.</p>
</caption>
<thead>
<tr>
<th>&nbsp;</th>
<th>CurateND Identifier</th>
<th>Date Archived</th>
</tr>
</thead>
<tbody>
<% curation_concern.archived_versions_of_source_project.each do |archived_version| %>
<% is_viewing_version = (archived_version.pid == curation_concern.pid) %>
<tr class="referenced_by_works attributes <%= 'selected-row' if is_viewing_version %>">
<td class="modifier current"><%= is_viewing_version ? 'Viewing' : '&nbsp;'.html_safe %></td>
<td class="attribute noid">
<%= link_to_unless(is_viewing_version, archived_version.noid, polymorphic_path([:curation_concern, archived_version])) %>
</td>
<td class="attribute date_archived"><%= content_tag :time, archived_version.date_archived.strftime('%Y-%m-%d'), datetime: archived_version.date_archived.iso8601 %></td>
</tr>
<% end %>
</tbody>
</table>
@@ -1,5 +1,6 @@
<fieldset class="optional prompt">
<legend>Additional Information</legend>
<%= f.input :osf_project_identifier, label: "OSF Project Identifier", input_html: { class: 'input-xlarge' } %>
<%= f.input :affiliation,
label: 'Academic Status',
as: :select,
Expand Down
Expand Up @@ -11,5 +11,5 @@
},
label: 'Description'
%>
<%= f.input :source, label: "Original OSF Project", input_html: {class: 'input-xlarge', type:'url'} %>
<%= f.input :source, label: "Original OSF URL", input_html: {class: 'input-xlarge', type:'url'} %>
</fieldset>
3 changes: 3 additions & 0 deletions config/predicate_mappings.yml
Expand Up @@ -5,6 +5,7 @@
fedora-model: info:fedora/fedora-system:def/model#
fedora-relations-model: info:fedora/fedora-system:def/relations-external#
hydramata-rel: http://projecthydra.org/ns/relations#
pav: 'http://purl.org/pav/'

# namespace mappings---
# you can add specific mappings for your institution by providing the following:
Expand Down Expand Up @@ -49,6 +50,8 @@
:is_deployment_of: isDeploymentOf
:has_service: hasService
:has_model: hasModel
http://purl.org/pav/:
:previousVersion: previousVersion
http://www.openarchives.org/OAI/2.0/:
:oai_item_id: itemID
http://projecthydra.org/ns/relations#:
Expand Down
1 change: 1 addition & 0 deletions lib/rdf/nd.rb
Expand Up @@ -2,5 +2,6 @@
module RDF
class ND < Vocabulary("https://library.nd.edu/ns/terms/")
property :alephIdentifier
property :osfProjectIdentifier, comment: "The identifier of the source OSF Project. Extends dc:identifier."
end
end
5 changes: 5 additions & 0 deletions lib/rdf/pav.rb
@@ -0,0 +1,5 @@
module RDF
class PAV < Vocabulary("http://purl.org/pav/")
property :previousVersion
end
end
16 changes: 16 additions & 0 deletions script/one-off/DLTP-721-seed-data.rb
@@ -0,0 +1,16 @@
attributes = {
osf_project_identifier: 'abcde',
source: 'https://osf.io/abcde',
title: 'Example OSF Project',
description: "Many Bothan's died to bring you this project",
visibility: Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_PUBLIC
}

previous_project = OsfArchive.create!(attributes.merge(date_archived: '2016-01-02'))
registration = OsfArchive.create!(attributes.merge(date_archived: '2016-02-02', source: 'https://osf.io/12345'))
project = OsfArchive.create!(attributes.merge(date_archived: '2016-03-02', previousVersion: previous_project))

puts "Previous Project URL: http://localhost:3000/show/#{previous_project.noid}"
puts "Registration URL: http://localhost:3000/show/#{registration.noid}"
puts "Project URL: http://localhost:3000/show/#{project.noid}"
`open http://localhost:3000/show/#{project.noid}`
3 changes: 3 additions & 0 deletions spec/factories/osf_archives_factory.rb
Expand Up @@ -12,7 +12,10 @@
date_created { Date.today }
date_modified { Date.today }
date_archived { Date.today }
# the source and osf_project_identifier are related
source { 'https://osf.io/xxxxx/' }
osf_project_identifier { 'xxxxx' }

visibility Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_AUTHENTICATED

before(:create) { |work, evaluator|
Expand Down
80 changes: 77 additions & 3 deletions spec/repository_models/osf_archive_spec.rb
Expand Up @@ -6,21 +6,95 @@
it { should have_unique_field(:title) }
it { should have_unique_field(:source) }
it { should have_unique_field(:type) }
it { should have_unique_field(:osf_project_identifier) }

it_behaves_like 'is_a_curation_concern_model'
it_behaves_like 'with_access_rights'
it_behaves_like 'can_be_a_member_of_library_collections'
it_behaves_like 'is_embargoable'
it_behaves_like 'with_json_mapper'

describe 'new archive' do
describe 'new archive (after validation)' do
let(:archive) {OsfArchive.new}

it "should set initialize dates and stamp type on create" do
it "should set initialize dates" do
archive.valid?
expect(archive.date_modified).to eq(Date.today)
expect(archive.date_archived).to eq(Date.today)
expect(archive.type).to eq('OSF Archive')
end

[
{ attributes: { osf_project_identifier: "abcde", source: 'https://osf.io/abcde' }, type: 'OSF Project' },
{ attributes: { osf_project_identifier: "12345", source: 'https://osf.io/abcde' }, type: 'OSF Registration' },
{ attributes: { osf_project_identifier: "", source: 'https://osf.io/abcde' }, type: 'OSF Project' },
{ attributes: { osf_project_identifier: "", source: "" }, type: nil },
{ attributes: { osf_project_identifier: "12345", source: "" }, type: 'OSF Registration' }
].each_with_index do |data, index|
it "will assign dc:type of #{data.fetch(:type).inspect} for attributes #{data.fetch(:attributes).inspect} (Scenario ##{index})" do
osf_archive = OsfArchive.new(data.fetch(:attributes))
osf_archive.valid? # required because we are setting via before validation
expect(osf_archive.type).to eq(data.fetch(:type))
end
end
end

describe 'related versions of OSF Archive objects' do
let(:osf_project_identifier) { 'abcde' }
let(:osf_registration_identifier) { '12345' }
let!(:previous_version) do
FactoryGirl.create(
:osf_archive,
source: "https://osf.io/#{osf_project_identifier}",
osf_project_identifier: osf_project_identifier,
date_archived: 2.days.ago
)
end
let!(:current_version) do
FactoryGirl.create(
:osf_archive,
previousVersion: previous_version,
source: "https://osf.io/#{osf_project_identifier}",
osf_project_identifier: osf_project_identifier,
date_archived: 1.days.ago
)
end
let!(:project_registration) do
FactoryGirl.create(
:osf_archive,
source: "https://osf.io/#{osf_registration_identifier}",
osf_project_identifier: osf_project_identifier,
date_archived: 0.days.ago
)
end

it 'has expected JSON-LD, RELS-EXT, setter/getter behavior, and #archived_versions_of_source_project' do
osf_archive = OsfArchive.find(current_version.id)

# Ensuring that the previousVersion works
expect(osf_archive.previousVersion).to eq(previous_version)

# Ensuring a well configured SOLR
to_solr = osf_archive.to_solr
expect(to_solr.fetch(OsfArchive::SOLR_KEY_SOURCE)).to eq([osf_archive.source])
expect(to_solr.fetch(OsfArchive::SOLR_KEY_OSF_PROJECT_IDENTIFIER)).to eq([osf_project_identifier])
expect(to_solr.key?(OsfArchive::SOLR_KEY_ARCHIVED_DATE)).to eq(true)

# Ensuring #archived_versions_of_source_project
archived_versions_of_source_project = osf_archive.archived_versions_of_source_project
expect(archived_versions_of_source_project).to eq([
previous_version, current_version, project_registration
])

jsonld = osf_archive.as_jsonld
# Ensuring that the as_jsonld contains the correct relationship
expect(jsonld.fetch('@context').fetch('pav')).to eq('http://purl.org/pav/')
expect(jsonld.fetch('@context').fetch('previousVersion')).to eq('pav:previousVersion')
expect(jsonld.fetch("previousVersion")).to eq({"@id" => previous_version.pid})

# Ensuring that we have a meaningful RELS-EXT
rels_ext = osf_archive.datastreams.fetch('RELS-EXT').content
expect(rels_ext).to include(%(xmlns:pav="http://purl.org/pav/"))
expect(rels_ext).to include(%(<pav:previousVersion rdf:resource="info:fedora/#{previous_version.pid}"></pav:previousVersion>))
end
end
end

0 comments on commit 96a9c59

Please sign in to comment.