Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Note persistent id issue #2649

Merged
merged 2 commits into from
Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 65 additions & 0 deletions backend/app/lib/job_runners/ns2_remover_runner.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
include JSONModel

class NS2RemoverRunner < JobRunner
register_for_job_type('ns2_remover_job',
:create_permissions => :administer_system,
:cancel_permissions => :administer_system)

def run
begin
modified_records = []

job_data = @json.job

RequestContext.open(:repo_id => @job.repo_id) do
count = 0
Note.each do |n|
parent = NotePersistentId.where(:note_id => n[:id]).first
next unless ['resource', 'archival_object', 'digital_object', 'digital_object_component'].include?(parent[:parent_type])
if n.notes.lit.include?(' ns2:')
replaced = n.notes.lit.gsub('ns2:', '')
if replaced != n[:notes].lit
count += 1
parent_repo = parent[:parent_type].capitalize.constantize.where(id: parent[:parent_id]).get(:repo_id)
if job_data['dry_run']
changes = <<~CHANGES
Note:
#{n[:notes].lit}
would become:
#{replaced}\n
CHANGES
@job.write_output(changes)
else
n.update(:notes => replaced.to_sequel_blob)
end
if parent_repo == @job.repo_id
modified_records << JSONModel(parent[:parent_type].to_sym).uri_for(parent[:parent_id], :repo_id => @job.repo_id)
else
modified_records << JSONModel(parent[:parent_type].to_sym).uri_for(parent[:parent_id], :repo_id => parent_repo)
end
end
end
end

@job.write_output("#{count} note(s)#{' would be' if job_data['dry_run']} modified.")
@job.write_output("================================")
end

if job_data['dry_run']
@job.write_output("Dry run complete, no records modified.")
elsif modified_records.empty?
@job.write_output("All done, no records modified.")
else
@job.write_output("All done, logging modified records.")
end

self.success!

@job.record_created_uris(modified_records.uniq) unless job_data['dry_run']
rescue Exception => e
@job.write_output(e.message)
@job.write_output(e.backtrace)
raise e
end
end
end
82 changes: 82 additions & 0 deletions backend/spec/model_ns2_remover_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
require 'spec_helper'

def ns2_remover_job(dry_run = false)
build(
:json_job,
:job_type => 'ns2_remover_job',
:job => build(:json_ns2_remover_job, dry_run: dry_run)
)
end

def create_messy_note
content =<<~CONTENT
<p>I am a <extref ns2:actuate="onRequest" ns2:show="new"
ns2:title="oh no" ns2:href="http://example.com/">bad, bad note</extref>.<p>
CONTENT
note = build(:json_note_bibliography,
:content => [content],
:persistent_id => "123456")

note
end

describe "ns2 remover job model" do

let(:user) { create_nobody_user }
let(:resource) {
create(:json_resource, {
:notes => [create_messy_note] })
}

it "can create a ns2 remover job" do
json = ns2_remover_job
job = Job.create_from_json(
json,
:repo_id => $repo_id,
:user => user
)

jr = JobRunner.for(job)
jr.run
job.refresh

expect(job).not_to be_nil
expect(job.job_type).to eq('ns2_remover_job')
expect(job.owner.username).to eq('nobody')
end

it "deletes 'ns2:' strings in notes" do
expect(Resource.to_jsonmodel(resource.id).notes[0]['content']).to include(/ ns2:/)

json = ns2_remover_job
job = Job.create_from_json(
json,
:repo_id => $repo_id,
:user => user
)

jr = JobRunner.for(job)
jr.run
job.refresh

expect(Resource.to_jsonmodel(resource.id).notes[0]['content']).not_to include(/ ns2:/)
end

it "won't delete 'ns2:' strings in notes when running dry run" do
expect(Resource.to_jsonmodel(resource.id).notes[0]['content']).to include(/ ns2:/)

json = ns2_remover_job(true)
job = Job.create_from_json(
json,
:repo_id => $repo_id,
:user => user
)

jr = JobRunner.for(job)
jr.run
job.refresh

expect(Resource.to_jsonmodel(resource.id).notes[0]['content']).to include(/ ns2:/)
end

end
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require 'json'

Sequel.migration do
$stderr.puts "Assigning note persistent ids for agent contact notes"
up do
suspect_fks = [
:rights_statement_id,
:lang_material_id,
:agent_topic_id,
:agent_place_id,
:agent_occupation_id,
:agent_function_id,
:agent_gender_id,
:used_language_id,
:agent_contact_id
]

self[:note]
.left_join(:note_persistent_id, Sequel.qualify(:note, :id) => Sequel.qualify(:note_persistent_id, :note_id))
.filter(Sequel.qualify(:note_persistent_id, :persistent_id) => nil)
.select(Sequel.qualify(:note, :id), :notes, *suspect_fks)
.each do |row|
notes = JSON.parse(row[:notes].to_s)
next unless notes["persistent_id"]
row.reject! { |k, v| v.nil? }
parent_type = row.keys.last.to_s.sub(/_id$/, '')
parent_id = row.values.last
self[:note_persistent_id].insert(note_id: row[:id], persistent_id: notes["persistent_id"], parent_type: parent_type, parent_id: parent_id)
end
end
end
9 changes: 9 additions & 0 deletions common/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2494,6 +2494,7 @@ en:
unknown_job_type: Unknown job type
bulk_import_job: Load via Spreadsheet
top_container_linker_job: Top Container Linker
ns2_remover_job: ns2 Namespace Remover
status: Status
status_completed: Completed
status_queued: Queued
Expand Down Expand Up @@ -2574,6 +2575,14 @@ en:
trim_whitespace_job:
description: Removes leading and trailing whitespace from titles for resources, accessions, digital objects, archival objects, and digital object components.

ns2_remover_job:
description: >
Archivists' Toolit-era EADs imported into ASpace may have vestigal "ns2" namespace references pre-pended to various EAD tag attributes inside note subrecords.
This may produce validation errors on EAD export from ArchivesSpace, as the root <ead> tag doesn't declare the "ns2" namespace.
This job bulk deletes "ns2" references within Notes subrecords attached to resource, archival_object, digital_object, and digital_object_component
records. As this job works across all repositories, it is restricted to users with <b>System Administrator permissions only</b>.
dry_run: Dry run?

advanced_search:
type:
text: Text
Expand Down
16 changes: 16 additions & 0 deletions common/schemas/ns2_remover_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
:schema => {
"$schema" => "http://www.archivesspace.org/archivesspace.json",
"version" => 1,
"type" => "object",

"properties" => {

"dry_run" => {
"type" => "boolean",
"default" => false
}

}
}
}
4 changes: 4 additions & 0 deletions common/spec/lib/factory_bot_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,10 @@ def few_or_none(key)
{}
end

factory :json_ns2_remover_job, class: JSONModel(:ns2_remover_job) do
dry_run { false }
end

factory :json_group, class: JSONModel(:group) do
group_code { generate(:alphanumstr) }
description { generate(:generic_description) }
Expand Down
4 changes: 2 additions & 2 deletions frontend/app/helpers/resolver_helper.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module ResolverHelper

def resolve_readonly_link_to(label, uri)
link_to label, :controller => :resolver, :action => :resolve_readonly, :uri => uri
def resolve_readonly_link_to(label, uri, active = true)
link_to_if active, label, :controller => :resolver, :action => :resolve_readonly, :uri => uri
end


Expand Down
12 changes: 11 additions & 1 deletion frontend/app/views/jobs/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@
<br/>
<% end %>

<% define_template("ns2_remover_job", jsonmodel_definition(:ns2_remover_job)) do |form| %>
<p><%= I18n.t("ns2_remover_job.description") %></p>

<div class="col-sm-9 checkbox">
<%= form.label_and_boolean "dry_run" %>
</div>
<br/>
<br/>
<% end %>

<% define_template("job", jsonmodel_definition(:job)) do |form| %>

<input id="job_type" name="job[job_type]" type="hidden" value="<%= params['job_type'] %>">
Expand Down Expand Up @@ -186,7 +196,7 @@

<%# Now create a template for all job types not handled above - eg from plugins %>
<% job_types.keys.each do |type| %>
<% next if ['container_labels_job', 'find_and_replace_job', 'print_to_pdf_job', 'import_job', 'report_job', 'generate_slugs_job', 'generate_arks_job', 'bulk_import_job'].include?(type) %>
<% next if ['container_labels_job', 'find_and_replace_job', 'print_to_pdf_job', 'import_job', 'report_job', 'generate_slugs_job', 'generate_arks_job', 'ns2_remover_job', 'bulk_import_job'].include?(type) %>

<% define_template(type, jsonmodel_definition(type.intern)) do |form| %>

Expand Down
12 changes: 11 additions & 1 deletion frontend/app/views/jobs/_job_records.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,17 @@
end
%>
<div>
<span class="asicon icon-<%= result["record"]["_resolved"]["jsonmodel_type"] %>" title="<%= I18n.t("#{result["record"]["_resolved"]["jsonmodel_type"]}._singular") %>"></span> <%= resolve_readonly_link_to record_title, result["record"]["ref"] %>
<span class="asicon icon-<%= result["record"]["_resolved"]["jsonmodel_type"] %>"
title="<%= I18n.t("#{result["record"]["_resolved"]["jsonmodel_type"]}._singular") %>">
</span>
<% if result["record"]["ref"].include?(current_repo.uri) %>
<%= resolve_readonly_link_to record_title, result["record"]["ref"] %>
<% else %>
<%= resolve_readonly_link_to "#{record_title} (#{result["record"]["ref"]})", result["record"]["ref"], false %>
<span class="label label-warning">
<%= I18n.t("job._frontend.external_repo") %>
</span>
<% end %>
</div>
<% end %>
</div>
Expand Down
1 change: 1 addition & 0 deletions frontend/config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1400,6 +1400,7 @@ en:
job:
_frontend:
audit_data: Audit Data
external_repo: Record belongs to a different repository
actions:
create: Create Job
save: Start Job
Expand Down
35 changes: 35 additions & 0 deletions frontend/spec/helpers/resolver_helper_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

require 'spec_helper'
require 'rails_helper'

describe ResolverHelper do
let(:url) { '/repositories/2/resources/1' }
let(:label) { 'A Test Link' }

describe '#resolve_readonly_link_to' do

it 'generates a readonly link' do
link = resolve_readonly_link_to(label, url)

expect(link).to have_link(label,
href: '/resolve/readonly?uri=%2Frepositories%2F2%2Fresources%2F1')
end

it 'does not generate a link if active is false' do
inactive_link = resolve_readonly_link_to(label, url, false)

expect(inactive_link).not_to have_link(label)
expect(inactive_link).to have_text(label)
end
end

describe '#resolve_edit_link_to' do

it 'generates an edit link' do
link = resolve_edit_link_to(label, url)
expect(link).to have_link(label,
href: '/resolve/edit?uri=%2Frepositories%2F2%2Fresources%2F1')
end
end
end