diff --git a/.dockerignore b/.dockerignore index bb0342712..512bc2abf 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,3 +8,4 @@ db/*.sqlite3-journal log/* node_modules tmp +ops/provision diff --git a/.env b/.env index e6595e3e9..529da5fc1 100644 --- a/.env +++ b/.env @@ -1,13 +1,26 @@ AAPB_HOST=americanarchive.org APP_NAME=gbh-hyrax CHROME_HOSTNAME=chrome -DB_ADAPTER=mysql2 +DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL=true +DB_ADAPTER=postgresql +DB_HOST=postgres +DB_NAME=ams +DB_PASSWORD=ams_password +DB_PORT=5432 +DB_TEST_NAME=ams_test +DB_USERNAME=ams_user FCREPO_URL=http://fcrepo:8080/rest +HYRAX_VALKYRIE=true MYSQL_DATABASE=gbh MYSQL_HOST=db MYSQL_PASSWORD=DatabaseFTW MYSQL_ROOT_PASSWORD=DatabaseFTW MYSQL_USER=root +NODE_OPTIONS=--openssl-legacy-provider +POSTGRES_DB=ams +POSTGRES_HOST_AUTH_METHOD=trust +POSTGRES_PASSWORD=ams_password +POSTGRES_USER=ams_user REDIS_HOST=redis REDIS_SERVER=redis SETTINGS__BULKRAX__ENABLED=true @@ -24,16 +37,8 @@ SOLR_HOST=solr SOLR_PORT=8983 SOLR_URL="http://${SOLR_ADMIN_USER}:${SOLR_ADMIN_PASSWORD}@solr:8983/solr/" TAG=latest +TB_RSPEC_FORMATTER=progress +TB_RSPEC_OPTIONS="--format RspecJunitFormatter --out rspec.xml" TEST_DB=GBH_test +VALKYRIE_ID_TYPE=string ZK_HOST=zoo:2181 - -# SMTP Mailer variables -# To enable mailer: -# - Uncomment and edit SMTP vars -# - Uncomment SMTP Mailer section in respective config/environments file -# SMTP_USER_NAME=CHANGEME -# SMTP_PASSWORD=CHANGEME -# SMTP_ADDRESS=CHANGEME -# SMTP_DOMAIN=CHANGEME -# SMTP_PORT=CHANGEME -# SMTP_TYPE=CHA NGEME diff --git a/.github/workflows/build-test-lint.yaml b/.github/workflows/build-test-lint.yaml index 5820118a0..d1446240f 100644 --- a/.github/workflows/build-test-lint.yaml +++ b/.github/workflows/build-test-lint.yaml @@ -14,9 +14,10 @@ on: env: REGISTRY: ghcr.io + RAILS_ENV: test jobs: - build-rails-5-1: + build-rails-6-1: runs-on: ubuntu-latest steps: - name: Set env @@ -25,6 +26,7 @@ jobs: echo ${HEAD_TAG::8} env: HEAD_TAG: ${{ github.event.pull_request.head.sha || github.sha }} + SE_NODE_SESSION_TIMEOUT: 999999 shell: bash - name: Downcase repo run: echo "REPO_LOWER=${REPO,,}" >> $GITHUB_ENV @@ -45,7 +47,7 @@ jobs: - name: Pull from cache to speed up build run: >- touch .env.development; - TAG=latest docker-compose pull web || true + TAG=latest docker compose pull web || true - name: Setup tmate session uses: mxschmitt/action-tmate@v3 if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} @@ -104,10 +106,10 @@ jobs: # - name: Pull from cache to speed up build # run: >- # touch .env.development; - # docker-compose pull web; - # docker-compose pull worker + # docker compose pull web; + # docker compose pull worker # - name: Run Rubocop - # run: docker-compose run web bundle exec rubocop --parallel --format progress --format junit --out rubocop.xml --display-only-failed + # run: docker compose run web bundle exec rubocop --parallel --format progress --format junit --out rubocop.xml --display-only-failed # - name: Publish Test Report # uses: mikepenz/action-junit-report@v3 # if: always() # always run even if the previous step fails @@ -115,7 +117,7 @@ jobs: # report_paths: 'rubocop*.xml' test: - needs: build-rails-5-1 + needs: build-rails-6-1 runs-on: ubuntu-latest strategy: fail-fast: false @@ -131,6 +133,7 @@ jobs: ALLOW_ANONYMOUS_LOGIN: "yes" CONFDIR: "/app/samvera/hyrax-webapp/solr/config" DB_CLEANER_ALLOW_REMOTE_DB_URL: "true" + SE_NODE_SESSION_TIMEOUT: 999999 TB_RSPEC_FORMATTER: progress TB_RSPEC_OPTIONS: --format RspecJunitFormatter --out rspec.xml steps: @@ -152,25 +155,25 @@ jobs: - name: Pull from cache to speed up build run: >- touch .env.development; - docker-compose pull web; - docker-compose pull worker + docker compose pull web; + docker compose pull worker - name: Setup tmate session uses: mxschmitt/action-tmate@v3 if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} with: limit-access-to-actor: true - name: Start containers - run: docker-compose up -d web + run: docker compose up -d web - name: Setup solr run: >- - docker-compose exec -T web bash -c + docker compose exec -T web bash -c "solrcloud-upload-configset.sh /app/samvera/hyrax-webapp/solr/config && SOLR_COLLECTION_NAME=hydra-test solrcloud-assign-configset.sh && solrcloud-assign-configset.sh" - name: Setup db run: >- - docker-compose exec -T web bash -c - "RAILS_ENV=test bundle exec rake db:schema:load db:migrate db:seed" + docker compose exec -T web bash -c + "RAILS_ENV=test bundle exec rake db:environment:set db:schema:load db:migrate db:seed" - name: Run Specs id: run-specs env: @@ -181,84 +184,18 @@ jobs: CI_NODE_INDEX: ${{ matrix.ci_node_index }} continue-on-error: true run: >- - docker-compose exec -T web bash -c + docker compose exec -T web bash -c "gem install semaphore_test_boosters && - rspec_booster --job $CI_NODE_INDEX/$CI_NODE_TOTAL" - - name: Fail job if spec failure - run: if [[ ${{ steps.run-specs.outcome }} == "failure" ]]; then exit 1; else exit 0; fi + rspec_booster --job $CI_NODE_INDEX/$CI_NODE_TOTAL && + ls -l $PWD" - name: Publish Test Report uses: mikepenz/action-junit-report@v3 - if: always() # always run even if the previous step fails - with: - report_paths: 'rspec*.xml' - build-rails-6-1: - runs-on: ubuntu-latest - steps: - - name: Set env - run: >- - echo "TAG=${HEAD_TAG::8}" >> ${GITHUB_ENV}; - echo ${HEAD_TAG::8} - env: - HEAD_TAG: ${{ github.event.pull_request.head.sha || github.sha }} - SE_NODE_SESSION_TIMEOUT: 999999 - BUNDLE_GEMFILE: Gemfile_next - BUNDLER_VERSION: 2.0.1 - shell: bash - - name: Downcase repo - run: echo "REPO_LOWER=${REPO,,}" >> $GITHUB_ENV - env: - REPO: '${{ github.repository }}' - - name: Checkout code - uses: actions/checkout@v3 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Github Container Login - uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Pull from cache to speed up build - run: >- - touch .env.development; - TAG=latest docker-compose pull web || true - - name: Setup tmate session - uses: mxschmitt/action-tmate@v3 - if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} - with: - limit-access-to-actor: true - - name: Build and push - uses: docker/build-push-action@v3 - with: - context: . - # platforms: linux/amd64,linux/arm64 - platforms: linux/amd64 - target: ams-base - build-args: | - SETTINGS__BULKRAX__ENABLED=true - EXTRA_APK_PACKAGES=less vim bash openjdk11-jre ffmpeg rsync yarn - cache-from: | - ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}:${TAG} - push: true - tags: | - ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}:${{ env.TAG }} - - name: Build and push worker - uses: docker/build-push-action@v3 + if: success() || failure() with: - context: . - platforms: linux/amd64 - # platforms: linux/amd64,linux/arm64 - target: ams-worker - build-args: | - SETTINGS__BULKRAX__ENABLED=true - EXTRA_APK_PACKAGES=less vim bash openjdk11-jre ffmpeg rsync yarn - cache-from: | - ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}:${TAG} - push: true - tags: | - ${{ env.REGISTRY }}/${{ env.REPO_LOWER }}/worker:${{ env.TAG }} + report_paths: '**/rspec*.xml' + fail_on_failure: true + require_passed_tests: true + detailed_summary: true # lint: # needs: build @@ -282,93 +219,12 @@ jobs: # - name: Pull from cache to speed up build # run: >- # touch .env.development; - # docker-compose pull web; - # docker-compose pull worker + # docker compose pull web; + # docker compose pull worker # - name: Run Rubocop - # run: docker-compose run web bundle exec rubocop --parallel --format progress --format junit --out rubocop.xml --display-only-failed + # run: docker compose run web bundle exec rubocop --parallel --format progress --format junit --out rubocop.xml --display-only-failed # - name: Publish Test Report # uses: mikepenz/action-junit-report@v3 # if: always() # always run even if the previous step fails # with: # report_paths: 'rubocop*.xml' - - test-rails-6-1: - needs: build-rails-6-1 - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - # Set N number of parallel jobs you want to run tests on. - # Use higher number if you have slow tests to split them on more parallel jobs. - # Remember to update ci_node_index below to 0..N-1 - ci_node_total: [3] - # set N-1 indexes for parallel jobs - # When you run 2 parallel jobs then first job will have index 0, the second job will have index 1 etc - ci_node_index: [0, 1, 2] - env: - ALLOW_ANONYMOUS_LOGIN: "yes" - BUNDLE_GEMFILE: Gemfile_next - BUNDLER_VERSION: 2.0.1 - CONFDIR: "/app/samvera/hyrax-webapp/solr/config" - DB_CLEANER_ALLOW_REMOTE_DB_URL: "true" - SE_NODE_SESSION_TIMEOUT: 999999 - TB_RSPEC_FORMATTER: progress - TB_RSPEC_OPTIONS: --format RspecJunitFormatter --out rspec.xml - steps: - - name: Set env - run: >- - echo "TAG=${HEAD_TAG::8}" >> ${GITHUB_ENV}; - echo ${HEAD_TAG::8} - env: - HEAD_TAG: ${{ github.event.pull_request.head.sha || github.sha }} - shell: bash - - name: Checkout code - uses: actions/checkout@v3 - - name: Github Container Login - uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Pull from cache to speed up build - run: >- - touch .env.development; - docker-compose pull web; - docker-compose pull worker - - name: Setup tmate session - uses: mxschmitt/action-tmate@v3 - if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} - with: - limit-access-to-actor: true - - name: Start containers - run: docker-compose up -d web - - name: Setup solr - run: >- - docker-compose exec -T web bash -c - "solrcloud-upload-configset.sh /app/samvera/hyrax-webapp/solr/config && - SOLR_COLLECTION_NAME=hydra-test solrcloud-assign-configset.sh && - solrcloud-assign-configset.sh" - - name: Setup db - run: >- - docker-compose exec -T web bash -c - "RAILS_ENV=test bundle exec rake db:schema:load db:migrate db:seed" - - name: Run Specs - id: run-specs - env: - # Specifies how many jobs you would like to run in parallel, - # used for partitioning - CI_NODE_TOTAL: ${{ matrix.ci_node_total }} - # Use the index from matrix as an environment variable - CI_NODE_INDEX: ${{ matrix.ci_node_index }} - continue-on-error: true - run: >- - docker-compose exec -T web bash -c - "gem install semaphore_test_boosters && - rspec_booster --job $CI_NODE_INDEX/$CI_NODE_TOTAL" - - name: Fail job if spec failure - run: if [[ ${{ steps.run-specs.outcome }} == "failure" ]]; then exit 1; else exit 0; fi - - name: Publish Test Report - uses: mikepenz/action-junit-report@v3 - if: always() # always run even if the previous step fails - with: - report_paths: 'rspec*.xml' diff --git a/Dockerfile b/Dockerfile index c03945097..d9da74038 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,4 @@ -ARG HYRAX_IMAGE_VERSION=v4.0.0.beta2 -ARG RUBY_VERSION=2.7.6 -FROM ruby:$RUBY_VERSION-alpine3.15 as builder - -RUN apk add build-base -RUN wget -O - https://github.com/jemalloc/jemalloc/releases/download/5.2.1/jemalloc-5.2.1.tar.bz2 | tar -xj && \ - cd jemalloc-5.2.1 && \ - ./configure && \ - make && \ - make install - - +ARG HYRAX_IMAGE_VERSION=hyrax-v5.0.0.rc1 FROM ghcr.io/samvera/hyrax/hyrax-base:$HYRAX_IMAGE_VERSION as ams-base USER root @@ -42,9 +31,6 @@ RUN apk --no-cache upgrade && \ echo "******** Packages Installed *********" USER app -COPY --from=builder /usr/local/lib/libjemalloc.so.2 /usr/local/lib/ -ENV LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 - RUN mkdir -p /app/fits && \ cd /app/fits && \ wget https://github.com/harvard-lts/fits/releases/download/1.5.5/fits-1.5.5.zip -O fits.zip && \ @@ -53,6 +39,7 @@ RUN mkdir -p /app/fits && \ chmod a+x /app/fits/fits.sh ENV PATH="${PATH}:/app/fits" +RUN rm -f /app/samvera/rails COPY --chown=1001:101 $APP_PATH/Gemfile* /app/samvera/hyrax-webapp/ RUN bundle install --jobs "$(nproc)" @@ -60,11 +47,7 @@ RUN bundle install --jobs "$(nproc)" # COPY --chown=1001:101 $APP_PATH/Gemfile /app/samvera/hyrax-webapp/Gemfile_next # RUN DEPENDENCIES_NEXT=1 bundle install --jobs "$(nproc)" -COPY --chown=1001:101 $APP_PATH/Gemfile /app/samvera/hyrax-webapp/Gemfile -RUN bundle install --jobs "$(nproc)" - COPY --chown=1001:101 $APP_PATH /app/samvera/hyrax-webapp - ARG SETTINGS__BULKRAX__ENABLED="false" # NOTE Bootboot enablement @@ -72,9 +55,8 @@ ARG SETTINGS__BULKRAX__ENABLED="false" # DEPENDENCIES_NEXT=1 yarn install && \ # SOLR_URL=localhost DEPENDENCIES_NEXT=1 RAILS_ENV=production SECRET_KEY_BASE=fake-key-for-asset-building-only DB_ADAPTER=nulldb bundle exec rake assets:precompile" -# RUN sh -l -c " \ -# yarn install && \ -# RAILS_ENV=production SECRET_KEY_BASE=fake-key-for-asset-building-only DB_ADAPTER=nulldb bundle exec rake assets:precompile" +RUN sh -l -c " \ + NODE_OPTIONS=--openssl-legacy-provider SOLR_URL=http://localhost:8983 RAILS_ENV=production SECRET_KEY_BASE=fake-key-for-asset-building-only DB_ADAPTER=nulldb bundle exec rake assets:precompile" CMD ./bin/web diff --git a/Gemfile b/Gemfile index e9561f72e..2a7db8e1f 100644 --- a/Gemfile +++ b/Gemfile @@ -12,7 +12,7 @@ if ENV['DEPENDENCIES_NEXT'] && !ENV['DEPENDENCIES_NEXT'].empty? else gem 'rails', '~> 6.0' gem 'hyrax-batch_ingest', git: 'https://github.com/samvera-labs/hyrax-batch_ingest', branch: 'dependency-upgrades' - gem 'hyrax', '~> 4.0' + gem 'hyrax', github: 'samvera/hyrax', branch: 'double_combo' # , tag: 'hyrax-v5.0.0.rc1' # Use SCSS for stylesheets gem 'sass-rails', '~> 6.0' gem 'bootstrap', '~> 4.0' @@ -22,7 +22,7 @@ else gem 'blacklight_advanced_search', '7.0' gem 'twitter-typeahead-rails', '0.11.1.pre.corejavascript' # our custom changes require us to lock in the version of bulkrax - gem 'bulkrax', git: 'https://github.com/samvera-labs/bulkrax.git', branch: 'hyrax-4-support' + gem 'bulkrax', git: 'https://github.com/samvera-labs/bulkrax.git', branch: 'hyrax-4-valkyrie-support' gem 'sidekiq', '~> 6.5.6' end @@ -79,7 +79,7 @@ group :development do # gem 'spring' # gem 'spring-watcher-listen', '~> 2.0.0' gem "letter_opener" - gem 'faker' + gem 'faker', '~> 3.0' # gem 'xray-rails' # should be commented out when actively using sidekiq. end @@ -92,9 +92,10 @@ gem 'aws-sdk-s3' gem 'aws-sdk-codedeploy' gem 'carrierwave', '~> 1.3' gem 'mysql2', '~> 0.5.3' +gem 'pg' gem 'nokogiri' gem 'bootstrap-multiselect-rails' -gem 'pbcore', '~> 0.3.0' +gem 'pbcore', github: 'scientist-softserv/pbcore', branch: 'fake_out' gem 'curb' # gem 'sony_ci_api', '~> 0.2.1' # gem 'hyrax-iiif_av', '>= 0.2.0' @@ -104,6 +105,8 @@ gem 'react-rails' gem 'database_cleaner' gem 'redlock', '~> 1.0' gem 'httparty', '~> 0.21' +# The maintainers yanked 0.3.2 version (see https://github.com/dryruby/json-canonicalization/issues/2) +gem 'json-canonicalization', "0.3.3" # Sentry-ruby for error handling gem "sentry-ruby" diff --git a/Gemfile.lock b/Gemfile.lock index 416c04852..02cc43dee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,13 +10,13 @@ GIT GIT remote: https://github.com/samvera-labs/bulkrax.git - revision: fe51a438e876406f75692fae11e2b2908123cc0b - branch: hyrax-4-support + revision: ed49dc2b366cd564ea9593aa60fdb6c63aa5aec9 + branch: hyrax-4-valkyrie-support specs: bulkrax (5.3.0) - bagit (~> 0.4) + bagit (~> 0.4.6) coderay - dry-monads (~> 1.5.0) + dry-monads (~> 1.5) iso8601 (~> 0.9.0) kaminari language_list (~> 1.2, >= 1.2.1) @@ -31,51 +31,123 @@ GIT GIT remote: https://github.com/samvera-labs/hyrax-batch_ingest - revision: 7983d39c62a7c288f964602b4055c33bc19d537d + revision: cab14d5db9f5b54ab7ee076c5b056bd49088a7c2 branch: dependency-upgrades specs: hyrax-batch_ingest (0.2.0) - hyrax (~> 4.0) + hyrax (>= 4.0, < 6.0) rails (~> 6.0) roo (~> 2.7.0) +GIT + remote: https://github.com/samvera/hyrax.git + revision: a904e5a07040c148e4d6c3a9288d048efc8e42b9 + branch: double_combo + specs: + hyrax (5.0.0.rc1) + active-fedora (~> 14.0) + almond-rails (~> 0.1) + awesome_nested_set (~> 3.1) + blacklight (~> 7.29) + blacklight-gallery (~> 4.0) + breadcrumbs_on_rails (~> 3.0) + browse-everything (>= 0.16, < 2.0) + carrierwave (~> 1.0) + clipboard-rails (~> 1.5) + connection_pool (~> 2.4) + draper (~> 4.0) + dry-container (~> 0.11) + dry-equalizer (~> 0.2) + dry-events (~> 1.0.0) + dry-logic (~> 1.5) + dry-monads (~> 1.5) + dry-struct (~> 1.0) + dry-validation (~> 1.3) + flipflop (~> 2.3) + flot-rails (~> 0.0.6) + font-awesome-rails (~> 4.2) + hydra-derivatives (~> 3.3) + hydra-editor (~> 6.0) + hydra-file_characterization (~> 1.1) + hydra-head (~> 12.0) + hydra-works (>= 0.16) + iiif_manifest (>= 0.3, < 2.0) + json-schema + legato (~> 0.3) + linkeddata + mailboxer (~> 0.12) + nest (~> 3.1) + noid-rails (~> 3.0) + oauth + oauth2 (~> 1.2) + openseadragon + posix-spawn + qa (~> 5.5, >= 5.5.1) + rails (~> 6.1) + rails_autolink (~> 1.1) + rdf-rdfxml + rdf-vocab (~> 3.0) + redis (~> 4.0) + redis-namespace (~> 1.5) + redlock (>= 0.1.2, < 2.0) + reform (~> 2.3) + reform-rails (~> 0.2.0) + retriable (>= 2.9, < 4.0) + sass-rails (~> 6.0) + select2-rails (~> 3.5) + signet + sprockets (~> 3.7) + tinymce-rails (~> 5.10) + valkyrie (~> 3.0.1) + view_component (~> 2.74.1) + +GIT + remote: https://github.com/scientist-softserv/pbcore.git + revision: 21677654f820dac3c8642b63e775ee2342a84114 + branch: fake_out + specs: + pbcore (0.3.2) + factory_bot (~> 4.11) + nokogiri (~> 1.10) + sax-machine (~> 1.3) + GEM remote: https://rubygems.org/ specs: - actioncable (6.1.7.4) - actionpack (= 6.1.7.4) - activesupport (= 6.1.7.4) + actioncable (6.1.7.6) + actionpack (= 6.1.7.6) + activesupport (= 6.1.7.6) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7.4) - actionpack (= 6.1.7.4) - activejob (= 6.1.7.4) - activerecord (= 6.1.7.4) - activestorage (= 6.1.7.4) - activesupport (= 6.1.7.4) + actionmailbox (6.1.7.6) + actionpack (= 6.1.7.6) + activejob (= 6.1.7.6) + activerecord (= 6.1.7.6) + activestorage (= 6.1.7.6) + activesupport (= 6.1.7.6) mail (>= 2.7.1) - actionmailer (6.1.7.4) - actionpack (= 6.1.7.4) - actionview (= 6.1.7.4) - activejob (= 6.1.7.4) - activesupport (= 6.1.7.4) + actionmailer (6.1.7.6) + actionpack (= 6.1.7.6) + actionview (= 6.1.7.6) + activejob (= 6.1.7.6) + activesupport (= 6.1.7.6) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.7.4) - actionview (= 6.1.7.4) - activesupport (= 6.1.7.4) + actionpack (6.1.7.6) + actionview (= 6.1.7.6) + activesupport (= 6.1.7.6) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7.4) - actionpack (= 6.1.7.4) - activerecord (= 6.1.7.4) - activestorage (= 6.1.7.4) - activesupport (= 6.1.7.4) + actiontext (6.1.7.6) + actionpack (= 6.1.7.6) + activerecord (= 6.1.7.6) + activestorage (= 6.1.7.6) + activesupport (= 6.1.7.6) nokogiri (>= 1.8.5) - actionview (6.1.7.4) - activesupport (= 6.1.7.4) + actionview (6.1.7.6) + activesupport (= 6.1.7.6) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -98,30 +170,30 @@ GEM active_encode (0.8.2) rails sprockets (< 4) - activejob (6.1.7.4) - activesupport (= 6.1.7.4) + activejob (6.1.7.6) + activesupport (= 6.1.7.6) globalid (>= 0.3.6) - activemodel (6.1.7.4) - activesupport (= 6.1.7.4) + activemodel (6.1.7.6) + activesupport (= 6.1.7.6) activemodel-serializers-xml (1.0.2) activemodel (> 5.x) activesupport (> 5.x) builder (~> 3.1) - activerecord (6.1.7.4) - activemodel (= 6.1.7.4) - activesupport (= 6.1.7.4) - activerecord-import (1.4.1) + activerecord (6.1.7.6) + activemodel (= 6.1.7.6) + activesupport (= 6.1.7.6) + activerecord-import (1.5.0) activerecord (>= 4.2) activerecord-nulldb-adapter (0.9.0) activerecord (>= 5.2.0, < 7.1) - activestorage (6.1.7.4) - actionpack (= 6.1.7.4) - activejob (= 6.1.7.4) - activerecord (= 6.1.7.4) - activesupport (= 6.1.7.4) + activestorage (6.1.7.6) + actionpack (= 6.1.7.6) + activejob (= 6.1.7.6) + activerecord (= 6.1.7.6) + activesupport (= 6.1.7.6) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7.4) + activesupport (6.1.7.6) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -137,11 +209,11 @@ GEM awesome_nested_set (3.5.0) activerecord (>= 4.0.0, < 7.1) aws-eventstream (1.2.0) - aws-partitions (1.802.0) + aws-partitions (1.815.0) aws-sdk-codedeploy (1.57.0) aws-sdk-core (~> 3, >= 3.177.0) aws-sigv4 (~> 1.1) - aws-sdk-core (3.180.3) + aws-sdk-core (3.181.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) @@ -149,8 +221,8 @@ GEM aws-sdk-kms (1.71.0) aws-sdk-core (~> 3, >= 3.177.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.132.1) - aws-sdk-core (~> 3, >= 3.179.0) + aws-sdk-s3 (1.134.0) + aws-sdk-core (~> 3, >= 3.181.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.6) aws-sigv4 (1.6.0) @@ -159,11 +231,10 @@ GEM babel-transpiler (0.7.0) babel-source (>= 4.0, < 6) execjs (~> 2.0) - bagit (0.4.5) + bagit (0.4.6) docopt (~> 0.5.0) validatable (~> 1.6) - bcp47 (0.3.3) - i18n + bcp47_spec (0.2.1) bcrypt (3.1.19) bindex (0.8.1) bixby (5.0.2) @@ -284,61 +355,58 @@ GEM dropbox_api (0.1.21) faraday (< 3.0) oauth2 (~> 1.1) - dry-configurable (0.16.1) - dry-core (~> 0.6) + dry-configurable (1.1.0) + dry-core (~> 1.0, < 2) zeitwerk (~> 2.6) dry-container (0.11.0) concurrent-ruby (~> 1.0) - dry-core (0.9.1) + dry-core (1.0.1) concurrent-ruby (~> 1.0) zeitwerk (~> 2.6) dry-equalizer (0.3.0) - dry-events (0.2.0) + dry-events (1.0.1) concurrent-ruby (~> 1.0) - dry-core (~> 0.4) - dry-equalizer (~> 0.2) - dry-inflector (0.3.0) + dry-core (~> 1.0, < 2) + dry-inflector (1.0.0) dry-initializer (3.1.1) - dry-logic (1.3.0) + dry-logic (1.5.0) concurrent-ruby (~> 1.0) - dry-core (~> 0.9, >= 0.9) + dry-core (~> 1.0, < 2) zeitwerk (~> 2.6) - dry-monads (1.5.0) + dry-monads (1.6.0) concurrent-ruby (~> 1.0) - dry-core (~> 0.9, >= 0.9) + dry-core (~> 1.0, < 2) zeitwerk (~> 2.6) - dry-schema (1.11.3) + dry-schema (1.13.3) concurrent-ruby (~> 1.0) - dry-configurable (~> 0.16, >= 0.16) - dry-core (~> 0.9, >= 0.9) + dry-configurable (~> 1.0, >= 1.0.1) + dry-core (~> 1.0, < 2) dry-initializer (~> 3.0) - dry-logic (~> 1.3) - dry-types (~> 1.6) + dry-logic (>= 1.4, < 2) + dry-types (>= 1.7, < 2) zeitwerk (~> 2.6) - dry-struct (1.5.2) - dry-core (~> 0.9, >= 0.9) - dry-types (~> 1.6) + dry-struct (1.6.0) + dry-core (~> 1.0, < 2) + dry-types (>= 1.7, < 2) ice_nine (~> 0.11) zeitwerk (~> 2.6) - dry-types (1.6.1) + dry-types (1.7.1) concurrent-ruby (~> 1.0) - dry-container (~> 0.3) - dry-core (~> 0.9, >= 0.9) - dry-inflector (~> 0.1, >= 0.1.2) - dry-logic (~> 1.3, >= 1.3) + dry-core (~> 1.0) + dry-inflector (~> 1.0) + dry-logic (~> 1.4) zeitwerk (~> 2.6) - dry-validation (1.9.0) + dry-validation (1.10.0) concurrent-ruby (~> 1.0) - dry-container (~> 0.7, >= 0.7.1) - dry-core (~> 0.9, >= 0.9) + dry-core (~> 1.0, < 2) dry-initializer (~> 3.0) - dry-schema (~> 1.11, >= 1.11.0) + dry-schema (>= 1.12, < 2) zeitwerk (~> 2.6) - ebnf (2.3.5) + ebnf (2.4.0) htmlentities (~> 4.3) - rdf (~> 3.2) + rdf (~> 3.3) scanf (~> 1.0) - sxp (~> 1.2) + sxp (~> 1.3) unicode-types (~> 1.8) erubi (1.12.0) ethon (0.16.0) @@ -349,8 +417,8 @@ GEM factory_bot_rails (4.11.1) factory_bot (~> 4.11.1) railties (>= 3.0.0) - faker (1.9.6) - i18n (>= 0.7) + faker (3.2.1) + i18n (>= 1.8.11, < 2) faraday (1.3.1) faraday-net_http (~> 1.0) multipart-post (>= 1.2, < 3) @@ -375,8 +443,8 @@ GEM font-awesome-rails (4.7.0.8) railties (>= 3.2, < 8.0) geocoder (1.8.2) - globalid (1.1.0) - activesupport (>= 5.0) + globalid (1.2.1) + activesupport (>= 6.1) google-apis-core (0.11.1) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) @@ -386,16 +454,15 @@ GEM retriable (>= 2.0, < 4.a) rexml webrick - google-apis-drive_v3 (0.43.0) + google-apis-drive_v3 (0.44.0) google-apis-core (>= 0.11.0, < 2.a) - googleauth (1.7.0) + googleauth (1.8.1) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) - haml (6.1.1) + haml (6.1.2) temple (>= 0.8.2) thor tilt @@ -460,60 +527,6 @@ GEM hydra-derivatives (~> 3.6) hydra-file_characterization (~> 1.0) hydra-pcdm (>= 0.9) - hyrax (4.0.0) - active-fedora (~> 14.0) - almond-rails (~> 0.1) - awesome_nested_set (~> 3.1) - blacklight (~> 7.29) - blacklight-gallery (~> 4.0) - breadcrumbs_on_rails (~> 3.0) - browse-everything (>= 0.16, < 2.0) - carrierwave (~> 1.0) - clipboard-rails (~> 1.5) - connection_pool (~> 2.4) - draper (~> 4.0) - dry-equalizer (~> 0.2) - dry-events (~> 0.2.0) - dry-monads (~> 1.5) - dry-struct (~> 1.0) - dry-validation (~> 1.3) - flipflop (~> 2.3) - flot-rails (~> 0.0.6) - font-awesome-rails (~> 4.2) - hydra-derivatives (~> 3.3) - hydra-editor (~> 6.0) - hydra-file_characterization (~> 1.1) - hydra-head (~> 12.0) - hydra-works (>= 0.16) - iiif_manifest (>= 0.3, < 2.0) - json-schema - legato (~> 0.3) - linkeddata - mailboxer (~> 0.12) - nest (~> 3.1) - noid-rails (~> 3.0) - oauth - oauth2 (~> 1.2) - openseadragon - posix-spawn - qa (~> 5.5, >= 5.5.1) - rails (~> 6.0) - rails_autolink (~> 1.1) - rdf-rdfxml - rdf-vocab (~> 3.0) - redis (~> 4.0) - redis-namespace (~> 1.5) - redlock (>= 0.1.2, < 2.0) - reform (~> 2.3) - reform-rails (~> 0.2.0) - retriable (>= 2.9, < 4.0) - sass-rails (~> 6.0) - select2-rails (~> 3.5) - signet - sprockets (~> 3.7) - tinymce-rails (~> 5.10) - valkyrie (~> 3.0.1) - view_component (~> 2.74.1) i18n (1.14.1) concurrent-ruby (~> 1.0) ice_nine (0.11.2) @@ -529,18 +542,18 @@ GEM railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (2.6.3) - json-canonicalization (0.3.2) - json-ld (3.2.5) + json-canonicalization (0.3.3) + json-ld (3.3.0) htmlentities (~> 4.3) json-canonicalization (~> 0.3, >= 0.3.2) link_header (~> 0.0, >= 0.0.8) multi_json (~> 1.15) rack (>= 2.2, < 4) - rdf (~> 3.2, >= 3.2.10) - json-ld-preloaded (3.2.2) - json-ld (~> 3.2) - rdf (~> 3.2) - json-schema (4.0.0) + rdf (~> 3.3) + json-ld-preloaded (3.3.0) + json-ld (~> 3.3) + rdf (~> 3.3) + json-schema (4.1.1) addressable (>= 2.8) jwt (2.7.1) kaminari (1.2.2) @@ -558,12 +571,12 @@ GEM language_list (1.2.1) launchy (2.5.2) addressable (~> 2.8) - ld-patch (3.2.2) - ebnf (~> 2.3) - rdf (~> 3.2) - rdf-xsd (~> 3.2) - sparql (~> 3.2) - sxp (~> 1.2) + ld-patch (3.3.0) + ebnf (~> 2.4) + rdf (~> 3.3) + rdf-xsd (~> 3.3) + sparql (~> 3.3) + sxp (~> 1.3) ldp (1.2.0) deprecation faraday (>= 1) @@ -586,38 +599,37 @@ GEM launchy (>= 2.2, < 3) libxml-ruby (3.2.4) link_header (0.0.8) - linkeddata (3.2.2) - json-ld (~> 3.2, >= 3.2.5) - json-ld-preloaded (~> 3.2, >= 3.2.2) - ld-patch (~> 3.2, >= 3.2.2) - nokogiri (~> 1.13, >= 1.13.8) + linkeddata (3.3.1) + json-ld (~> 3.3) + json-ld-preloaded (~> 3.3) + ld-patch (~> 3.3) + nokogiri (~> 1.15, >= 1.15.4) rdf (~> 3.2, >= 3.2.1) - rdf-aggregate-repo (~> 3.2, >= 3.2.1) - rdf-hamster-repo (~> 3.2, >= 3.2.1) - rdf-isomorphic (~> 3.2, >= 3.2.1) - rdf-json (~> 3.2) - rdf-microdata (~> 3.2, >= 3.2.1) - rdf-n3 (~> 3.2, >= 3.2.1) - rdf-normalize (~> 0.6, >= 0.6.1) - rdf-ordered-repo (~> 3.2, >= 3.2.1) - rdf-rdfa (~> 3.2, >= 3.2.3) - rdf-rdfxml (~> 3.2, >= 3.2.2) - rdf-reasoner (~> 0.8) - rdf-tabular (~> 3.2, >= 3.2.1) - rdf-trig (~> 3.2) - rdf-trix (~> 3.2) - rdf-turtle (~> 3.2, >= 3.2.1) - rdf-vocab (~> 3.2, >= 3.2.7) - rdf-xsd (~> 3.2, >= 3.2.1) - shacl (~> 0.3) - shex (~> 0.7, >= 0.7.1) - sparql (~> 3.2, >= 3.2.6) - sparql-client (~> 3.2, >= 3.2.2) + rdf-aggregate-repo (~> 3.2) + rdf-hamster-repo (~> 3.3) + rdf-isomorphic (~> 3.3) + rdf-json (~> 3.3) + rdf-microdata (~> 3.3) + rdf-n3 (~> 3.3) + rdf-normalize (~> 0.7) + rdf-ordered-repo (~> 3.3) + rdf-rdfa (~> 3.3) + rdf-rdfxml (~> 3.3) + rdf-reasoner (~> 0.9) + rdf-tabular (~> 3.3) + rdf-trig (~> 3.3) + rdf-trix (~> 3.3) + rdf-turtle (~> 3.3) + rdf-vocab (~> 3.3) + rdf-xsd (~> 3.3) + shacl (~> 0.4) + shex (~> 0.8) + sparql (~> 3.3) + sparql-client (~> 3.3) yaml-ld (~> 0.0) - listen (3.1.5) + listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) logger (1.5.3) loofah (2.21.3) crass (~> 1.0.2) @@ -632,15 +644,14 @@ GEM rails (>= 5.0.0) marcel (1.0.2) matrix (0.4.2) - memoist (0.16.2) method_source (1.0.0) - mime-types (3.5.0) + mime-types (3.5.1) mime-types-data (~> 3.2015) mime-types-data (3.2023.0808) mini_magick (4.12.0) mini_mime (1.1.5) mini_portile2 (2.8.4) - minitest (5.19.0) + minitest (5.20.0) multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.3.0) @@ -694,11 +705,7 @@ GEM ast (~> 2.4.1) racc parslet (2.0.0) - pbcore (0.3.2) - factory_bot (~> 4.11) - faker (~> 1.9) - nokogiri (~> 1.10) - sax-machine (~> 1.3) + pg (1.5.3) popper_js (1.16.1) posix-spawn (0.3.15) pry (0.14.2) @@ -722,33 +729,33 @@ GEM rdf racc (1.7.1) rack (2.2.8) - rack-linkeddata (3.2.3) - linkeddata (~> 3.2, >= 3.2.2) + rack-linkeddata (3.3.0) + linkeddata (~> 3.3) rack (>= 2.2, < 4) - rack-rdf (~> 3.2, >= 3.2.3) + rack-rdf (~> 3.3) rack-protection (2.2.4) rack rack-proxy (0.7.6) rack - rack-rdf (3.2.3) + rack-rdf (3.3.0) rack (>= 2.2, < 4) - rdf (~> 3.2) + rdf (~> 3.3) rack-test (2.1.0) rack (>= 1.3) - rails (6.1.7.4) - actioncable (= 6.1.7.4) - actionmailbox (= 6.1.7.4) - actionmailer (= 6.1.7.4) - actionpack (= 6.1.7.4) - actiontext (= 6.1.7.4) - actionview (= 6.1.7.4) - activejob (= 6.1.7.4) - activemodel (= 6.1.7.4) - activerecord (= 6.1.7.4) - activestorage (= 6.1.7.4) - activesupport (= 6.1.7.4) + rails (6.1.7.6) + actioncable (= 6.1.7.6) + actionmailbox (= 6.1.7.6) + actionmailer (= 6.1.7.6) + actionpack (= 6.1.7.6) + actiontext (= 6.1.7.6) + actionview (= 6.1.7.6) + activejob (= 6.1.7.6) + activemodel (= 6.1.7.6) + activerecord (= 6.1.7.6) + activestorage (= 6.1.7.6) + activesupport (= 6.1.7.6) bundler (>= 1.15.0) - railties (= 6.1.7.4) + railties (= 6.1.7.6) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -765,9 +772,9 @@ GEM actionview (> 3.1) activesupport (> 3.1) railties (> 3.1) - railties (6.1.7.4) - actionpack (= 6.1.7.4) - activesupport (= 6.1.7.4) + railties (6.1.7.6) + actionpack (= 6.1.7.6) + activesupport (= 6.1.7.6) method_source rake (>= 12.2) thor (~> 1.0) @@ -776,17 +783,18 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rdf (3.2.11) + rdf (3.3.0) + bcp47_spec (~> 0.2) link_header (~> 0.0, >= 0.0.8) - rdf-aggregate-repo (3.2.1) - rdf (~> 3.2) - rdf-hamster-repo (3.2.1) + rdf-aggregate-repo (3.3.0) + rdf (~> 3.3) + rdf-hamster-repo (3.3.0) hamster (~> 3.0) - rdf (~> 3.2, >= 3.2.1) - rdf-isomorphic (3.2.1) - rdf (~> 3.2) - rdf-json (3.2.0) - rdf (~> 3.2) + rdf (~> 3.3) + rdf-isomorphic (3.3.0) + rdf (~> 3.3) + rdf-json (3.3.0) + rdf (~> 3.3) rdf-ldp (2.1.0) json-ld (~> 3.2) ld-patch (~> 3.2) @@ -797,57 +805,57 @@ GEM rdf-turtle (~> 3.2) rdf-vocab (~> 3.2) sinatra (~> 2.1) - rdf-microdata (3.2.1) + rdf-microdata (3.3.0) htmlentities (~> 4.3) - nokogiri (~> 1.13) - rdf (~> 3.2) - rdf-rdfa (~> 3.2) - rdf-xsd (~> 3.2) - rdf-n3 (3.2.1) - ebnf (~> 2.2) - rdf (~> 3.2) - sparql (~> 3.2) - sxp (~> 1.2) - rdf-normalize (0.6.1) - rdf (~> 3.2) - rdf-ordered-repo (3.2.1) - rdf (~> 3.2, >= 3.2.1) - rdf-rdfa (3.2.3) - haml (>= 5.2, < 7) + nokogiri (~> 1.15, >= 1.15.4) + rdf (~> 3.3) + rdf-rdfa (~> 3.3) + rdf-xsd (~> 3.3) + rdf-n3 (3.3.0) + ebnf (~> 2.4) + rdf (~> 3.3) + sparql (~> 3.3) + sxp (~> 1.3) + rdf-normalize (0.7.0) + rdf (~> 3.3) + rdf-ordered-repo (3.3.0) + rdf (~> 3.3) + rdf-rdfa (3.3.0) + haml (~> 6.1) htmlentities (~> 4.3) - rdf (~> 3.2) - rdf-aggregate-repo (~> 3.2) - rdf-vocab (~> 3.2) - rdf-xsd (~> 3.2) - rdf-rdfxml (3.2.2) - builder (~> 3.2) + rdf (~> 3.3) + rdf-aggregate-repo (~> 3.3) + rdf-vocab (~> 3.3) + rdf-xsd (~> 3.3) + rdf-rdfxml (3.3.0) + builder (~> 3.2, >= 3.2.4) htmlentities (~> 4.3) - rdf (~> 3.2) - rdf-xsd (~> 3.2) - rdf-reasoner (0.8.0) - rdf (~> 3.2) - rdf-xsd (~> 3.2) - rdf-tabular (3.2.1) + rdf (~> 3.3) + rdf-xsd (~> 3.3) + rdf-reasoner (0.9.0) + rdf (~> 3.3) + rdf-xsd (~> 3.3) + rdf-tabular (3.3.0) addressable (~> 2.8) - bcp47 (~> 0.3, >= 0.3.3) - json-ld (~> 3.2) - rdf (~> 3.2, >= 3.2.7) - rdf-vocab (~> 3.2) - rdf-xsd (~> 3.2) - rdf-trig (3.2.0) - ebnf (~> 2.2) - rdf (~> 3.2) - rdf-turtle (~> 3.2) - rdf-trix (3.2.0) - rdf (~> 3.2) - rdf-xsd (~> 3.2) - rdf-turtle (3.2.1) - ebnf (~> 2.3) - rdf (~> 3.2) - rdf-vocab (3.2.7) - rdf (~> 3.2, >= 3.2.4) - rdf-xsd (3.2.1) - rdf (~> 3.2) + bcp47_spec (~> 0.2) + json-ld (~> 3.3) + rdf (~> 3.3) + rdf-vocab (~> 3.3) + rdf-xsd (~> 3.3) + rdf-trig (3.3.0) + ebnf (~> 2.4) + rdf (~> 3.3) + rdf-turtle (~> 3.3) + rdf-trix (3.3.0) + rdf (~> 3.3) + rdf-xsd (~> 3.3) + rdf-turtle (3.3.0) + ebnf (~> 2.4) + rdf (~> 3.3) + rdf-vocab (3.3.0) + rdf (~> 3.3) + rdf-xsd (3.3.0) + rdf (~> 3.3) rexml (~> 3.2) react-rails (2.7.1) babel-transpiler (>= 0.7.0) @@ -866,7 +874,7 @@ GEM disposable (>= 0.5.0, < 1.0.0) representable (>= 3.1.1, < 4) uber (< 0.2.0) - reform-rails (0.2.5) + reform-rails (0.2.6) activemodel (>= 5.0) reform (>= 2.3.1, < 3.0.0) regexp_parser (2.8.1) @@ -944,7 +952,6 @@ GEM oauth2 ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) - ruby_dep (1.5.0) rubyzip (1.3.0) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) @@ -966,27 +973,27 @@ GEM semantic_range (3.0.0) sentry-ruby (5.4.2) concurrent-ruby (~> 1.0, >= 1.0.2) - shacl (0.3.0) - json-ld (~> 3.2) - rdf (~> 3.2, >= 3.2.8) - sparql (~> 3.2, >= 3.2.4) + shacl (0.4.0) + json-ld (~> 3.3) + rdf (~> 3.3) + sparql (~> 3.3) sxp (~> 1.2) - shex (0.7.1) - ebnf (~> 2.2) + shex (0.8.0) + ebnf (~> 2.4) htmlentities (~> 4.3) - json-ld (~> 3.2) - json-ld-preloaded (~> 3.2) - rdf (~> 3.2) - rdf-xsd (~> 3.2) - sparql (~> 3.2) - sxp (~> 1.2) + json-ld (~> 3.3) + json-ld-preloaded (~> 3.3) + rdf (~> 3.3) + rdf-xsd (~> 3.3) + sparql (~> 3.3) + sxp (~> 1.3) shoulda-matchers (5.3.0) activesupport (>= 5.2.0) sidekiq (6.5.9) connection_pool (>= 2.2.5, < 3) rack (~> 2.0) redis (>= 4.5.0, < 5) - signet (0.17.0) + signet (0.18.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) @@ -1008,18 +1015,18 @@ GEM retriable ruby-progressbar rubyzip - sparql (3.2.6) + sparql (3.3.0) builder (~> 3.2, >= 3.2.4) - ebnf (~> 2.3, >= 2.3.5) + ebnf (~> 2.4) logger (~> 1.5) - rdf (~> 3.2, >= 3.2.11) - rdf-aggregate-repo (~> 3.2, >= 3.2.1) - rdf-xsd (~> 3.2) - sparql-client (~> 3.2, >= 3.2.2) - sxp (~> 1.2, >= 1.2.4) - sparql-client (3.2.2) + rdf (~> 3.3) + rdf-aggregate-repo (~> 3.3) + rdf-xsd (~> 3.3) + sparql-client (~> 3.3) + sxp (~> 1.3) + sparql-client (3.3.0) net-http-persistent (~> 4.0, >= 4.0.2) - rdf (~> 3.2, >= 3.2.11) + rdf (~> 3.3) sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) @@ -1032,9 +1039,9 @@ GEM activesupport (>= 5.2) sprockets (>= 3.0.0) ssrf_filter (1.0.8) - sxp (1.2.4) + sxp (1.3.0) matrix (~> 0.4) - rdf (~> 3.2) + rdf (~> 3.3) temple (0.10.2) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) @@ -1137,19 +1144,21 @@ DEPENDENCIES devise-guests (~> 0.6) dotenv-rails factory_bot_rails (~> 4.0) - faker + faker (~> 3.0) fcrepo_wrapper httparty (~> 0.21) hydra-role-management (= 1.1.0) - hyrax (~> 4.0) + hyrax! hyrax-batch_ingest! jbuilder (~> 2.5) jquery-rails + json-canonicalization (= 0.3.3) letter_opener listen (>= 3.0.5, < 3.2) mysql2 (~> 0.5.3) nokogiri - pbcore (~> 0.3.0) + pbcore! + pg pry-byebug puma (~> 4.3) rails (~> 6.0) diff --git a/app/assets/images/asset_resource.png b/app/assets/images/asset_resource.png new file mode 100644 index 000000000..7e4fef20f Binary files /dev/null and b/app/assets/images/asset_resource.png differ diff --git a/app/assets/images/digital_instantiation_resource.png b/app/assets/images/digital_instantiation_resource.png new file mode 100644 index 000000000..a02ef7a00 Binary files /dev/null and b/app/assets/images/digital_instantiation_resource.png differ diff --git a/app/assets/images/essence_track_resource.png b/app/assets/images/essence_track_resource.png new file mode 100644 index 000000000..0a40e428b Binary files /dev/null and b/app/assets/images/essence_track_resource.png differ diff --git a/app/assets/images/physical_instantiation_resource.png b/app/assets/images/physical_instantiation_resource.png new file mode 100644 index 000000000..a04a9a0ea Binary files /dev/null and b/app/assets/images/physical_instantiation_resource.png differ diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb index 1f8bed55d..4efa07db7 100644 --- a/app/controllers/catalog_controller.rb +++ b/app/controllers/catalog_controller.rb @@ -465,17 +465,17 @@ class CatalogController < ApplicationController config.add_sort_field "#{solr_name('system_create', :stored_sortable, type: :date)} asc", label: "date uploaded \u25B2" config.add_sort_field "#{solr_name('system_modified', :stored_sortable, type: :date)} desc", label: "date modified \u25BC" config.add_sort_field "#{solr_name('system_modified', :stored_sortable, type: :date)} asc", label: "date modified \u25B2" - config.add_sort_field "#{solr_name('broadcast', :stored_sortable)} dsc", label: "broadcast \u25BC" + config.add_sort_field "#{solr_name('broadcast', :stored_sortable)} desc", label: "broadcast \u25BC" config.add_sort_field "#{solr_name('broadcast', :stored_sortable)} asc", label: "broadcast \u25B2" - config.add_sort_field "#{solr_name('created', :stored_sortable, type: :date)} dsc", label: "created \u25BC" + config.add_sort_field "#{solr_name('created', :stored_sortable, type: :date)} desc", label: "created \u25BC" config.add_sort_field "#{solr_name('created', :stored_sortable, type: :date)} asc", label: "created \u25B2" - config.add_sort_field "#{solr_name('copyright_date', :stored_sortable, type: :date)} dsc", label: "copyright date \u25BC" + config.add_sort_field "#{solr_name('copyright_date', :stored_sortable, type: :date)} desc", label: "copyright date \u25BC" config.add_sort_field "#{solr_name('copyright_date', :stored_sortable, type: :date)} asc", label: "copyright date \u25B2" - config.add_sort_field "#{solr_name('date', :stored_sortable, type: :date)} dsc", label: "date \u25BC" + config.add_sort_field "#{solr_name('date', :stored_sortable, type: :date)} desc", label: "date \u25BC" config.add_sort_field "#{solr_name('date', :stored_sortable, type: :date)} asc", label: "date \u25B2" - config.add_sort_field "#{solr_name('title', :stored_sortable)} dsc", label: "title \u25BC" + config.add_sort_field "#{solr_name('title', :stored_sortable)} desc", label: "title \u25BC" config.add_sort_field "#{solr_name('title', :stored_sortable)} asc", label: "title \u25B2" - config.add_sort_field "#{solr_name('episode_number', :stored_sortable)} dsc", label: "Episode Number \u25BC" + config.add_sort_field "#{solr_name('episode_number', :stored_sortable)} desc", label: "Episode Number \u25BC" config.add_sort_field "#{solr_name('episode_number', :stored_sortable)} asc", label: "Episode Number \u25B2" # If there are more than this many search results, no spelling ("did you diff --git a/app/controllers/concerns/hyrax/child_work_redirect.rb b/app/controllers/concerns/hyrax/child_work_redirect.rb index 61b1802e5..64a896ede 100644 --- a/app/controllers/concerns/hyrax/child_work_redirect.rb +++ b/app/controllers/concerns/hyrax/child_work_redirect.rb @@ -17,7 +17,7 @@ def after_create_response end def after_update_response - if curation_concern.file_sets.present? + if curation_concern.try(:file_sets).present? || Hyrax.custom_queries.find_child_file_sets(resource: curation_concern).to_a.present? return redirect_to hyrax.confirm_access_permission_path(curation_concern) if permissions_changed? return redirect_to main_app.confirm_hyrax_permission_path(curation_concern) if curation_concern.visibility_changed? end diff --git a/app/controllers/hyrax/asset_resources_controller.rb b/app/controllers/hyrax/asset_resources_controller.rb index 62e2c1db0..d82bd8a36 100644 --- a/app/controllers/hyrax/asset_resources_controller.rb +++ b/app/controllers/hyrax/asset_resources_controller.rb @@ -9,9 +9,21 @@ class AssetResourcesController < ApplicationController include Hyrax::WorksControllerBehavior include Hyrax::BreadcrumbsForWorks self.curation_concern_type = ::AssetResource + # Handle Child Work button and redirect to child work page + include Hyrax::ChildWorkRedirect # Use a Valkyrie aware form service to generate Valkyrie::ChangeSet style # forms. self.work_form_service = Hyrax::FormFactory.new + self.show_presenter = AssetResourcePresenter + + private + # This extends functionality from + # Hyrax::WorksControllerBehavior#additional_response_formats, adding a + # response for a ".xml" extension, returning the PBCore XML. + def additional_response_formats(format) + format.xml { render(plain: presenter.solr_document.export_as_pbcore) } + super + end end end diff --git a/app/controllers/hyrax/contribution_resources_controller.rb b/app/controllers/hyrax/contribution_resources_controller.rb index 2bf57cf5c..1ec7f4306 100644 --- a/app/controllers/hyrax/contribution_resources_controller.rb +++ b/app/controllers/hyrax/contribution_resources_controller.rb @@ -8,10 +8,16 @@ class ContributionResourcesController < ApplicationController # Adds Hyrax behaviors to the controller. include Hyrax::WorksControllerBehavior include Hyrax::BreadcrumbsForWorks + # Handle Child Work button and redirect to child work page + include Hyrax::ChildWorkRedirect + # Redirects away from controller#new if object does not have parent_id + include Hyrax::RedirectNewAction + self.curation_concern_type = ::ContributionResource # Use a Valkyrie aware form service to generate Valkyrie::ChangeSet style # forms. self.work_form_service = Hyrax::FormFactory.new + self.show_presenter = ContributionResourcePresenter end end diff --git a/app/controllers/hyrax/digital_instantiation_resources_controller.rb b/app/controllers/hyrax/digital_instantiation_resources_controller.rb index 3df1bd113..3afbb112b 100644 --- a/app/controllers/hyrax/digital_instantiation_resources_controller.rb +++ b/app/controllers/hyrax/digital_instantiation_resources_controller.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true - + # Generated via # `rails generate hyrax:work_resource DigitalInstantiationResource` module Hyrax @@ -8,10 +8,16 @@ class DigitalInstantiationResourcesController < ApplicationController # Adds Hyrax behaviors to the controller. include Hyrax::WorksControllerBehavior include Hyrax::BreadcrumbsForWorks + # Handle Child Work button and redirect to child work page + include Hyrax::ChildWorkRedirect + # Redirects away from controller#new if object does not have parent_id + include Hyrax::RedirectNewAction + self.curation_concern_type = ::DigitalInstantiationResource # Use a Valkyrie aware form service to generate Valkyrie::ChangeSet style # forms. self.work_form_service = Hyrax::FormFactory.new + self.show_presenter = DigitalInstantiationResourcePresenter end end diff --git a/app/controllers/hyrax/essence_track_resources_controller.rb b/app/controllers/hyrax/essence_track_resources_controller.rb index 80dff3097..be35deacf 100644 --- a/app/controllers/hyrax/essence_track_resources_controller.rb +++ b/app/controllers/hyrax/essence_track_resources_controller.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true - + # Generated via # `rails generate hyrax:work_resource EssenceTrackResource` module Hyrax @@ -8,10 +8,16 @@ class EssenceTrackResourcesController < ApplicationController # Adds Hyrax behaviors to the controller. include Hyrax::WorksControllerBehavior include Hyrax::BreadcrumbsForWorks + # Handle Child Work button and redirect to child work page + include Hyrax::ChildWorkRedirect + # Redirects away from controller#new if object does not have parent_id + include Hyrax::RedirectNewAction + self.curation_concern_type = ::EssenceTrackResource # Use a Valkyrie aware form service to generate Valkyrie::ChangeSet style # forms. self.work_form_service = Hyrax::FormFactory.new + self.show_presenter = EssenceTrackResourcePresenter end end diff --git a/app/controllers/hyrax/physical_instantiation_resources_controller.rb b/app/controllers/hyrax/physical_instantiation_resources_controller.rb index 041a9e00d..26c71d299 100644 --- a/app/controllers/hyrax/physical_instantiation_resources_controller.rb +++ b/app/controllers/hyrax/physical_instantiation_resources_controller.rb @@ -8,10 +8,15 @@ class PhysicalInstantiationResourcesController < ApplicationController # Adds Hyrax behaviors to the controller. include Hyrax::WorksControllerBehavior include Hyrax::BreadcrumbsForWorks + # Handle Child Work button and redirect to child work page + include Hyrax::ChildWorkRedirect + # Redirects away from controller#new if object does not have parent_id + include Hyrax::RedirectNewAction self.curation_concern_type = ::PhysicalInstantiationResource # Use a Valkyrie aware form service to generate Valkyrie::ChangeSet style # forms. self.work_form_service = Hyrax::FormFactory.new + self.show_presenter = PhysicalInstantiationResourcePresenter end end diff --git a/app/forms/asset_resource_form.rb b/app/forms/asset_resource_form.rb index bea0f0f98..51816b547 100644 --- a/app/forms/asset_resource_form.rb +++ b/app/forms/asset_resource_form.rb @@ -8,6 +8,219 @@ class AssetResourceForm < Hyrax::Forms::ResourceForm(AssetResource) include Hyrax::FormFields(:basic_metadata) include Hyrax::FormFields(:asset_resource) + include ChildCreateButton + include DisabledFields + + attr_accessor :controller, :current_ability + + class_attribute :field_groups + + self.hidden_fields += [ :hyrax_batch_ingest_batch_id, :last_pushed, :last_updated, :needs_update, :bulkrax_importer_id ] + + admin_data_attributes = (AdminData.attribute_names.dup - ['id', 'created_at', 'updated_at']).map &:to_sym + + self.field_groups = { + identifying_info: [:titles_with_types, :producing_organization, :local_identifier, :pbs_nola_code, :eidr_id, :asset_types, :dates_with_types, :descriptions_with_types], + subject_info: [:genre, :topics, :subject, :spatial_coverage, :temporal_coverage, :audience_level, :audience_rating, :annotation], + rights: [:rights_summary, :rights_link], + credits: [:child_contributors], + aapb_admin_data: admin_data_attributes, + annotations: [:child_annotations] + } + + def disabled?(field) + disabled_fields = self.disabled_fields.dup + disabled_fields += self.field_groups[:aapb_admin_data] if current_ability.cannot?(:create, AdminData) + disabled_fields.include?(field) + end + + def hidden?(field) + hidden_fields = self.hidden_fields.dup + hidden_fields.include?(field) + end + + def multiple?(field) + if [:child_contributors, :child_annotations, :special_collection, :sonyci_id, :special_collection_category].include?(field.to_sym) + true + else + super + end + end + + def primary_terms + [] + end + + def secondary_terms + [] + end + + def expand_field_group?(group) + #Get terms for a certian field group + return true if group == :credits && model.members.map{ |member| member.class }.include?(Contribution) + + #Get terms for a certian field group + return true if group == :aapb_admin_data && model.admin_data && !model.admin_data.empty? + + field_group_terms(group).each do |term| + #Expand field group + return true if !model.attributes[term.to_s].blank? || errors.has_key?(term) + end + false + end + + def field_group_terms(group) + group_terms = field_groups[group] + if group == :identifying_info + group_terms = field_groups[group] - [:titles_with_types, :descriptions_with_types] + group_terms += [:title, :program_title, :episode_title, :episode_number, :segment_title, :raw_footage_title, :promo_title, :clip_title] + group_terms += [:description, :episode_description, :segment_description, :raw_footage_description, :promo_description, :clip_description] + end + group_terms + end + + property :child_contributors, virtual: true + def child_contributors + child_contributions = [] + model.members.to_a.each do |member| + if( member.class == Contribution ) + child_contributions << [member.id, member.contributor_role, member.contributor.first , member.portrayal, member.affiliation] + end + end + child_contributions + end + + property :child_annotations, virtual: true + def child_annotations + child_annotations = [] + + if model.admin_data + model.admin_data.annotations.each do |annotation| + child_annotations << [annotation.id, annotation.admin_data_id, annotation.annotation_type, annotation.ref, annotation.source, annotation.value, annotation.annotation, annotation.version] + end + end + + child_annotations + end + + property :titles_with_types, virtual: true, required: true + def titles_with_types + titles_with_types = [] + title_type_service = TitleTypesService.new + title_types = title_type_service.all_ids + title_types.each do |title_type| + model_field = title_type_service.model_field(title_type) + raise "Unable to find model property" unless model.respond_to?(model_field) + titles_with_types += model.try(model_field).to_a.map { |title| [title_type, title] } + end + titles_with_types + end + + property :descriptions_with_types, virtual: true, required: true + def descriptions_with_types + descriptions_with_types = [] + description_type_service = DescriptionTypesService.new + types = description_type_service.all_ids + types.each do |description_type| + model_field = description_type_service.model_field(description_type) + raise "Unable to find model property" unless model.respond_to?(model_field) + descriptions_with_types += model.try(model_field).to_a.map { |value| [description_type, value] } + end + descriptions_with_types + end + + property :dates_with_types, virtual: true + def dates_with_types + dates_with_types = [] + date_type_service = DateTypesService.new + types = date_type_service.all_ids + types.each do |date_type| + model_field = date_type_service.model_field(date_type) + raise "Unable to find model property" unless model.respond_to?(model_field) + dates_with_types += model.try(model_field).to_a.map { |value| [date_type, value] } + end + dates_with_types + end + + property :sonyci_id, virtual: true + def sonyci_id + if model.admin_data + Array(model.admin_data.sonyci_id) + else + [] + end + end + + property :annotations, virtual: true + def annotations + if model.admin_data + Array(model.admin_data.annotations) + else + [] + end + end + + property :bulkrax_importer_id, virtual: true, display: false, multiple: false + def bulkrax_importer_id + if model.admin_data + model.admin_data.bulkrax_importer_id + else + "" + end + end + + property :hyrax_batch_ingest_batch_id, virtual: true, multiple: false + def hyrax_batch_ingest_batch_id + if model.admin_data + model.admin_data.hyrax_batch_ingest_batch_id + else + "" + end + end + + property :last_pushed, virtual: true, display: false, multiple: false + def last_pushed + if model.admin_data + model.admin_data.last_pushed + else + "" + end + end + property :last_updated, virtual: true, display: false, multiple: false + def last_updated + if model.admin_data + model.admin_data.last_updated + else + "" + end + end + property :needs_update, virtual: true, display: false, multiple: false + def needs_update + if model.admin_data + model.admin_data.needs_update + else + "" + end + end + + # Id can be written by importers, but not in the UI + property :id, display: false + + def permitted_params + @permitted ||= build_permitted_params + end + + def build_permitted_params + permitted = [] + (self.class.required_fields + field_groups.values.map(&:to_a).flatten).uniq.each do |term| + if multiple?(term) + permitted << { term => [] } + else + permitted << term + end + end + permitted + end # Define custom form fields using the Valkyrie::ChangeSet interface # diff --git a/app/forms/contribution_resource_form.rb b/app/forms/contribution_resource_form.rb index 3633b765d..85abeb9fd 100644 --- a/app/forms/contribution_resource_form.rb +++ b/app/forms/contribution_resource_form.rb @@ -8,6 +8,45 @@ class ContributionResourceForm < Hyrax::Forms::ResourceForm(ContributionResource) include Hyrax::FormFields(:basic_metadata) include Hyrax::FormFields(:contribution_resource) + include SingleValuedForm + include InheritParentTitle + + attr_accessor :controller, :current_ability + + self.single_valued_fields = [:title, :contributor] + + property :title, required: true, primary: true + + # remove fields from the form that are defined either from the + # core metadata or basic metadata + def self.remove(terms) + terms.each do |term| + property term, required: false, display: false + end + end + remove( + %i( + affiliation + based_near + bibliographic_citation + creator + date_created + description + identifier + import_url + keyword + label + language + license + publisher + related_url + relative_path + resource_type + rights_statement + source + subject + ) + ) # Define custom form fields using the Valkyrie::ChangeSet interface # diff --git a/app/forms/digital_instantiation_resource_form.rb b/app/forms/digital_instantiation_resource_form.rb index 633373f20..5faf46ec5 100644 --- a/app/forms/digital_instantiation_resource_form.rb +++ b/app/forms/digital_instantiation_resource_form.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true - + # Generated via # `rails generate hyrax:work_resource DigitalInstantiationResource` # @@ -8,6 +8,97 @@ class DigitalInstantiationResourceForm < Hyrax::Forms::ResourceForm(DigitalInstantiationResource) include Hyrax::FormFields(:basic_metadata) include Hyrax::FormFields(:digital_instantiation_resource) + include DisabledFields + include ChildCreateButton + include InheritParentTitle + + attr_accessor :controller, :current_ability + + self.required_fields -= [:creator, :keyword, :rights_statement] + self.required_fields += [:title, :location, :holding_organization] + + class_attribute :field_groups + + #removing id, created_at & updated_at from attributes + instantiation_admin_data_attributes = (InstantiationAdminData.attribute_names.dup - ['id', 'created_at', 'updated_at']).map &:to_sym + + self.field_groups = { + technical_info: [:local_instantiation_identifier, :media_type, :digital_format, :dimensions, :standard, :file_size, + :duration, :time_start, :data_rate, :colors, :tracks, :channel_configuration, :alternative_modes], + identifying_info: [:title, :holding_organization, :location, :generations, :language, :date, :annotation], + rights: [:rights_summary, :rights_link], + instantiation_admin_data: instantiation_admin_data_attributes + } + + self.fields += (self.required_fields + field_groups.values.map(&:to_a).flatten).uniq + + self.disabled_fields = self.fields - ( [:title, :location, :generations, :language, :date, :annotation, :rights_link, :rights_summary, :holding_organization] + instantiation_admin_data_attributes ) + self.readonly_fields = [:title] + + def primary_terms + [:digital_instantiation_pbcore_xml] + end + + def secondary_terms + [] + end + + def expand_field_group?(group) + #Get terms for a certian field group + field_group_terms(group).each do |term| + #Get terms for a certian field group + return true if group == :instantiation_admin_data && model.instantiation_admin_data && !model.instantiation_admin_data.empty? + #Expand field group + return true if !model.attributes[term.to_s].blank? || errors.has_key?(term) + end + false + end + + property :aapb_preservation_lto, virtual: true + def aapb_preservation_lto + if model.instantiation_admin_data + model.instantiation_admin_data.aapb_preservation_lto + else + "" + end + end + + property :aapb_preservation_disk, virtual: true + def aapb_preservation_disk + if model.instantiation_admin_data + model.instantiation_admin_data.aapb_preservation_disk + else + "" + end + end + + property :md5, virtual: true + def md5 + if model.instantiation_admin_data + model.instantiation_admin_data.md5 + else + "" + end + end + + def field_group_terms(group) + field_groups[group] + end + + def disabled?(field) + disabled_fields = self.disabled_fields.dup + disabled_fields += self.field_groups[:instantiation_admin_data] if current_ability.cannot?(:create, InstantiationAdminData) + disabled_fields.include?(field) + end + + def self.model_attributes(form_params) + clean_params = sanitize_params(form_params) + terms.each do |key| + clean_params[key].delete('') if clean_params[key] && multiple?(key) + end + clean_params[:title] = Array(clean_params[:title]) + clean_params + end # Define custom form fields using the Valkyrie::ChangeSet interface # @@ -17,4 +108,15 @@ class DigitalInstantiationResourceForm < Hyrax::Forms::ResourceForm(DigitalInsta # model attribute, make it virtual # # property :user_input_not_destined_for_the_model, virtual: true + property :parent_id, virtual: true + + def instantiation_admin_data + @instantiation_admin_data_gid ||= InstantiationAdminData.find_by_gid(instantiation_admin_data_gid) + end + + def instantiation_admin_data=(new_admin_data) + self[:instantiation_admin_data_gid] = new_admin_data.gid + end + + property :digital_instantiation_pbcore_xml, virtual: true end diff --git a/app/forms/disabled_fields.rb b/app/forms/disabled_fields.rb index 60eac1554..f9de7dd5b 100644 --- a/app/forms/disabled_fields.rb +++ b/app/forms/disabled_fields.rb @@ -5,7 +5,6 @@ module DisabledFields self.disabled_fields = [] self.readonly_fields = [] self.hidden_fields = [] - delegate :errors, to: :model base.extend FieldState end @@ -22,4 +21,3 @@ def hidden?(field) end end end - diff --git a/app/forms/essence_track_resource_form.rb b/app/forms/essence_track_resource_form.rb index f07fab40b..1062c0f37 100644 --- a/app/forms/essence_track_resource_form.rb +++ b/app/forms/essence_track_resource_form.rb @@ -8,6 +8,52 @@ class EssenceTrackResourceForm < Hyrax::Forms::ResourceForm(EssenceTrackResource) include Hyrax::FormFields(:basic_metadata) include Hyrax::FormFields(:essence_track_resource) + include DisabledFields + # TODO comment back in when we have a parent + include InheritParentTitle + + attr_accessor :controller, :current_ability + + self.readonly_fields = [:title] + + def self.model_attributes(form_params) + clean_params = sanitize_params(form_params) + clean_params[:title] = Array(clean_params[:title]) + clean_params + end + + property :title, required: true, primary: true, multiple: false + + # remove fields from the form that are defined either from the + # core metadata or basic metadata + def self.remove(terms) + terms.each do |term| + property term, required: false, display: false + end + end + remove( + %i( + based_near + bibliographic_citation + contributor + creator + date_created + description + identifier + import_url + keyword + label + language + license + publisher + related_url + relative_path + resource_type + rights_statement + source + subject + ) + ) # Define custom form fields using the Valkyrie::ChangeSet interface # diff --git a/app/forms/inherit_parent_title.rb b/app/forms/inherit_parent_title.rb index f9835628e..d0092ad5d 100644 --- a/app/forms/inherit_parent_title.rb +++ b/app/forms/inherit_parent_title.rb @@ -3,8 +3,24 @@ module InheritParentTitle included do def title #Get parent title from solr document where title logic is defined + solr_document = case model + when ActiveFedora::Base + ::SolrDocument.new(find_parent_object_hash) unless find_parent_object_hash.nil? + when Valkyrie::Resource + # TODO: Bulkrax imports don't seem to have a controller so we're guarding for now + return nil unless @controller.present? + action = @controller.params[:action] + if action == "new" || action == "create" + # find parent title + return nil unless (@controller.params[:parent_id] ||find_parent_object_hash) + + ::SolrDocument.find(@controller.params[:parent_id]) || (::SolrDocument.new(find_parent_object_hash) unless find_parent_object_hash.nil?) + else + # find object title + ::SolrDocument.find(@controller.params[:id]) + end + end - solr_document = ::SolrDocument.new(find_parent_object_hash) unless find_parent_object_hash.nil? if(solr_document.title.any?) return [solr_document.title] [] @@ -13,8 +29,14 @@ def title def find_parent_object_hash if @controller.params.has_key?(:parent_id) - return ActiveFedora::Base.search_by_id(@controller.params[:parent_id]) - elsif model.in_objects.any? + case model + when ActiveFedora::Base + return ActiveFedora::Base.search_by_id(@controller.params[:parent_id]) + when Valkyrie::Resource + parent_resource = Hyrax.query_service.find_by(id: @controller.params[:parent_id]) + return Hyrax::ValkyrieIndexer.for(resource: parent_resource).to_solr + end + elsif model.try(:in_objects)&.any? return model.in_objects.first.to_solr else return nil diff --git a/app/forms/physical_instantiation_resource_form.rb b/app/forms/physical_instantiation_resource_form.rb index 5e01256ad..2383290c9 100644 --- a/app/forms/physical_instantiation_resource_form.rb +++ b/app/forms/physical_instantiation_resource_form.rb @@ -1,13 +1,101 @@ # frozen_string_literal: true - + # Generated via # `rails generate hyrax:work_resource PhysicalInstantiationResource` # # @see https://github.com/samvera/hyrax/wiki/Hyrax-Valkyrie-Usage-Guide#forms # @see https://github.com/samvera/valkyrie/wiki/ChangeSets-and-Dirty-Tracking class PhysicalInstantiationResourceForm < Hyrax::Forms::ResourceForm(PhysicalInstantiationResource) - include Hyrax::FormFields(:basic_metadata) + # include Hyrax::FormFields(:basic_metadata) include Hyrax::FormFields(:physical_instantiation_resource) + include DisabledFields + include ChildCreateButton + include SingleValuedForm + # TODO comment back in when we have a parent + include InheritParentTitle + + attr_accessor :controller, :current_ability + + self.required_fields -= [:creator, :keyword, :rights_statement] + self.required_fields += [:format, :location, :media_type, :holding_organization] + + self.single_valued_fields = [:title] + + #removing id, created_at & updated_at from attributes + instantiation_admin_data_attributes = (InstantiationAdminData.attribute_names.dup - ['id', 'created_at', 'updated_at']).map &:to_sym + + class_attribute :field_groups + + self.field_groups = { + identifying_info: [:title, :holding_organization, :local_instantiation_identifier, :media_type, :format, :location, :generations, :date, :digitization_date, + :language, :annotation], + technical_info: [:dimensions, :standard, :duration, :time_start, :colors, :tracks, :channel_configuration, + :alternative_modes], + rights: [:rights_summary, :rights_link], + instantiation_admin_data: instantiation_admin_data_attributes + } + + self.fields += (self.required_fields + field_groups.values.map(&:to_a).flatten).uniq + + self.readonly_fields = [:title] + #self.disabled_fields = instantiation_admin_data_attributes + + def primary_terms + [] + end + + def secondary_terms + [] + end + + def expand_field_group?(group) + #Get terms for a certian field group + field_group_terms(group).each do |term| + #Get terms for a certian field group + return true if group == :instantiation_admin_data && model.instantiation_admin_data && !model.instantiation_admin_data.empty? + #Expand field group + return true if !model.attributes[term.to_s].blank? || errors.has_key?(term) + end + false + end + + property :aapb_preservation_lto, virtual: true + def aapb_preservation_lto + if model.instantiation_admin_data + model.instantiation_admin_data.aapb_preservation_lto + else + "" + end + end + + def disabled?(field) + disabled_fields = self.disabled_fields.dup + # TODO: current_ability isn't a thing right now so I'm commenting this out for now + # disabled_fields += self.field_groups[:instantiation_admin_data] if current_ability.cannot?(:create, InstantiationAdminData) + disabled_fields.include?(field) + end + + property :aapb_preservation_disk, virtual: true + def aapb_preservation_disk + if model.instantiation_admin_data + model.instantiation_admin_data.aapb_preservation_disk + else + "" + end + end + + property :md5, virtual: true + def md5 + if model.instantiation_admin_data + model.instantiation_admin_data.md5 + else + "" + end + end + + def field_group_terms(group) + field_groups[group] + end # Define custom form fields using the Valkyrie::ChangeSet interface # @@ -17,4 +105,12 @@ class PhysicalInstantiationResourceForm < Hyrax::Forms::ResourceForm(PhysicalIns # model attribute, make it virtual # # property :user_input_not_destined_for_the_model, virtual: true + + def instantiation_admin_data + @instantiation_admin_data_gid ||= InstantiationAdminData.find_by_gid(instantiation_admin_data_gid) + end + + def instantiation_admin_data=(new_admin_data) + self[:instantiation_admin_data_gid] = new_admin_data.gid + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index aab7e8ad5..e6877753f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -17,7 +17,7 @@ def self.display_date(date_time, format: '%Y-%m-%d', from_format: nil, time_zone end # Instance method delegates to ApplicationHelper.display_date. - def display_date(*args); ApplicationHelper.display_date(*args); end + def display_date(*args, **kwargs); ApplicationHelper.display_date(*args, **kwargs); end def render_thumbnail(document, options) # send(blacklight_config.view_config(document_index_view_type).thumbnail_method, document, image_options) diff --git a/app/indexers/asset_resource_indexer.rb b/app/indexers/asset_resource_indexer.rb index 247a14257..9e6202d02 100644 --- a/app/indexers/asset_resource_indexer.rb +++ b/app/indexers/asset_resource_indexer.rb @@ -6,11 +6,38 @@ class AssetResourceIndexer < Hyrax::ValkyrieWorkIndexer include Hyrax::Indexer(:basic_metadata) include Hyrax::Indexer(:asset_resource) + self.thumbnail_path_service = AAPB::AssetThumbnailPathService + # Uncomment this block if you want to add custom indexing behavior: - # def to_solr - # super.tap do |index_document| - # index_document[:my_field_tesim] = resource.my_field.map(&:to_s) - # index_document[:other_field_ssim] = resource.other_field - # end - # end + def to_solr + super.tap do |index_document| + index_document['date_drsim'] = resource.date if resource.date + index_document['broadcast_date_drsim'] = resource.broadcast_date if resource.broadcast_date + index_document['created_date_drsim'] = resource.created_date if resource.created_date + index_document['copyright_date_drsim'] = resource.copyright_date if resource.copyright_date + index_document['bulkrax_identifier_sim'] = resource.bulkrax_identifier + index_document['intended_children_count_isi'] = resource.intended_children_count.to_i + + if resource.admin_data + # Index the admin_data_gid + index_document['admin_data_tesim'] = resource.admin_data.gid if !resource.admin_data.gid.blank? + index_document['sonyci_id_ssim'] = resource.admin_data.sonyci_id if !resource.admin_data.sonyci_id.blank? + + # Programmatically assign annotations by type from controlled vocab + AnnotationTypesService.new.select_all_options.each do |type| + # Use the ID defined in the AnnotationType service + type_id = type[1] + index_document["#{type_id.underscore}_ssim"] = resource.try(type_id.to_sym) unless resource.try(type_id.to_sym).empty? + end + + #Indexing for search by batch_id + index_document['hyrax_batch_ingest_batch_id_tesim'] = resource.admin_data.hyrax_batch_ingest_batch_id if !resource.admin_data.hyrax_batch_ingest_batch_id.blank? + index_document['bulkrax_importer_id_tesim'] = resource.admin_data.bulkrax_importer_id if !resource.admin_data.bulkrax_importer_id.blank? + + index_document['last_pushed'] = resource.admin_data.last_pushed if !resource.admin_data.last_pushed.blank? + index_document['last_updated'] = resource.admin_data.last_updated if !resource.admin_data.last_updated.blank? + index_document['needs_update'] = resource.admin_data.needs_update if !resource.admin_data.needs_update.blank? + end + end + end end diff --git a/app/indexers/digital_instantiation_resource_indexer.rb b/app/indexers/digital_instantiation_resource_indexer.rb index 1fdaeb7b8..ee541e931 100644 --- a/app/indexers/digital_instantiation_resource_indexer.rb +++ b/app/indexers/digital_instantiation_resource_indexer.rb @@ -6,11 +6,20 @@ class DigitalInstantiationResourceIndexer < Hyrax::ValkyrieWorkIndexer include Hyrax::Indexer(:basic_metadata) include Hyrax::Indexer(:digital_instantiation_resource) + self.thumbnail_path_service = AAPB::WorkThumbnailPathService + # Uncomment this block if you want to add custom indexing behavior: - # def to_solr - # super.tap do |index_document| - # index_document[:my_field_tesim] = resource.my_field.map(&:to_s) - # index_document[:other_field_ssim] = resource.other_field - # end - # end + def to_solr + super.tap do |index_document| + index_document['bulkrax_identifier_sim'] = resource.bulkrax_identifier + if resource.instantiation_admin_data + #Indexing as english text so we can use it on asset show page + index_document['instantiation_admin_data_tesim'] = resource.instantiation_admin_data.gid if resource.instantiation_admin_data.gid.present? + index_document['aapb_preservation_lto_ssim'] = index_document['aapb_preservation_lto_tesim'] = resource.instantiation_admin_data.aapb_preservation_lto if resource.instantiation_admin_data.aapb_preservation_lto.present? + index_document['aapb_preservation_disk_ssim'] = index_document['aapb_preservation_disk_tesim'] = resource.instantiation_admin_data.aapb_preservation_disk if resource.instantiation_admin_data.aapb_preservation_disk.present? + index_document['md5_ssim'] = resource.instantiation_admin_data.md5 if resource.instantiation_admin_data.md5.present? + end + index_document + end + end end diff --git a/app/indexers/essence_track_resource_indexer.rb b/app/indexers/essence_track_resource_indexer.rb index c1d3bcf43..3666a6464 100644 --- a/app/indexers/essence_track_resource_indexer.rb +++ b/app/indexers/essence_track_resource_indexer.rb @@ -6,11 +6,12 @@ class EssenceTrackResourceIndexer < Hyrax::ValkyrieWorkIndexer include Hyrax::Indexer(:basic_metadata) include Hyrax::Indexer(:essence_track_resource) + self.thumbnail_path_service = AAPB::WorkThumbnailPathService + # Uncomment this block if you want to add custom indexing behavior: - # def to_solr - # super.tap do |index_document| - # index_document[:my_field_tesim] = resource.my_field.map(&:to_s) - # index_document[:other_field_ssim] = resource.other_field - # end - # end + def to_solr + super.tap do |index_document| + index_document['bulkrax_identifier_sim'] = resource.bulkrax_identifier + end + end end diff --git a/app/indexers/physical_instantiation_resource_indexer.rb b/app/indexers/physical_instantiation_resource_indexer.rb index ce029e9fc..194a3f5e6 100644 --- a/app/indexers/physical_instantiation_resource_indexer.rb +++ b/app/indexers/physical_instantiation_resource_indexer.rb @@ -6,11 +6,18 @@ class PhysicalInstantiationResourceIndexer < Hyrax::ValkyrieWorkIndexer include Hyrax::Indexer(:basic_metadata) include Hyrax::Indexer(:physical_instantiation_resource) + self.thumbnail_path_service = AAPB::WorkThumbnailPathService + # Uncomment this block if you want to add custom indexing behavior: - # def to_solr - # super.tap do |index_document| - # index_document[:my_field_tesim] = resource.my_field.map(&:to_s) - # index_document[:other_field_ssim] = resource.other_field - # end - # end + def to_solr + super.tap do |index_document| + index_document['bulkrax_identifier_sim'] = resource.bulkrax_identifier + if resource.instantiation_admin_data + #Indexing as english text so we can use it on asset show page + index_document['instantiation_admin_data_tesim'] = resource.instantiation_admin_data.gid if !resource.instantiation_admin_data.gid.blank? + index_document['aapb_preservation_lto_ssim'] = index_document['aapb_preservation_lto_tesim'] = resource.instantiation_admin_data.aapb_preservation_lto if !resource.instantiation_admin_data.aapb_preservation_lto.blank? + index_document['aapb_preservation_disk_ssim'] = index_document['aapb_preservation_disk_tesim'] = resource.instantiation_admin_data.aapb_preservation_disk if !resource.instantiation_admin_data.aapb_preservation_disk.blank? + end + end + end end diff --git a/app/input/multiple_date_input.rb b/app/input/multiple_date_input.rb index c2ecb162b..839b52cec 100644 --- a/app/input/multiple_date_input.rb +++ b/app/input/multiple_date_input.rb @@ -1,7 +1,7 @@ class MultipleDateInput < MultiValueInput def build_field(value, index) options = build_field_options(value, index) - options[:pattern] = AMS::NonExactDateService.regex.to_s + options[:pattern] = AMS::NonExactDateService.regex_string options[:class] += ["datepicker","multi_value","multi-text-field"] @builder.text_field(attribute_name, options) diff --git a/app/input/multiple_dates_with_types_input.rb b/app/input/multiple_dates_with_types_input.rb index 72fd32546..7ccfd81e6 100644 --- a/app/input/multiple_dates_with_types_input.rb +++ b/app/input/multiple_dates_with_types_input.rb @@ -1,7 +1,7 @@ class MultipleDatesWithTypesInput < AMS::MultiTypedInput def text_input_html_options(value, index) - super.merge( { pattern: AMS::NonExactDateService.regex.to_s } ) + super.merge( { pattern: AMS::NonExactDateService.regex_string } ) end def type_choices diff --git a/app/input/sony_ci_id_input.rb b/app/input/sony_ci_id_input.rb index 0095e1e27..cdb4b5d24 100644 --- a/app/input/sony_ci_id_input.rb +++ b/app/input/sony_ci_id_input.rb @@ -10,7 +10,11 @@ def sony_ci_filename_html(sony_ci_id) # we need to guard against that. filename = if object.model.admin_data.present? object.model.admin_data.sonyci_records&.fetch(sony_ci_id, nil)&.fetch('name', nil) + end + if filename.present? + "
Filename: #{filename}
" + else + '' end - "Filename: #{filename}
" end end diff --git a/app/jobs/bulkrax/child_relationships_job.rb b/app/jobs/bulkrax/child_relationships_job.rb index d4be2d6b1..3ac3e376e 100644 --- a/app/jobs/bulkrax/child_relationships_job.rb +++ b/app/jobs/bulkrax/child_relationships_job.rb @@ -5,6 +5,7 @@ # - overrides method work_membership module Bulkrax class ChildWorksError < RuntimeError; end + class ParentWorkError < RuntimeError; end class ChildRelationshipsJob < ApplicationJob queue_as :import @@ -16,12 +17,11 @@ def perform(*args) work_membership end - rescue Bulkrax::ChildWorksError + rescue Bulkrax::ChildWorksError, Bulkrax::ParentWorkError # Not all of the Works/Collections exist yet; reschedule # In case the work hasn't been created, don't endlessly reschedule the job attempts = (args[3] || 0) + 1 child_ids = @missing_entry_ids.presence || args[1] - reschedule(args[0], child_ids, args[2], attempts) unless attempts > 5 end @@ -38,15 +38,18 @@ def collection_membership end def work_membership + raise ParentWorkError unless parent_record seen_count = 0 child_entries.each do |child_entry| child_record = child_entry.factory.find next if child_record.blank? or child_record.is_a?(Collection) - next if parent_record.ordered_members&.to_a&.include?(child_record) - parent_record.ordered_members << child_record + next if parent_record.blank? + parent_record.member_ids << child_record.id unless parent_record.member_ids&.to_a&.include?(child_record.id) seen_count += 1 end - parent_record.save! + Hyrax.persister.save(resource: parent_record) + user ||= ::User.find_by_user_key(parent_record.depositor) + Hyrax.publisher.publish('object.metadata.updated', object: parent_record, user: user) raise ChildWorksError if seen_count < child_entries.count end @@ -112,25 +115,6 @@ def collection_parent_collection_child(member_ids) entry.status_info(e) ImporterRun.find(importer_run_id).increment!(:failed_relationships) end - - # Work-Work membership is added to the parent as member_ids - def work_parent_work_child(member_ids) - # build work_members_attributes - attrs = { id: entry&.factory&.find&.id, - work_members_attributes: member_ids.each.with_index.each_with_object({}) do |(member, index), ids| - ids[index] = { id: member } - end } - Bulkrax::ObjectFactory.new(attributes: attrs, - source_identifier_value: entry.identifier, - work_identifier: entry.parser.work_identifier, - replace_files: false, - user: user, - klass: entry.factory_class).run - ImporterRun.find(importer_run_id).increment!(:processed_relationships) - rescue StandardError => e - entry.status_info(e) - ImporterRun.find(importer_run_id).increment!(:failed_relationships) - end # rubocop:enable Rails/SkipsModelValidations def reschedule(entry_id, child_entry_ids, importer_run_id, attempts) diff --git a/app/jobs/bulkrax/index_after_job.rb b/app/jobs/bulkrax/index_after_job.rb index 2055df3c2..b5588497d 100644 --- a/app/jobs/bulkrax/index_after_job.rb +++ b/app/jobs/bulkrax/index_after_job.rb @@ -10,14 +10,14 @@ def perform(importer) return reschedule(importer.id) unless pending_num.zero? # read queue and index objects - set = Redis.current.zpopmax("nested:index:#{importer.id}", 100) + set = Hyrax.config.redis_connection.zpopmax("nested:index:#{importer.id}", 100) logger.debug(set.to_s) return if set.blank? loop do set.each do |key, score| Hyrax.config.nested_relationship_reindexer.call(id: key, extent: 'full') end - set = Redis.current.zpopmax("nested:index:#{importer.id}", 100) + set = Hyrax.config.redis_connection.zpopmax("nested:index:#{importer.id}", 100) logger.debug(set.to_s) break if set.blank? end diff --git a/app/matchers/concerns/has_ams_matchers.rb b/app/matchers/concerns/has_ams_matchers.rb new file mode 100644 index 000000000..a00ff6d04 --- /dev/null +++ b/app/matchers/concerns/has_ams_matchers.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true +# rubocop:disable Metrics/ModuleLength +module HasAmsMatchers + extend ActiveSupport::Concern + ## + # Field of the model that can be supported + def field_supported?(field) + field = field.gsub("_attributes", "") + + return false if excluded?(field) + return true if supported_bulkrax_fields.include?(field) + # title is not defined in M3 + return true if field == "title" + return true if field == "description" + return true if field == "subject" + return true if field == "contributors" + + property_defined = factory_class.singleton_methods.include?(:properties) && factory_class.properties[field].present? + + factory_class.method_defined?(field) && (Bulkrax::ValkyrieObjectFactory.schema_properties(factory_class).include?(field) || property_defined) + end + + ## + # Determine a multiple properties field + def multiple?(field) + @multiple_bulkrax_fields ||= + %W[ + file + remote_files + rights_statement + #{related_parents_parsed_mapping} + #{related_children_parsed_mapping} + ] + + return true if @multiple_bulkrax_fields.include?(field) + return false if field == "model" + # title is not defined in M3 + return true if field == "title" + return true if field == "description" + return true if field == "subject" + + field_supported?(field) && (multiple_field?(field) || factory_class.singleton_methods.include?(:properties) && factory_class&.properties&.[](field)&.[]("multiple")) + end + + def multiple_field?(field) + form_definition = schema_form_definitions[field.to_sym] + form_definition.nil? ? false : form_definition[:multiple] + end + + def add_metadata(node_name, node_content, index = nil) + field_to(node_name).each do |name| + matcher = self.class.matcher(name, mapping[name].symbolize_keys) if mapping[name] # the field matched to a pre parsed value in application_matcher.rb + object_name = get_object_name(name) || false # the "key" of an object property. e.g. { object_name: { alpha: 'beta' } } + multiple = multiple?(name) # the property has multiple values. e.g. 'letters': ['a', 'b', 'c'] + object_multiple = object_name && multiple?(object_name) # the property's value is an array of object(s) + + next unless field_supported?(name) || (object_name && field_supported?(object_name)) + + if object_name + Rails.logger.info("Bulkrax Column automatically matched object #{node_name}, #{node_content}") + parsed_metadata[object_name] ||= object_multiple ? [{}] : {} + end + + value = if matcher + result = matcher.result(self, node_content) + matched_metadata(multiple, name, result, object_multiple) + elsif multiple + Rails.logger.info("Bulkrax Column automatically matched #{node_name}, #{node_content}") + # OVERRIDE BULKRAX 1.0.2 to avoid ActiveTriples::Relation::ValueError + multiple_metadata(node_content, node_name) + else + Rails.logger.info("Bulkrax Column automatically matched #{node_name}, #{node_content}") + single_metadata(node_content) + end + + object_name.present? ? set_parsed_object_data(object_multiple, object_name, name, index, value) : set_parsed_data(name, value) + end + end + + def multiple_metadata(content, name = nil) + return unless content + + case content + when Nokogiri::XML::NodeSet + content&.content + when Array + # OVERRIDE BULKRAX 1.0.2 to avoid ActiveTriples::Relation::ValueError + if name == 'head' || name == 'tail' + content.map do |obj| + obj.delete("id") + end + else + content + end + when Hash + Array.wrap(content) + when String + Array.wrap(content.strip) + else + Array.wrap(content) + end + end + + # override: we want to directly infer from a property being multiple that we should split when it's a String + # def multiple_metadata(content) + # return unless content + + # case content + # when Nokogiri::XML::NodeSet + # content&.content + # when Array + # content + # when Hash + # Array.wrap(content) + # when String + # String(content).strip.split(Bulkrax.multi_value_element_split_on) + # else + # Array.wrap(content) + # end + # end + + def schema_form_definitions + @schema_form_definitions ||= Hyrax::SimpleSchemaLoader.new.form_definitions_for(schema: factory_class.name.underscore.to_sym) + end +end diff --git a/app/models/admin_data.rb b/app/models/admin_data.rb index d72d88142..37cd3a07e 100644 --- a/app/models/admin_data.rb +++ b/app/models/admin_data.rb @@ -4,6 +4,7 @@ class AdminData < ApplicationRecord belongs_to :hyrax_batch_ingest_batch, optional: true belongs_to :bulkrax_importer, optional: true, class_name: 'Bulkrax::Importer' has_many :annotations, dependent: :destroy + accepts_nested_attributes_for :annotations, allow_destroy: true self.table_name = "admin_data" include ::EmptyDetection diff --git a/app/models/ams/create_member_methods.rb b/app/models/ams/create_member_methods.rb index 5b2d8db40..4ea161b3b 100644 --- a/app/models/ams/create_member_methods.rb +++ b/app/models/ams/create_member_methods.rb @@ -1,20 +1,31 @@ module AMS module CreateMemberMethods + extend ActiveSupport::Concern - def self.included(klass) - klass.class_eval do - after_initialize :create_child_methods + included do + def create_child_methods + self.valid_child_concerns.each do |child_class| + method_name = child_class.to_s.underscore.pluralize + self.class.send(:define_method, method_name) do + case self + when ActiveFedora::Base + condition = { has_model_ssim: child_class.to_s, id: self.members.map(&:id) } + ActiveFedora::Base.search_with_conditions(condition).collect { |v| SolrDocument.new(v) } + when Valkyrie::Resource + child_works = Hyrax.custom_queries.find_child_works(resource: self).to_a + child_works_of_type = child_works.select { |work| work.is_a?(child_class) } + child_works_of_type.map { |work| resource_to_solr(work) } + end + end + end end - end - def create_child_methods - self.valid_child_concerns.each do |child_class| - method_name = child_class.to_s.underscore.pluralize - self.class.send(:define_method,method_name) do - condition= {has_model_ssim:child_class.to_s, id:self.members.map(&:id)} - ActiveFedora::Base.search_with_conditions(condition).collect { |v| SolrDocument.new(v) } - end + def resource_to_solr(resource) + indexable_hash = Hyrax::ValkyrieIndexer.for(resource: resource).to_solr + ::SolrDocument.new(indexable_hash) end + + after_initialize :create_child_methods if self.ancestors.include?(ActiveFedora::Base) end end -end \ No newline at end of file +end diff --git a/app/models/ams/pbcore_xml_export_extension.rb b/app/models/ams/pbcore_xml_export_extension.rb index 9c33d08c3..2bff1b6cc 100644 --- a/app/models/ams/pbcore_xml_export_extension.rb +++ b/app/models/ams/pbcore_xml_export_extension.rb @@ -328,7 +328,7 @@ def prepare_essence_track(instantiation_node, essence_track) end def prepare_annotations(xml) - return if annotations.empty? + return if annotations.blank? annotations.each do |annotation| xml.pbcoreAnnotation(annotationType: AnnotationTypesService.new.label(annotation.annotation_type), ref: annotation.ref, source: annotation.source, annotation: annotation.annotation, version: annotation.version) { xml.text(annotation.value) } diff --git a/app/models/asset_resource.rb b/app/models/asset_resource.rb index eae843a9f..535fd362b 100644 --- a/app/models/asset_resource.rb +++ b/app/models/asset_resource.rb @@ -1,8 +1,161 @@ # frozen_string_literal: true - + # Generated via # `rails generate hyrax:work_resource AssetResource` class AssetResource < Hyrax::Work include Hyrax::Schema(:basic_metadata) include Hyrax::Schema(:asset_resource) + include Hyrax::ArResource + include AMS::WorkBehavior + include AMS::CreateMemberMethods + + self.valid_child_concerns = [DigitalInstantiationResource, PhysicalInstantiationResource, ContributionResource] + + VALIDATION_STATUSES = { + valid: 'valid', + missing_children: 'missing child record(s)', + status_not_validated: 'not yet validated', + empty: 'missing a validation status' + }.freeze + + # after_initialize does not work with Valkyrie apparently + # so to get the #create_child_methods method to run + # we have to call it like this + def initialize(*args) + create_child_methods + super + end + + def admin_data + return @admin_data if @admin_data + + if admin_data_gid.present? + @admin_data = AdminData.find_by_gid(admin_data_gid) + end + + @admin_data ||= nil + end + + def admin_data=(new_admin_data) + self.admin_data_gid = new_admin_data.gid + @admin_data = new_admin_data + end + + def annotations + @annotations ||= admin_data.annotations + end + + def sonyci_id + sonyci_id ||= find_admin_data_attribute("sonyci_id") + end + + def supplemental_material + supplemental_material ||= find_annotation_attribute("supplemental_material") + end + + def organization + organization ||= find_annotation_attribute("organization") + end + + def level_of_user_access + level_of_user_access ||= find_annotation_attribute("level_of_user_access") + end + + def transcript_status + transcript_status ||= find_annotation_attribute("transcript_status") + end + + def transcript_url + transcript_url ||= find_annotation_attribute("transcript_url") + end + + def transcript_source + transcript_source ||= find_annotation_attribute("transcript_source") + end + + def cataloging_status + cataloging_status ||= find_annotation_attribute("cataloging_status") + end + + def captions_url + captions_url ||= find_annotation_attribute("captions_url") + end + + def last_modified + licensing_info ||= find_annotation_attribute("last_modified") + end + + def licensing_info + licensing_info ||= find_annotation_attribute("licensing_info") + end + + def outside_url + outside_url ||= find_annotation_attribute("outside_url") + end + + def external_reference_url + external_reference_url ||= find_annotation_attribute("external_reference_url") + end + + def mavis_number + mavis_number ||= find_annotation_attribute("mavis_number") + end + + def playlist_group + playlist_group ||= find_annotation_attribute("playlist_group") + end + + def playlist_order + playlist_order ||= find_annotation_attribute("playlist_order") + end + + def special_collections + special_collection ||= find_annotation_attribute("special_collections") + end + + def special_collection_category + special_collection_category ||= find_annotation_attribute("special_collection_category") + end + + def project_code + project_code ||= find_annotation_attribute("project_code") + end + + def canonical_meta_tag + canonical_meta_tag ||= find_annotation_attribute("canonical_meta_tag") + end + + def proxy_start_time + proxy_start_time ||= find_annotation_attribute("proxy_start_time") + end + + def find_annotation_attribute(attribute) + if admin_data.annotations.select { |a| a.annotation_type == attribute }.present? + return admin_data.annotations.select { |a| a.annotation_type == attribute }.map(&:value) + else + [] + end + end + + def find_admin_data_attribute(attribute) + if admin_data.try(attribute.to_sym).present? + return [ admin_data.try(attribute.to_sym) ] unless admin_data.try(attribute.to_sym).is_a?(Array) + return admin_data.try(attribute.to_sym) + else + [] + end + end + + def set_validation_status + current_children_count = SolrDocument.get_members(self).reject { |child| child.is_a?(Contribution) || child.id == self.id }.size + intended_children_count = self.intended_children_count.to_i + + self.validation_status_for_aapb = if intended_children_count.blank? && self.validation_status_for_aapb.blank? + [Asset::VALIDATION_STATUSES[:status_not_validated]] + elsif current_children_count < intended_children_count + [Asset::VALIDATION_STATUSES[:missing_children]] + else + [Asset::VALIDATION_STATUSES[:valid]] + end + end end diff --git a/app/models/bulkrax/csv_entry.rb b/app/models/bulkrax/csv_entry.rb index cd3ddf7d1..c0527cef0 100644 --- a/app/models/bulkrax/csv_entry.rb +++ b/app/models/bulkrax/csv_entry.rb @@ -4,6 +4,8 @@ require_dependency Bulkrax::Engine.root.join('app', 'models', 'bulkrax', 'csv_entry') Bulkrax::CsvEntry.class_eval do + include HasAmsMatchers + def self.read_data(path) raise StandardError, 'CSV path empty' if path.blank? diff --git a/app/models/bulkrax/pbcore_xml_entry.rb b/app/models/bulkrax/pbcore_xml_entry.rb index c7caf2649..4dc00d047 100644 --- a/app/models/bulkrax/pbcore_xml_entry.rb +++ b/app/models/bulkrax/pbcore_xml_entry.rb @@ -4,6 +4,7 @@ module Bulkrax class PbcoreXmlEntry < XmlEntry + include HasAmsMatchers def self.read_data(path) if MIME::Types.type_for(path).include?('text/csv') CSV.read(path, @@ -39,7 +40,7 @@ def build_metadata self.parsed_metadata = {} self.parsed_metadata[work_identifier] = self.raw_metadata[source_identifier] self.parsed_metadata['model'] = self.raw_metadata['model'] - if self.raw_metadata['model'] == 'DigitalInstantiation' + if self.raw_metadata['model'] == 'DigitalInstantiationResource' self.parsed_metadata['pbcore_xml'] = self.raw_metadata['pbcore_xml'] if self.raw_metadata['pbcore_xml'].present? self.parsed_metadata['format'] = self.raw_metadata['format'] self.parsed_metadata['skip_file_upload_validation'] = self.raw_metadata['skip_file_upload_validation'] if self.raw_metadata['skip_file_upload_validation'] == true @@ -49,7 +50,7 @@ def build_metadata add_metadata(key_without_numbers(key), value) end - if self.raw_metadata['model'] == 'Asset' + if self.raw_metadata['model'] == 'AssetResource' bulkrax_importer_id = importer.id admin_data_gid = update_or_create_admin_data_gid(bulkrax_importer_id) @@ -69,10 +70,9 @@ def build_metadata end def update_or_create_admin_data_gid(bulkrax_importer_id) - manifest_asset_id = self.raw_metadata['Asset.id'].strip if self.raw_metadata.keys.include?('Asset.id') - xml_asset_id = self.raw_metadata['id'] - work = Asset.where(id: manifest_asset_id || xml_asset_id).first if manifest_asset_id || xml_asset_id - + asset_resource_id = self.raw_metadata['Asset.id'].strip if self.raw_metadata.keys.include?('Asset.id') + asset_resource_id ||= self.raw_metadata['id'] + work = Hyrax.query_service.find_by(id: asset_resource_id) if asset_resource_id admin_data_gid = if work.present? && work.admin_data.present? work.admin_data.update!(bulkrax_importer_id: bulkrax_importer_id) work.admin_data_gid @@ -81,6 +81,8 @@ def update_or_create_admin_data_gid(bulkrax_importer_id) end admin_data_gid + rescue Valkyrie::Persistence::ObjectNotFoundError + AdminData.create(bulkrax_importer_id: bulkrax_importer_id).gid end def build_annotations(annotations, admin_data_gid) @@ -89,14 +91,13 @@ def build_annotations(annotations, admin_data_gid) raise "annotation_type not registered with the AnnotationTypesService: #{annotation['annotation_type']}." end - admin_data = AdminData.find_by_gid(admin_data_gid) Annotation.find_or_create_by( annotation_type: annotation['annotation_type'], source: annotation['source'], value: annotation['value'], annotation: annotation['annotation'], version: annotation['version'], - admin_data_id: admin_data.id + admin_data_id: admin_data_gid.split('/').last ) end end diff --git a/app/models/concerns/ams/work_behavior.rb b/app/models/concerns/ams/work_behavior.rb new file mode 100644 index 000000000..17781ff2e --- /dev/null +++ b/app/models/concerns/ams/work_behavior.rb @@ -0,0 +1,27 @@ +module AMS + module WorkBehavior + extend ActiveSupport::Concern + + included do + attribute :internal_resource, Valkyrie::Types::Any.default(self.name.gsub(/Resource$/,'').freeze), internal: true + cattr_accessor :valid_child_concerns + end + + class_methods do + def _hyrax_default_name_class + Hyrax::Name + end + end + + def members + return @members if @members.present? + @members = member_ids.map do |id| + Hyrax.query_service.find_by(id: id) + end + end + + def to_solr + Hyrax::ValkyrieIndexer.for(resource: self).to_solr + end + end +end diff --git a/app/models/concerns/bulkrax/has_matchers.rb b/app/models/concerns/bulkrax/has_matchers.rb deleted file mode 100644 index 857dd5211..000000000 --- a/app/models/concerns/bulkrax/has_matchers.rb +++ /dev/null @@ -1,58 +0,0 @@ -# OVERRIDE BULKRAX 1.0.2 to avoid ActiveTriples::Relation::ValueError -require_dependency Bulkrax::Engine.root.join('app', 'models', 'concerns', 'bulkrax', 'has_matchers') - -Bulkrax::HasMatchers.class_eval do # rubocop:disable Metrics/ParameterLists - def add_metadata(node_name, node_content, index = nil) - field_to(node_name).each do |name| - matcher = self.class.matcher(name, mapping[name].symbolize_keys) if mapping[name] # the field matched to a pre parsed value in application_matcher.rb - object_name = get_object_name(name) || false # the "key" of an object property. e.g. { object_name: { alpha: 'beta' } } - multiple = multiple?(name) # the property has multiple values. e.g. 'letters': ['a', 'b', 'c'] - object_multiple = object_name && multiple?(object_name) # the property's value is an array of object(s) - - next unless field_supported?(name) || (object_name && field_supported?(object_name)) - - if object_name - Rails.logger.info("Bulkrax Column automatically matched object #{node_name}, #{node_content}") - parsed_metadata[object_name] ||= object_multiple ? [{}] : {} - end - - value = if matcher - result = matcher.result(self, node_content) - matched_metadata(multiple, name, result, object_multiple) - elsif multiple - Rails.logger.info("Bulkrax Column automatically matched #{node_name}, #{node_content}") - # OVERRIDE BULKRAX 1.0.2 to avoid ActiveTriples::Relation::ValueError - multiple_metadata(node_content, node_name) - else - Rails.logger.info("Bulkrax Column automatically matched #{node_name}, #{node_content}") - single_metadata(node_content) - end - - object_name.present? ? set_parsed_object_data(object_multiple, object_name, name, index, value) : set_parsed_data(name, value) - end - end - - def multiple_metadata(content, name = nil) - return unless content - - case content - when Nokogiri::XML::NodeSet - content&.content - when Array - # OVERRIDE BULKRAX 1.0.2 to avoid ActiveTriples::Relation::ValueError - if name == 'head' || name == 'tail' - content.map do |obj| - obj.delete("id") - end - else - content - end - when Hash - Array.wrap(content) - when String - Array.wrap(content.strip) - else - Array.wrap(content) - end - end -end diff --git a/app/models/concerns/bulkrax/import_behavior.rb b/app/models/concerns/bulkrax/import_behavior.rb index 737680e2b..e54553eda 100644 --- a/app/models/concerns/bulkrax/import_behavior.rb +++ b/app/models/concerns/bulkrax/import_behavior.rb @@ -6,7 +6,7 @@ Bulkrax::ImportBehavior.class_eval do def factory - @factory ||= Bulkrax::ObjectFactory.new(attributes: self.parsed_metadata || self.raw_metadata, + @factory ||= Bulkrax.object_factory.new(attributes: self.parsed_metadata || self.raw_metadata, source_identifier_value: identifier, work_identifier: parser.work_identifier, collection_field_mapping: parser.collection_field_mapping, diff --git a/app/models/concerns/bulkrax/pbcore_parser_behavior.rb b/app/models/concerns/bulkrax/pbcore_parser_behavior.rb index e22d039f8..c38690fe5 100644 --- a/app/models/concerns/bulkrax/pbcore_parser_behavior.rb +++ b/app/models/concerns/bulkrax/pbcore_parser_behavior.rb @@ -29,7 +29,7 @@ def parse_rows(rows, type, asset_id, related_identifier = nil, parent_asset = ni def set_model(type, asset_id, current_object, parent_asset, counter = nil) key_count = counter || objects.select { |obj| obj[:model] == type }.size + 1 - bulkrax_identifier = current_object[:bulkrax_identifier] || Bulkrax.fill_in_blank_source_identifiers.call(type, asset_id, key_count) + bulkrax_identifier = current_object[:bulkrax_identifier] || Bulkrax.fill_in_blank_source_identifiers.call(type.gsub(/Resource$/, ''), asset_id, key_count) if current_object && current_object[:model].blank? current_object.merge!({ diff --git a/app/models/contribution_resource.rb b/app/models/contribution_resource.rb index 3129035e4..7e95d2d2f 100644 --- a/app/models/contribution_resource.rb +++ b/app/models/contribution_resource.rb @@ -5,4 +5,8 @@ class ContributionResource < Hyrax::Work include Hyrax::Schema(:basic_metadata) include Hyrax::Schema(:contribution_resource) + include Hyrax::ArResource + include AMS::WorkBehavior + + self.valid_child_concerns = [] end diff --git a/app/models/digital_instantiation_resource.rb b/app/models/digital_instantiation_resource.rb index 33d20d7a6..a536395f6 100644 --- a/app/models/digital_instantiation_resource.rb +++ b/app/models/digital_instantiation_resource.rb @@ -2,7 +2,55 @@ # Generated via # `rails generate hyrax:work_resource DigitalInstantiationResource` +require 'carrierwave/validations/active_model' + class DigitalInstantiationResource < Hyrax::Work + attr_accessor :skip_file_upload_validation + include Hyrax::Schema(:basic_metadata) include Hyrax::Schema(:digital_instantiation_resource) + include Hyrax::ArResource + include AMS::WorkBehavior + include ::AMS::CreateMemberMethods + extend CarrierWave::Mount + + mount_uploader :digital_instantiation_pbcore_xml, PbCoreInstantiationXmlUploader + + self.valid_child_concerns = [EssenceTrackResource] + + # after_initialize does not work with Valkyrie apparently + # so to get the #create_child_methods method to run + # we have to call it like this + def initialize(*args) + super + create_child_methods + end + + def pbcore_validate_instantiation_xsd + if digital_instantiation_pbcore_xml.file + schema = Nokogiri::XML::Schema(File.read(Rails.root.join('spec', 'fixtures', 'pbcore-2.1.xsd'))) + document = Nokogiri::XML(File.read(digital_instantiation_pbcore_xml.file.file)) + schema.validate(document).each do |error| + errors.add(:digital_instantiation_pbcore_xml, error.message) + end + elsif self.new_record? && !skip_file_upload_validation + errors.add(:digital_instantiation_pbcore_xml,"Please select pbcore xml document") + end + end + + def instantiation_admin_data + return @instantiation_admin_data if @instantiation_admin_data.present? + + if instantiation_admin_data_gid.present? + @instantiation_admin_data = InstantiationAdminData.find_by_gid(instantiation_admin_data_gid) + end + + @instantiation_admin_data + end + + def instantiation_admin_data=(new_admin_data) + self.instantiation_admin_data_gid = new_admin_data.gid + @instantiation_admin_data = new_admin_data + end + end diff --git a/app/models/essence_track_resource.rb b/app/models/essence_track_resource.rb index 7d35c6f03..dad0eaad5 100644 --- a/app/models/essence_track_resource.rb +++ b/app/models/essence_track_resource.rb @@ -5,4 +5,9 @@ class EssenceTrackResource < Hyrax::Work include Hyrax::Schema(:basic_metadata) include Hyrax::Schema(:essence_track_resource) + include Hyrax::ArResource + include AMS::WorkBehavior + + self.valid_child_concerns = [] + end diff --git a/app/models/hyrax/pcdm_collection_decorator.rb b/app/models/hyrax/pcdm_collection_decorator.rb new file mode 100644 index 000000000..ca953516a --- /dev/null +++ b/app/models/hyrax/pcdm_collection_decorator.rb @@ -0,0 +1,5 @@ +# OVERRIDE Hyrax 5.0 to add basic metadata to collection + +Hyrax::PcdmCollection.class_eval do + include Hyrax::Schema(:basic_metadata) +end diff --git a/app/models/physical_instantiation_resource.rb b/app/models/physical_instantiation_resource.rb index 7a8722c21..225902f5d 100644 --- a/app/models/physical_instantiation_resource.rb +++ b/app/models/physical_instantiation_resource.rb @@ -5,4 +5,32 @@ class PhysicalInstantiationResource < Hyrax::Work include Hyrax::Schema(:basic_metadata) include Hyrax::Schema(:physical_instantiation_resource) + include Hyrax::ArResource + include AMS::WorkBehavior + include ::AMS::CreateMemberMethods + + self.valid_child_concerns = [EssenceTrackResource] + + # after_initialize does not work with Valkyrie apparently + # so to get the #create_child_methods method to run + # we have to call it like this + def initialize(*args) + super + create_child_methods + end + + def instantiation_admin_data + return @instantiation_admin_data if @instantiation_admin_data + + if instantiation_admin_data_gid.present? + @instantiation_admin_data = InstantiationAdminData.find_by_gid(instantiation_admin_data_gid) + end + + @instantiation_admin_data + end + + def instantiation_admin_data=(new_admin_data) + self.instantiation_admin_data_gid = new_admin_data.gid + @instantiation_admin_data = new_admin_data + end end diff --git a/app/models/push.rb b/app/models/push.rb index 9d9da9337..593638822 100644 --- a/app/models/push.rb +++ b/app/models/push.rb @@ -30,25 +30,25 @@ def ids_not_found def validate_status invalid_docs = export_search.solr_documents.reject do |doc| - doc.validation_status_for_aapb == [Asset::VALIDATION_STATUSES[:valid]] + doc.validation_status_for_aapb == [AssetResource::VALIDATION_STATUSES[:valid]] end return if invalid_docs.blank? - Asset::VALIDATION_STATUSES.each_pair do |status_key, status_value| + AssetResource::VALIDATION_STATUSES.each_pair do |status_key, status_value| next if status_key == :valid add_status_error(invalid_docs, status_value) end end def add_status_error(invalid_docs, status) - ids_matching_status = if status == Asset::VALIDATION_STATUSES[:empty] + ids_matching_status = if status == AssetResource::VALIDATION_STATUSES[:empty] invalid_docs.select { |doc| doc.validation_status_for_aapb.blank? }.map(&:id) else invalid_docs.select { |doc| doc.validation_status_for_aapb.include?(status) }.map(&:id) end # Prevents adding errors to docs that don't have a value - # in :validation_status_for_aapb, including all non-Assets. + # in :validation_status_for_aapb, including all non-AssetResources. return if ids_matching_status.blank? errors.add(:pushed_id_csv, "The following IDs are #{status}: #{ids_matching_status.join(', ')}") diff --git a/app/models/solr_document.rb b/app/models/solr_document.rb index 146e31eb4..2687adfe0 100644 --- a/app/models/solr_document.rb +++ b/app/models/solr_document.rb @@ -39,9 +39,9 @@ def has_model end # Define boolean predicates for determining record type. - def is_asset?; has_model == "Asset"; end - def is_physical_instantiation?; has_model == "PhysicalInstantiation"; end - def is_digital_instantiation?; has_model == "DigitalInstantiation"; end + def is_asset?; has_model.match(/^Asset/); end + def is_physical_instantiation?; has_model.match(/^PhysicalInstantiation/); end + def is_digital_instantiation?; has_model.match(/^DigitalInstantiation/); end def is_instantiation?; is_digital_instantiations || is_physical_instantiation; end # Specific ID accessors based on record type. @@ -81,11 +81,11 @@ def parent_asset_id end def physical_instantiations - members only: PhysicalInstantiation + members only: [PhysicalInstantiation, PhysicalInstantiationResource] end def digital_instantiations - members only: DigitalInstantiation + members only: [DigitalInstantiation, DigitalInstantiationResource] end def asset_types @@ -541,11 +541,11 @@ def all_members(only: [], exclude: []) def admin_data_gid return unless is_asset? - self['admin_data_gid_ssim'].first + self['admin_data_gid_ssim']&.first end def admin_data - return unless is_asset? + return unless is_asset? && admin_data_gid @admin_data ||= AdminData.find_by_gid(admin_data_gid) end diff --git a/app/parsers/pbcore_manifest_parser.rb b/app/parsers/pbcore_manifest_parser.rb index 798a6103d..cd11b81de 100644 --- a/app/parsers/pbcore_manifest_parser.rb +++ b/app/parsers/pbcore_manifest_parser.rb @@ -180,7 +180,7 @@ def get_manifest_filename(csv_row) end def build_digital_instantiations(file, csv_row, digital_instantiation, index, asset) - current_object = [AAPB::BatchIngest::PBCoreXMLMapper.new(file[:data]).digital_instantiation_attributes.merge!( + current_object = [AAPB::BatchIngest::PBCoreXMLMapper.new(file[:data]).digital_instantiation_resource_attributes.merge!( { filename: file[:filename], pbcore_xml: file[:data], diff --git a/app/parsers/pbcore_xml_parser.rb b/app/parsers/pbcore_xml_parser.rb index ff6d35011..e999dca4a 100644 --- a/app/parsers/pbcore_xml_parser.rb +++ b/app/parsers/pbcore_xml_parser.rb @@ -112,7 +112,7 @@ def total def setup_parents parents = [] - importer.entries.where('raw_metadata REGEXP ?', '.*children\":\[.+\].*') + importer.entries.where('raw_metadata ~ ?', '.*children\":\[.+\].*') end # Will be skipped unless the #record is a Hash @@ -124,7 +124,7 @@ def create_parent_child_relationships importer.entries.find_by(identifier: child_id) end - Bulkrax::ChildRelationshipsJob.perform_later(parent.id, children.map(&:id), current_run.id) if parent.present? && children.present? + Bulkrax::ChildRelationshipsJob.set(wait: 5.minutes).perform_later(parent.id, children.map(&:id), current_run.id) if parent.present? && children.present? end rescue StandardError => e status_info(e) @@ -145,10 +145,15 @@ def set_objects(file, index) xml_asset = AAPB::BatchIngest::PBCoreXMLMapper.new(file[:data]).asset_attributes.merge!({ delete: file[:delete], pbcore_xml: file[:data] }) xml_asset[:children] = [] asset_id = xml_asset[:id] - asset = Asset.where(id: xml_asset[:id])&.first + # resource = Hyrax.query_service.find_by(id: Valkyrie::ID.new(doc_id)) + + begin + asset = Hyrax.query_service.find_by(id: xml_asset[:id]) + rescue Valkyrie::Persistence::ObjectNotFoundError + end asset_attributes = asset&.attributes&.symbolize_keys xml_asset = asset_attributes.merge(xml_asset) if asset_attributes - parse_rows([xml_asset], 'Asset', asset_id) + parse_rows([xml_asset], 'AssetResource', asset_id) add_object(xml_asset) instantiation_rows(instantiations, xml_asset, asset, asset_id) objects @@ -157,13 +162,14 @@ def set_objects(file, index) def instantiation_rows(instantiations, xml_asset, asset, asset_id) xml_records = [] instantiations.each.with_index do |inst, i| - instantiation_class = 'PhysicalInstantiation' if inst.physical - instantiation_class ||= 'DigitalInstantiation' if inst.digital + instantiation_class = 'PhysicalInstantiationResource' if inst.physical + instantiation_class ||= 'DigitalInstantiationResource' if inst.digital next unless instantiation_class xml_record = AAPB::BatchIngest::PBCoreXMLMapper.new(inst.to_xml).send("#{instantiation_class.to_s.underscore}_attributes").merge!({ pbcore_xml: inst.to_xml, skip_file_upload_validation: true }) # Find members of the asset that have the same class and local identifier. If no asset, then no digital instantiation can exist instantiation = asset.members[i] if asset && asset.members[i]&.class == instantiation_class.constantize xml_record = instantiation.attributes.symbolize_keys.merge(xml_record) if instantiation + xml_record[:title] = create_title(nil) xml_record[:children] = [] # we accumulate the tracks here so that they are added to the bulkrax entries list in the order they appear in the pbcore xml xml_tracks = [] @@ -171,7 +177,8 @@ def instantiation_rows(instantiations, xml_asset, asset, asset_id) xml_track = AAPB::BatchIngest::PBCoreXMLMapper.new(track.to_xml).essence_track_attributes.merge({ pbcore_xml: track.to_xml }) essence_track = instantiation.members[j] if instantiation&.members&.[](j)&.class == EssenceTrack xml_track = essence_track.attributes.symbolize_keys.merge(xml_track) if essence_track - parse_rows([xml_track], 'EssenceTrack', asset_id, asset, j+1) + xml_track[:title] = create_title(nil) + parse_rows([xml_track], 'EssenceTrackResource', asset_id, asset, j+1) xml_record[:children] << xml_track[work_identifier] xml_tracks << xml_track end diff --git a/app/presenters/aapb/attribute_indexed_to_parent_presenter.rb b/app/presenters/aapb/attribute_indexed_to_parent_presenter.rb index 0d0ee5623..675e14c93 100644 --- a/app/presenters/aapb/attribute_indexed_to_parent_presenter.rb +++ b/app/presenters/aapb/attribute_indexed_to_parent_presenter.rb @@ -1,13 +1,13 @@ module AAPB module AttributeIndexedToParentPresenter def attribute_indexed_to_parent?(field, work_class) - return true unless attributes_indexed_to_parent(work_class)[field.to_s].nil? - false + attributes = attributes_indexed_to_parent(work_class) + attributes && attributes.key?(field.to_s) end def attribute_facetable?(field, work_class) - return true unless facetable_attributes(work_class)[field.to_s].nil? - false + attributes = facetable_attributes(work_class) + attributes && attributes.key?(field.to_s) end private @@ -23,4 +23,4 @@ def facetable_attributes(work_class) end end -end \ No newline at end of file +end diff --git a/app/presenters/hyrax/asset_presenter.rb b/app/presenters/hyrax/asset_presenter.rb index 84ccd9afb..3c0d94aeb 100644 --- a/app/presenters/hyrax/asset_presenter.rb +++ b/app/presenters/hyrax/asset_presenter.rb @@ -72,13 +72,13 @@ def filter_item_ids_to_display(solr_query) end def list_of_instantiation_ids_to_display - query = "(has_model_ssim:DigitalInstantiation OR has_model_ssim:PhysicalInstantiation) " + query = "(has_model_ssim:DigitalInstantiationResource OR has_model_ssim:PhysicalInstantiationResource OR has_model_ssim:DigitalInstantiation OR has_model_ssim:PhysicalInstantiation) " authorized_instantiation_ids = filter_item_ids_to_display(query) paginated_item_list(page_array: authorized_instantiation_ids) end def list_of_contribution_ids_to_display - query = "(has_model_ssim:Contribution) " + query = "(has_model_ssim:Contribution OR has_model_ssim:ContributionResource) " authorized_contribution_ids = filter_item_ids_to_display(query) paginated_item_list(page_array: authorized_contribution_ids) end diff --git a/app/presenters/hyrax/asset_resource_presenter.rb b/app/presenters/hyrax/asset_resource_presenter.rb new file mode 100644 index 000000000..9b37abe71 --- /dev/null +++ b/app/presenters/hyrax/asset_resource_presenter.rb @@ -0,0 +1,123 @@ +# Generated via +# `rails generate hyrax:work Asset` +module Hyrax + class AssetResourcePresenter < Hyrax::WorkShowPresenter + include ActionView::Helpers::TagHelper + + delegate :id, :genre, :asset_types, :broadcast_date, :created_date, :copyright_date, + :episode_number, :spatial_coverage, :temporal_coverage, + :audience_level, :audience_rating, :annotation, :rights_summary, :rights_link, + :date, :local_identifier, :pbs_nola_code, :eidr_id, :topics, :subject, + :program_title, :episode_title, :segment_title, :raw_footage_title, :promo_title, :clip_title, :description, + :program_description, :episode_description, :segment_description, :raw_footage_description, + :promo_description, :clip_description, :copyright_date, :validation_status_for_aapb, + :level_of_user_access, :outside_url, :special_collections, :transcript_status, :organization, + :sonyci_id, :licensing_info, :producing_organization, :series_title, :series_description, + :playlist_group, :playlist_order, :hyrax_batch_ingest_batch_id, :bulkrax_importer_id, :last_pushed, :last_update, :needs_update, :special_collection_category, :canonical_meta_tag, :cataloging_status, + to: :solr_document + + def batch + raise 'No Batch ID associated with this Asset' unless hyrax_batch_ingest_batch_id.first.present? + @batch ||= Hyrax::BatchIngest::Batch.find(hyrax_batch_ingest_batch_id.first) + end + + def batch_url + @batch_url ||= "/batches/#{batch.id}" + end + + def batch_ingest_label + @batch_ingest_label ||= Hyrax::BatchIngest.config.ingest_types[batch.ingest_type.to_sym].label + end + + def batch_ingest_date + @batch_ingest_date ||= Date.parse(batch.created_at.to_s) + end + + def bulkrax_import + raise 'No Bulkrax Import ID associated with this Asset' unless bulkrax_importer_id.present? + @bulkrax_import ||= Bulkrax::Importer.find(bulkrax_importer_id.first) + end + + def bulkrax_import_url + @bulkrax_import_url ||= "/importers/#{bulkrax_import.id}" + end + + def bulkrax_import_label + @bulkrax_import_ingest_label ||= bulkrax_import.parser_klass + end + + def bulkrax_import_date + @bulkrax_import_ingest_date ||= Date.parse(bulkrax_import.updated_at.to_s) + end + + def annotations + @annotations ||= Hyrax.query_service.find_by(id: solr_document['id']).annotations + end + + def aapb_badge + if validation_status_for_aapb.to_a.include?('valid') + tag.span('AAPB Valid', class: "aapb-badge badge badge-success") + elsif validation_status_for_aapb.blank? + tag.span('Not AAPB Validated', class: "aapb-badge badge badge-warning") + else + tag.span(validation_status_for_aapb.join(", ").humanize, class: "aapb-badge badge badge-danger") + end + end + + def last_pushed + timestamp_to_display_date solr_document['last_pushed'] + end + + def last_updated + timestamp_to_display_date solr_document['last_updated'] + end + + def needs_update + solr_document['needs_update'] + end + + def filter_item_ids_to_display(solr_query) + return [] if authorized_item_ids.empty? + query_ids = authorized_item_ids.map {|id| "id:#{id}"} .join(" OR ") + solr_query += " AND (#{query_ids})" + Hyrax::SolrService.query(solr_query, rows: 10_000, fl: "id").map(&:id) + end + + def list_of_instantiation_ids_to_display + query = "(has_model_ssim:DigitalInstantiationResource OR has_model_ssim:PhysicalInstantiationResource OR has_model_ssim:DigitalInstantiation OR has_model_ssim:PhysicalInstantiation) " + authorized_instantiation_ids = filter_item_ids_to_display(query) + paginated_item_list(page_array: authorized_instantiation_ids) + end + + def list_of_contribution_ids_to_display + query = "(has_model_ssim:ContributionResource OR has_model_ssim:Contribution) " + authorized_contribution_ids = filter_item_ids_to_display(query) + paginated_item_list(page_array: authorized_contribution_ids) + end + + def display_aapb_admin_data? + ! ( sonyci_id.blank? && + bulkrax_importer_id.blank? && + hyrax_batch_ingest_batch_id.blank? && + last_updated.blank? && + last_pushed.blank? && + needs_update.blank? + ) + end + + def display_annotations? + return true if Annotation.registered_annotation_types.values.map{ |type| solr_document.send(type.to_sym).present? }.uniq.include?(true) + false + end + + def media_available? + sonyci_id.blank? ? false : true + end + + private + + def timestamp_to_display_date(timestamp) + ApplicationHelper.display_date(timestamp, format: '%m-%e-%y %H:%M %Z', from_format: '%s', time_zone: 'US/Eastern') + end + end +end diff --git a/app/presenters/hyrax/contribution_resource_presenter.rb b/app/presenters/hyrax/contribution_resource_presenter.rb new file mode 100644 index 000000000..fce3c9c9c --- /dev/null +++ b/app/presenters/hyrax/contribution_resource_presenter.rb @@ -0,0 +1,7 @@ +# Generated via +# `rails generate hyrax:work Contribution` +module Hyrax + class ContributionResourcePresenter < Hyrax::WorkShowPresenter + delegate :contributor_role, :portrayal, :affiliation, to: :solr_document + end +end diff --git a/app/presenters/hyrax/digital_instantiation_resource_presenter.rb b/app/presenters/hyrax/digital_instantiation_resource_presenter.rb new file mode 100644 index 000000000..382e949af --- /dev/null +++ b/app/presenters/hyrax/digital_instantiation_resource_presenter.rb @@ -0,0 +1,12 @@ +# Generated via +# `rails generate hyrax:work DigitalInstantiation` +module Hyrax + class DigitalInstantiationResourcePresenter < Hyrax::WorkShowPresenter + include AAPB::InstantiationAdminDataPresenter + include AAPB::AttributeIndexedToParentPresenter + + delegate :date, :dimensions, :digital_format, :standard, :location, :media_type, :generations, :file_size, :time_start, :duration, + :data_rate, :colors, :language, :rights_summary, :rights_link, :annotation, :local_instantiation_identifier, :tracks, + :channel_configuration, :alternative_modes, :holding_organization, :aapb_preservation_lto, :aapb_preservation_disk, :md5, to: :solr_document + end +end diff --git a/app/presenters/hyrax/essence_track_resource_presenter.rb b/app/presenters/hyrax/essence_track_resource_presenter.rb new file mode 100644 index 000000000..37fd20e81 --- /dev/null +++ b/app/presenters/hyrax/essence_track_resource_presenter.rb @@ -0,0 +1,13 @@ +# Generated via +# `rails generate hyrax:work EssenceTrack` +module Hyrax + class EssenceTrackResourcePresenter < Hyrax::WorkShowPresenter + delegate :track_type, :track_id, :standard, :encoding, :data_rate, :frame_rate,:playback_speed, :playback_speed_units, + :sample_rate, :bit_depth, :frame_width, :frame_height, :time_start, :duration, :annotation, to: :solr_document + + def attribute_to_html(field, options = {}) + options.merge!({:html_dl=> true}) + super(field, options) + end + end +end diff --git a/app/presenters/hyrax/physical_instantiation_resource_presenter.rb b/app/presenters/hyrax/physical_instantiation_resource_presenter.rb new file mode 100644 index 000000000..bac5311d4 --- /dev/null +++ b/app/presenters/hyrax/physical_instantiation_resource_presenter.rb @@ -0,0 +1,12 @@ +# Generated via +# `rails generate hyrax:work PhysicalInstantiation` +module Hyrax + class PhysicalInstantiationResourcePresenter < Hyrax::WorkShowPresenter + include AAPB::InstantiationAdminDataPresenter + include AAPB::AttributeIndexedToParentPresenter + + delegate :date, :digitization_date, :dimensions, :format, :standard, :location, :media_type, :generations, :time_start, :duration, :colors, + :language, :rights_summary, :rights_link, :annotation, :local_instantiation_identifier, :tracks, :channel_configuration, + :alternative_modes, :holding_organization, :aapb_preservation_lto, :aapb_preservation_disk, to: :solr_document + end +end diff --git a/app/presenters/hyrax/select_type_list_presenter.rb b/app/presenters/hyrax/select_type_list_presenter.rb index eea499254..705752d3f 100644 --- a/app/presenters/hyrax/select_type_list_presenter.rb +++ b/app/presenters/hyrax/select_type_list_presenter.rb @@ -24,7 +24,7 @@ def authorized_models end def new_works_models - [Asset] + [AssetResource] end # Return or yield the first model in the list. This is used when the list diff --git a/app/search_builders/ams/search_builder.rb b/app/search_builders/ams/search_builder.rb index 5c3d9b31a..493750e74 100644 --- a/app/search_builders/ams/search_builder.rb +++ b/app/search_builders/ams/search_builder.rb @@ -7,7 +7,7 @@ class SearchBuilder < Hyrax::CatalogSearchBuilder # Overrides Hyrax::FilterModels. def models - [Asset] + [AssetResource] end # Adds date filters to the :fq of the solr params. diff --git a/app/services/aapb/asset_thumbnail_path_service.rb b/app/services/aapb/asset_thumbnail_path_service.rb index 5240adb7b..950f40f0f 100644 --- a/app/services/aapb/asset_thumbnail_path_service.rb +++ b/app/services/aapb/asset_thumbnail_path_service.rb @@ -8,11 +8,16 @@ class << self def call(object) @object_type = object.class.name.underscore - @sonyci_id = object.admin_data.sonyci_id || [] + @sonyci_id = object.admin_data&.sonyci_id || [] @id = object.id - @digital_instantiations = object.digital_instantiations - @aapb_digital_instantiation = object.digital_instantiations.find { |inst| inst.holding_organization&.include?( "American Archive of Public Broadcasting") } || nil - + case object + when ActiveFedora::Base + @digital_instantiations = object.digital_instantiations + @aapb_digital_instantiation = object.digital_instantiations.find { |inst| inst.holding_organization&.include?( "American Archive of Public Broadcasting") } || nil + when Valkyrie::Resource + @digital_instantiations = object.digital_instantiation_resources + @aapb_digital_instantiation = object.digital_instantiation_resources.find { |inst| inst.holding_organization&.include?( "American Archive of Public Broadcasting") } || nil + end super end diff --git a/app/services/aapb/batch_ingest/bulkrax_xml_mapper.rb b/app/services/aapb/batch_ingest/bulkrax_xml_mapper.rb new file mode 100644 index 000000000..5e8f3549f --- /dev/null +++ b/app/services/aapb/batch_ingest/bulkrax_xml_mapper.rb @@ -0,0 +1,319 @@ +module AAPB + module BatchIngest + class BulkraxXMLMapper + + attr_reader :pbcore_xml + + def initialize(pbcore_xml) + @pbcore_xml = pbcore_xml + end + + def categorize(collection, criteria: [:type,:to_s,:downcase,:strip], accessor: [:value]) + grouped = {} + collection.each do |anno| + cat_name = criteria.inject(anno) {|object, method| object.send(method) } + value = accessor.inject(anno) {|object, method| object.send(method) } + + if value + grouped[ cat_name ] ||= [] + grouped[ cat_name ] << value + end + end + grouped + end + + def prepare_annotations(annotations) + final_annotations = [] + + annotations.each do |anno| + annotation_type = find_annotation_type_id(anno.type) + + anno_hash = { + "ref" => anno.ref, + "annotation_type" => annotation_type, + "source" => anno.source, + "value" => anno.value, + "annotation" => anno.annotation, + "version" => anno.version + } + + final_annotations << anno_hash + end + final_annotations + end + + def find_annotation_type_id(type) + type_id = Annotation.find_annotation_type_id(type) + + if ENV['SETTINGS__BULKRAX__ENABLED'] == 'true' + type_id + else + if type_id.present? + type_id + else + raise "annotation_type not registered with the AnnotationTypesService: #{type}." + end + end + end + + def asset_attributes + @asset_attributes ||= {}.tap do |attrs| + + attrs[:annotations] = prepare_annotations(pbcore.annotations) + + # Saves Asset with AAPB ID if present + attrs[:id] = normalized_aapb_id(aapb_id) if aapb_id + + # grouped by title type + grouped_titles = categorize(pbcore.titles) + # pull out no-type titles, removing from grouped_titles + titles_no_type = grouped_titles.slice!(*title_types) + attrs[:title] = titles_no_type.values.flatten + attrs[:episode_title] = grouped_titles["episode"] if grouped_titles["episode"] + attrs[:program_title] = grouped_titles["program"] if grouped_titles["program"] + attrs[:segment_title] = grouped_titles["segment"] if grouped_titles["segment"] + attrs[:clip_title] = grouped_titles["clip"] if grouped_titles["clip"] + attrs[:promo_title] = grouped_titles["promo"] if grouped_titles["promo"] + attrs[:series_title] = grouped_titles["series"] if grouped_titles["series"] + attrs[:raw_footage_title] = grouped_titles["raw footage"] if grouped_titles["raw footage"] + attrs[:episode_number] = grouped_titles["episode number"] if grouped_titles["episode number"] + + grouped_descriptions = categorize(pbcore.descriptions) + # pull out no-type descs, removing from grouped_descs + descriptions_no_type = grouped_descriptions.slice!(*desc_types) + attrs[:description] = descriptions_no_type.values.flatten + attrs[:episode_description] = (grouped_descriptions.fetch("episode", []) + grouped_descriptions.fetch("episode description", [])) + attrs[:series_description] = (grouped_descriptions.fetch("series", []) + grouped_descriptions.fetch("series description", [])) + attrs[:program_description] = (grouped_descriptions.fetch("program", []) + grouped_descriptions.fetch("program description", [])) + attrs[:segment_description] = (grouped_descriptions.fetch("segment", []) + grouped_descriptions.fetch("segment description", [])) + attrs[:clip_description] = (grouped_descriptions.fetch("clip", []) + grouped_descriptions.fetch("clip description", [])) + attrs[:promo_description] = (grouped_descriptions.fetch("promo", []) + grouped_descriptions.fetch("promo description", [])) + attrs[:raw_footage_description] = (grouped_descriptions.fetch("raw footage", []) + grouped_descriptions.fetch("raw footage description", [])) + + grouped_dates = categorize(pbcore.asset_dates) + # pull out no-type dates, removing from grouped_dates + dates_no_type = grouped_dates.slice!(*date_types) + attrs[:date] = transform_dates dates_no_type.values.flatten + attrs[:broadcast_date] = transform_dates grouped_dates.fetch("broadcast", []) + attrs[:copyright_date] = transform_dates grouped_dates.fetch("copyright", []) + attrs[:created_date] = transform_dates grouped_dates.fetch("created", []) + + attrs[:audience_level] = pbcore.audience_levels.map(&:value) + attrs[:audience_rating] = pbcore.audience_ratings.map(&:value) + attrs[:asset_types] = pbcore.asset_types.map(&:value) + attrs[:genre] = pbcore.genres.select { |genre| genre.source.to_s.downcase == "aapb format genre" }.map(&:value) + attrs[:topics] = pbcore.genres.select { |genre| genre.source.to_s.downcase == "aapb topical genre" }.map(&:value) + + grouped_coverages = categorize(pbcore.coverages, criteria: [:type,:value,:downcase,:strip], accessor: [:coverage, :value]) + attrs[:spatial_coverage] = grouped_coverages["spatial"] if grouped_coverages["spatial"] + attrs[:temporal_coverage] = grouped_coverages["temporal"] if grouped_coverages["temporal"] + + attrs[:rights_summary] = pbcore.rights_summaries.map(&:summary).compact.map(&:value) + attrs[:rights_link] = pbcore.rights_summaries.map(&:link).compact.map(&:value) + + grouped_identifiers = categorize(pbcore.identifiers, criteria: [:source,:to_s,:downcase]) + # pull out identifiers with an unknown source and add as local identifiers + other_identifiers = grouped_identifiers.slice!(*identifier_sources) + attrs[:local_identifier] = (grouped_identifiers.fetch("local identifier", []) + other_identifiers.values).flatten + attrs[:pbs_nola_code] = (grouped_identifiers.fetch("nola code", []) + grouped_identifiers.fetch("nola", [])) + attrs[:eidr_id] = grouped_identifiers["eidr"] if grouped_identifiers["eidr"] + attrs[:sonyci_id] = grouped_identifiers["sony ci"] if grouped_identifiers["sony ci"] + + attrs[:subject] = pbcore.subjects.map(&:value) + + creator_orgs, creator_people = pbcore.creators.partition { |pbcreator| pbcreator.role&.value == 'Producing Organization' } + all_people = pbcore.contributors + pbcore.publishers + creator_people + attrs[:contributors] = people_attributes(all_people) + attrs[:producing_organization] = creator_orgs.map {|co| co.creator.value} + + intended_children_count = 0 + intended_children_count += pbcore.instantiations.size + intended_children_count += pbcore.instantiations.map(&:essence_tracks).flatten.size + attrs[:intended_children_count] = intended_children_count + end + end + + def aapb_id + @aapb_id ||= pbcore.identifiers.select do |id| + id.source == "http://americanarchiveinventory.org" + end.map(&:value).first + end + + def normalized_aapb_id(id) + id.gsub('cpb-aacip/', 'cpb-aacip-') if id + end + + def people_attributes(people) + people.map do |person_node| + person = if person_node.is_a? PBCore::Contributor + person_node.contributor + elsif person_node.is_a? PBCore::Publisher + person_node.publisher + elsif person_node.is_a? PBCore::Creator + person_node.creator + end + + role = person_node.role + person_attributes(person, role) + end + end + + def person_attributes(person, role) + { + contributor: (person.value if person), + contributor_role: (role.value if role), + # pbcorecontributor ONLY + affiliation: (person.affiliation if defined? person.affiliation), + portrayal: (role.portrayal if role && defined? role.portrayal), + } + + end + + def physical_instantiation_resource_attributes + @physical_instantiation_resource_attributes ||= instantiation_attributes.tap do |attrs| + attrs[:format] = pbcore.physical.value || nil + end + end + + def digital_instantiation_resource_attributes + @digital_instantiation_attributes ||= instantiation_attributes.tap do |attrs| + attrs[:format] = pbcore.digital.value || nil + end + end + + def instantiation_attributes + @instantiation_attributes ||= {}.tap do |attrs| + attrs[:date] = transform_dates(pbcore.dates.select { |date| date.type.to_s.downcase.strip != "digitized" }.map(&:value)) + attrs[:digitization_date] = transform_dates(pbcore.dates.select { |date| date.type.to_s.downcase.strip == "digitized" }.map(&:value).first) + + attrs[:dimensions] = pbcore.dimensions.map(&:value) + attrs[:standard] = pbcore.standard&.value + attrs[:location] = pbcore.location&.value + attrs[:media_type] = pbcore.media_type&.value + attrs[:format] = pbcore.physical&.value + attrs[:generations] = pbcore.generations.map(&:value) + attrs[:time_start] = pbcore.time_start&.value + attrs[:duration] = pbcore.duration&.value&.gsub('?', '') + attrs[:colors] = pbcore.colors&.value + + # TODO: change left side to rights_summaries (because multiple: true), or to rights (to agree with PBCore gem's Instantiation#rights) + attrs[:rights_summary] = pbcore.rights.map(&:summary).map(&:value) + attrs[:rights_link] = pbcore.rights.map(&:link).map(&:value) + attrs[:local_instantiation_identifier] = pbcore.identifiers.select { |identifier| identifier.source.to_s.downcase.strip != "ams" }.map(&:value) + attrs[:tracks] = pbcore.tracks&.value + attrs[:channel_configuration] = pbcore.channel_configuration&.value + attrs[:alternative_modes] = pbcore.alternative_modes&.value + + orgs, annotations = pbcore.annotations.partition { |anno| anno.type && anno.type.downcase == 'organization' } + attrs[:holding_organization] = orgs.first.value if orgs.present? + attrs[:annotation] = annotations.map(&:value) + end + end + + def essence_track_attributes + @essence_track_attributes ||= {}.tap do |attrs| + + attrs[:track_type] = pbcore.type.value if pbcore.type + attrs[:track_id] = pbcore.identifiers.map(&:value) if pbcore.identifiers + attrs[:standard] = pbcore.standard.value if pbcore.standard + attrs[:encoding] = pbcore.encoding.value if pbcore.encoding + attrs[:data_rate] = pbcore.data_rate.value if pbcore.data_rate + attrs[:frame_rate] = pbcore.frame_rate.value if pbcore.frame_rate + attrs[:sample_rate] = pbcore.sampling_rate.value if pbcore.sampling_rate + attrs[:bit_depth] = pbcore.bit_depth.value if pbcore.bit_depth + + # frame size becomes: + frame_width, frame_height = pbcore.frame_size.value.split('x') if pbcore.frame_size + attrs[:frame_width] = frame_width + attrs[:frame_height] = frame_height + attrs[:aspect_ratio] = pbcore.aspect_ratio.value if pbcore.aspect_ratio + attrs[:time_start] = pbcore.time_start.value if pbcore.time_start + attrs[:duration] = pbcore.duration.value.gsub('?', '') if pbcore.duration + attrs[:annotation] = pbcore.annotations.map(&:value) if pbcore.annotations + end + end + + private + + def pbcore + @pbcore ||= case + when is_description_document? + PBCore::DescriptionDocument.parse(pbcore_xml) + when is_instantiation_document? + PBCore::InstantiationDocument.parse(pbcore_xml) + when is_instantiation? + PBCore::Instantiation.parse(pbcore_xml) + when is_essence_track? + PBCore::Instantiation::EssenceTrack.parse(pbcore_xml) + else + # TODO: Custom error class? + raise "XML not recognized as PBCore" + end + end + + def is_description_document? + pbcore_xml =~ /pbcoreDescriptionDocument/ + end + + def is_instantiation_document? + pbcore_xml =~ /pbcoreInstantiationDocument/ + end + + def is_instantiation? + pbcore_xml =~ /pbcoreInstantiation/ + end + + def is_essence_track? + pbcore_xml =~ /instantiationEssenceTrack/ + end + + def title_types + @title_types ||= ['program', 'episode', 'episode title', 'episode number', 'segment', 'clip', 'promo', 'raw footage', 'series'] + end + + def desc_types + @desc_types ||= ['program','segment','clip','promo','footage','episode','series','raw footage','program description','segment description','clip description','promo description','raw footage description','episode description','series description'] + end + + def date_types + @date_types ||= ['broadcast', 'copyright', 'created'] + end + + def identifier_sources + @identifier_sources ||= ['nola code','local identifier','eidr','sony ci', 'http://americanarchiveinventory.org'] + end + + # Transforms one or more dates. + # @param [Array<%= t('.annotation_type') %> | +<%= t('.ref') %> | +<%= t('.source') %> | +<%= t('.value') %> | +<%= t('.annotation') %> | +<%= t('.version') %> | +
---|---|---|---|---|---|
<%= annotation.try(:annotation_type) %> | +<%= annotation.try(:ref) %> | +<%= annotation.try(:source) %> | +<%= annotation.try(:value) %> | +<%= annotation.try(:annotation) %> | +<%= annotation.try(:version) %> | +
<%= t('.role') %> | +<%= t('.name') %> | +<%= t('.affiliation') %> | +<%= t('.portrayal') %> | +
---|---|---|---|
<%= contribution.solr_document.try(:contributor_role).try(:first) %> | +<%= contribution.solr_document.try(:contributor).try(:first) %> | +<%= contribution.solr_document.try(:affiliation).try(:first) %> | +<%= contribution.solr_document.try(:portrayal).try(:first) %> | +
<%= t('.thumbnail') %> | +<%= t('.details') %> | +<%= t('.date_added') %> | +<%= t('.holding_institute') %> | +
---|