Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions app/controllers/contributors_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,16 @@ def translate_roles(hash:)
def process_org(hash:)
return hash unless hash.present? && hash[:org_id].present?

allow = !Rails.configuration.x.application.restrict_orgs
org = org_from_params(params_in: hash,
allow_create: allow)
org = org_from_params(params_in: hash, allow_create: true)

if org.nil?
flash[:alert] =
_('Contributor saved without affiliation. If you intended to add an affiliation, please check ' \
'if the organisation appears in the list in a different form.')
end

hash = remove_org_selection_params(params_in: hash)

return hash if org.blank? && !allow
return hash unless org.present?

hash[:org_id] = org.id
Expand Down
13 changes: 12 additions & 1 deletion app/javascript/src/utils/autoComplete.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'jquery-ui/ui/widgets/autocomplete';
import getConstant from './constants';
import { isObject, isString, isArray } from './isType';
import debounce from '../utils/debounce';

// Updates the ARIA help text that lets the user know how many suggestions
const updateAriaHelper = (autocomplete, suggestionCount) => {
Expand Down Expand Up @@ -94,6 +95,12 @@ const toggleWarning = (autocomplete, displayIt) => {
}
};

// Delayed warning display (fires only after typing pauses)
const debouncedToggleWarning = debounce((autocomplete, displayIt) => {
toggleWarning(autocomplete, displayIt);
}, 1000);


// Looks up the value in the crosswalk
const findInCrosswalk = (selection, crosswalk) => {
// Default to the name only
Expand Down Expand Up @@ -123,7 +130,11 @@ const warnableSelection = (selection) => {
const handleSelection = (autocomplete, hidden, crosswalk, selection) => {
const out = findInCrosswalk(selection, crosswalk);

toggleWarning(autocomplete, warnableSelection(out));
// When user types, 'displayIt = false' hides warning initially
toggleWarning(autocomplete, false);

// After user pauses for 1 second, show warning
debouncedToggleWarning(autocomplete, warnableSelection(out));

// Set the ID and trigger the onChange event for any view specific
// JS to trigger events
Expand Down
21 changes: 16 additions & 5 deletions app/services/org_selection/hash_to_org_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ def to_identifiers(hash:)

private

def match_hash_to_ror_org(hash:)
return nil unless hash[:ror].present?

ror_results = OrgSelection::SearchService.search_externally(search_term: hash[:name])
ror_results&.find { |r| r[:ror] == hash[:ror] }
end

# Lookup the Org by it's :id and return if the name matches the search
def lookup_org_by_id(hash:)
org = Org.where(id: hash[:id]).first if hash[:id].present?
Expand Down Expand Up @@ -92,14 +99,18 @@ def lookup_org_by_name(hash:)
def initialize_org(hash:)
return nil unless hash.present? && hash[:name].present?

# Attempt to find an ROR match to the hash
ror_hash = match_hash_to_ror_org(hash: hash)
return nil unless ror_hash

Org.new(
name: hash[:name],
links: links_from_hash(name: hash[:name], website: hash[:url]),
language: language_from_hash(hash: hash),
target_url: hash[:url],
name: ror_hash[:name],
links: links_from_hash(name: ror_hash[:name], website: ror_hash[:url]),
language: language_from_hash(hash: ror_hash),
target_url: ror_hash[:url],
institution: true,
is_other: false,
abbreviation: abbreviation_from_hash(hash: hash)
abbreviation: abbreviation_from_hash(hash: ror_hash)
)
end

Expand Down
3 changes: 1 addition & 2 deletions app/views/contributors/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,8 @@ roles_tooltip = _("Select each role that applies to the contributor.")

<div class="form-group row" id="contributor-org-controls"><!-- org -->
<div class="col-md-8">
<%= render partial: org_partial,
<%= render partial:'shared/org_selectors/combined',
locals: { form: form,
orgs: orgs,
default_org: contributor.org,
required: false,
label: _("Affiliation") } %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/shared/org_selectors/_combined.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,5 @@ placeholder = _("Begin typing to see a list of suggestions.")
class: "autocomplete-result" %>

<p class="autocomplete-warning red hide">
<%= _("A new entry will be created for the organisation you have named above. Please double check that your organisation does not appear in the list in a slightly different form.").html_safe %>
<%= _("Please double check that your organisation does not appear in the list in a slightly different form.").html_safe %>
</p>
10 changes: 10 additions & 0 deletions spec/controllers/contributors_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@
end

describe '#process_org(hash:)' do
before do
@request = ActionDispatch::TestRequest.create
@response = ActionDispatch::TestResponse.create
@controller.request = @request
@controller.response = @response
end
it 'returns the hash as is if no :org_id is present' do
@params_hash[:contributor].delete(:org_id)
hash = @controller.send(:process_org, hash: @params_hash[:contributor])
Expand All @@ -159,6 +165,10 @@
end
it 'sets the org_id to the idea of the org' do
new_org = create(:org)
# Clear name, id, and ror to prevent matching any existing org
@params_hash[:contributor][:org_name] = nil
@params_hash[:contributor][:org_id] = { id: nil, name: nil, ror: nil }.to_json

@controller.stubs(:org_from_params).returns(new_org)
hash = @controller.send(:process_org, hash: @params_hash[:contributor])
expect(hash[:org_id]).to eql(new_org.id)
Expand Down
107 changes: 105 additions & 2 deletions spec/services/org_selection/hash_to_org_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,63 @@
org = create(:org, name: @name)
expect(described_class.to_org(hash: @hash)).to eql(org)
end
it 'returns a new Org instance' do
expect(described_class.to_org(hash: @hash).new_record?).to eql(true)
it 'returns a new Org instance when no existing DB matches exist but an ROR match does' do
ror_id = Faker::Alphanumeric.alphanumeric(number: 9)

hash_with_ror = @hash.merge(ror: ror_id)

# heartbeat
stub_request(
:get,
"#{ExternalApis::RorService.api_base_url}#{ExternalApis::RorService.heartbeat_path}"
).with(headers: ExternalApis::RorService.headers)
.to_return(status: 200, body: '', headers: {})

# search
search_uri =
"#{ExternalApis::RorService.api_base_url}" \
"#{ExternalApis::RorService.search_path}" \
"?page=1&query=#{URI.encode_www_form_component(hash_with_ror[:name])}"

ror_response = {
number_of_results: 1,
time_taken: 1,
items: [
{
id: ror_id,
names: [
{ value: hash_with_ror[:name], types: ['ror_display'] },
{ value: hash_with_ror[:abbreviation], types: ['acronym'] }
],
links: [{ type: 'website', value: hash_with_ror[:url] }],
types: ['Education'],
status: 'active',
locations: [
{
geonames_id: 123,
geonames_details: {
country_name: 'United States',
country_code: 'US'
}
}
],
external_ids: []
}
]
}

stub_request(:get, search_uri)
.with(headers: ExternalApis::RorService.headers)
.to_return(
status: 200,
body: ror_response.to_json,
headers: { 'Content-Type' => 'application/json' }
)

org = described_class.to_org(hash: hash_with_ror)

expect(org).not_to be_nil
expect(org).to be_new_record
end
end

Expand Down Expand Up @@ -84,6 +139,39 @@
end

context 'private methods' do
describe '#match_hash_to_ror_org' do
it 'returns nil if no ROR results are found' do
OrgSelection::SearchService.stubs(:search_externally)
.with(search_term: @hash[:name])
.returns([])

result = described_class.send(:match_hash_to_ror_org, hash: @hash)
expect(result).to be_nil
end

it 'returns the ROR-matching result when ror is present' do
ror_id = Faker::Alphanumeric.alphanumeric(number: 9)

ror_results = [{
ror: ror_id,
name: "#{@name} (#{@abbrev})",
sort_name: @name,
url: @url,
language: @lang.abbreviation,
fundref: '',
abbreviation: @abbrev,
score: @hash[:score],
weight: @hash[:weight]
}]

OrgSelection::SearchService.stubs(:search_externally).returns(ror_results)
hash_with_ror = @hash.merge(ror: ror_id)
result = described_class.send(:match_hash_to_ror_org, hash: hash_with_ror)

expect(result).to eq(ror_results.first)
end
end

describe '#initialize_org(hash:)' do
it 'returns nil if the hash is nil' do
rslt = described_class.send(:initialize_org, hash: nil)
Expand All @@ -95,6 +183,21 @@
expect(rslt).to eql(nil)
end
it 'returns a new instance of Org' do
fake_ror = {
ror: Faker::Alphanumeric.alphanumeric(number: 9),
name: "#{@name} (#{@abbrev})",
sort_name: @name,
url: @url,
language: @lang.abbreviation,
fundref: '',
abbreviation: @abbrev,
score: @hash[:score],
weight: @hash[:weight]
}
# Stub the ROR matcher to return the fake ROR hash
# As valid ROR hash is needed for new org creation
described_class.stubs(:match_hash_to_ror_org).returns(fake_ror)

rslt = described_class.send(:initialize_org, hash: @hash)
nm = "#{@name} (#{@abbrev})"
lnks = JSON.parse({ org: [{ link: @url, text: nm }] }.to_json)
Expand Down