Skip to content

Commit

Permalink
Merge pull request #2384 from samvera/i286-add-per-tenant-bulkrax-fie…
Browse files Browse the repository at this point in the history
…ld-mapping-editor

Add per-tenant Bulkrax field mappings
  • Loading branch information
bkiahstroud authored Nov 21, 2024
2 parents 2ea73b9 + 7802691 commit 73214ba
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 5 deletions.
3 changes: 2 additions & 1 deletion app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@
//= require hyrax

//= require codemirror
//= require codemirror-autorefresh
//= require codemirror/modes/css
//= require codemirror/modes/javascript
//= require codemirror-autorefresh

//= require iiif_print

Expand Down
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*= require bulkrax/application
*= require fontselect
*= require codemirror
*= require codemirror/themes/neat
*= require codemirror-theme
*= require hyrax
*= require dataTables.bootstrap4
Expand Down
25 changes: 22 additions & 3 deletions app/models/concerns/account_settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module AccountSettings
extend ActiveSupport::Concern
# rubocop:disable Metrics/BlockLength
included do
cattr_accessor :array_settings, :boolean_settings, :hash_settings, :string_settings, :private_settings do
cattr_accessor :array_settings, :boolean_settings, :hash_settings, :json_editor_settings, :string_settings, :private_settings do
[]
end
cattr_accessor :all_settings do
Expand All @@ -23,6 +23,7 @@ module AccountSettings
setting :allow_downloads, type: 'boolean', default: true
setting :allow_signup, type: 'boolean', default: true
setting :analytics_provider, type: 'string'
setting :bulkrax_field_mappings, type: 'json_editor', default: Bulkrax.field_mappings.to_json
setting :bulkrax_validations, type: 'boolean', disabled: true
setting :cache_api, type: 'boolean', default: false
setting :contact_email, type: 'string', default: 'change-me-in-settings@example.com'
Expand Down Expand Up @@ -58,7 +59,7 @@ module AccountSettings
validates :contact_email, :oai_admin_email,
format: { with: URI::MailTo::EMAIL_REGEXP },
allow_blank: true
validate :validate_email_format, :validate_contact_emails
validate :validate_email_format, :validate_contact_emails, :validate_json
validates :google_analytics_id,
format: { with: /((UA|YT|MO)-\d+-\d+|G-[A-Z0-9]{10})/i },
allow_blank: true
Expand All @@ -70,7 +71,7 @@ module AccountSettings
# rubocop:disable Metrics/BlockLength
class_methods do
def setting(name, args)
known_type = ['array', 'boolean', 'hash', 'string'].include?(args[:type])
known_type = ['array', 'boolean', 'hash', 'string', 'json_editor'].include?(args[:type])
raise "Setting type #{args[:type]} is not supported. Can not laod." unless known_type

send("#{args[:type]}_settings") << name
Expand Down Expand Up @@ -136,6 +137,12 @@ def set_type(value, to_type)
value.is_a?(String) ? JSON.parse(value) : value
when 'string'
value.to_s
when 'json_editor'
begin
JSON.pretty_generate(JSON.parse(value))
rescue JSON::ParserError
value
end
end
end

Expand All @@ -155,6 +162,18 @@ def validate_contact_emails
end
end

def validate_json
json_editor_settings.each do |key|
next if settings[key].blank?

begin
JSON.parse(settings[key])
rescue JSON::ParserError => e
errors.add(:"#{key}", e.message)
end
end
end

def initialize_settings
return true unless self.class.column_names.include?('settings')
set_smtp_settings
Expand Down
2 changes: 1 addition & 1 deletion app/views/proprietor/accounts/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</div>

<h3><%= t(".account_settings") %></h3>
<% current_account.live_settings.each do |key, value| %>
<% @account.live_settings.each do |key, value| %>
<%= render 'shared/settings', f: f, key: key, value: value %>
<% end %>
Expand Down
23 changes: 23 additions & 0 deletions app/views/shared/_settings.html.erb
Original file line number Diff line number Diff line change
@@ -1,8 +1,31 @@
<% if value[:type] == 'array' %>
<%# FIXME:
I think current_account here should be @account. @account is present both within the tenant
as well as in the proprietor views, while current_account is only present within a tenant.
Changing it to @account causes a spec in spec/features/accounts_spec.rb to fail, I think due
to the form sending inputs that should be treated as arrays as strings.
@see AccountSettings#validate_email_format
%>
<% current_account.send(key).each do |sub_value| %>
<%= f.input key, value: sub_value %>
<% end %>
<% elsif value[:type] == 'hash' %>
<% elsif value[:type] == 'json_editor' %>
<%= f.input key, as: :text, required: false, input_html: { value: @account.send(key) }, label: key.to_s.titleize %>
<script>
document.addEventListener('turbolinks:load', () => {
const input_textarea = document.getElementById('<%= "account_#{key}" %>')
if (input_textarea) {
CodeMirror.fromTextArea(input_textarea, {
mode: 'application/json',
autofocus: true,
lineNumbers: true,
theme: 'neat',
autoRefresh: true
});
};
});
</script>
<% else %>
<%= f.input key, as: value[:type] %>
<% end %>
1 change: 1 addition & 0 deletions config/initializers/assets.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
# Rails.application.config.assets.precompile += %w( search.js )
Rails.application.config.assets.precompile += ['codemirror*', 'codemirror/**/*']
14 changes: 14 additions & 0 deletions lib/bulkrax/bulkrax_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

module PerTenantFieldMappings
# OVERRIDE: [Bulkrax v8.2.0] Use tenant-specific field mappings if present
def field_mappings
if Site.account.present? && Site.account.bulkrax_field_mappings.present?
JSON.parse(Site.account.bulkrax_field_mappings).with_indifferent_access
else
super
end
end
end

Bulkrax.singleton_class.send(:prepend, PerTenantFieldMappings)
34 changes: 34 additions & 0 deletions spec/lib/bulkrax/bulkrax_decorator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

RSpec.describe PerTenantFieldMappings, type: :decorator do
before do
allow(Site).to receive(:account).and_return(account)
end

context 'when the current Account does not have any tenant-specific field mappings' do
let(:account) { build(:account) }

it "returns Bulkrax's default field mappings" do
default_bulkrax_mapping_keys = ['Bulkrax::OaiDcParser', 'Bulkrax::OaiQualifiedDcParser', 'Bulkrax::CsvParser', 'Bulkrax::BagitParser', 'Bulkrax::XmlParser']

expect(Site.account.settings['bulkrax_field_mappings']).to be_nil
expect(Bulkrax.field_mappings).to be_a(Hash)
expect(Bulkrax.field_mappings.keys.sort).to eq(default_bulkrax_mapping_keys.sort)
end
end

context 'when the current Account has tenant-specific field mappings' do
let(:account) { build(:account, settings: { bulkrax_field_mappings: field_mapping_json }) }
let(:field_mapping_json) do
{
'Bulkrax::CsvParser' => {
'fake_field' => { from: %w[fake_column], split: /\s*[|]\s*/ }
}
}.to_json
end

it "returns the tenant's custom field mappings" do
expect(Bulkrax.field_mappings).to eq(JSON.parse(Site.account.bulkrax_field_mappings))
end
end
end
63 changes: 63 additions & 0 deletions spec/models/concerns/account_settings_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
expect(account.public_settings(is_superadmin: true).keys.sort).to eq %i[allow_downloads
allow_signup
analytics_provider
bulkrax_field_mappings
cache_api
contact_email
contact_email_to
Expand Down Expand Up @@ -49,4 +50,66 @@
end
end
end

describe '#bulkrax_field_mappings' do
context 'when the setting is blank' do
it 'returns the default field mappings configured in Bulkrax' do
expect(account.settings['bulkrax_field_mappings']).to be_nil
# For parity, parse Bulkrax field mappings from JSON. #to_json will stringify keys as
# well as turn a regex like /\|/ into (?-mix:\\|)
default_bulkrax_mappings = JSON.parse(Bulkrax.field_mappings.to_json)
default_tenant_mappings = JSON.parse(account.bulkrax_field_mappings)

expect(default_tenant_mappings).to eq(default_bulkrax_mappings)
end
end

context 'when the setting is present' do
let(:account) { build(:account, settings: { bulkrax_field_mappings: setting_value }) }

context 'when the value is valid JSON' do
let(:setting_value) do
{
'Bulkrax::CsvParser' => {
'fake_field' => { from: %w[fake_column], split: /\s*[|]\s*/ }
}
}.to_json
end

it 'parses the JSON into a Hash and prints it as pretty JSON' do
expect(account.bulkrax_field_mappings)
.to eq(JSON.pretty_generate(JSON.parse(setting_value)))
end
end

context 'when the value is not valid JSON' do
let(:setting_value) { 'hello world' }

it 'returns the raw value' do
expect(account.bulkrax_field_mappings).to eq(setting_value)
end
end
end
end

describe '#validate_json' do
let(:account) { build(:account, settings: { bulkrax_field_mappings: setting_value }) }

context 'when a "json_editor" setting is valid JSON' do
let(:setting_value) { { a: 'b' }.to_json }

it 'does not error' do
expect(account.valid?).to eq(true)
end
end

context 'when a "json_editor" setting is not valid JSON' do
let(:setting_value) { 'hello world' }

it 'adds an error to the setting' do
expect(account.valid?).to eq(false)
expect(account.errors.messages[:bulkrax_field_mappings]).to eq(["unexpected token at 'hello world'"])
end
end
end
end

0 comments on commit 73214ba

Please sign in to comment.