-
Notifications
You must be signed in to change notification settings - Fork 54
Add ROR Support for Institutions #2135
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
Merged
Merged
Changes from all commits
Commits
Show all changes
75 commits
Select commit
Hold shift + click to select a range
8d0b429
add a new column `ror_id` to the `institutions` table.
whomingbird 0d3ce74
be able to enter ROR ID in UI and save it
whomingbird c413060
display ROR id as a clickable link
whomingbird b12edcb
add ror related javascript
whomingbird 29963a1
add ror related css
whomingbird 0dc8740
Add ROR support: Enable users to type an institution name and see a s…
whomingbird 6166a48
validate ROR ID
whomingbird 8783b19
fetch institution metadata via ROR ID
whomingbird bb9d5b5
add test
whomingbird dba83bf
add test for institution model
whomingbird 6438f03
Simplify the form view for entering institution data.
whomingbird e524b00
better UI: add more informative text and related links
whomingbird 6940737
improve the error response message when ROR ID is invalid
whomingbird 76170fe
convert country code to country name
whomingbird e59942b
mini twist
whomingbird d5cc2f3
add `ror_id` into institution_serializer
whomingbird ed349c3
add `ror_id` into institution typehead
whomingbird 52fe079
Add ROR support for project creation: Implement local and remote inst…
whomingbird 9f7b717
Add `ror_id` attribute to Institution Read/Write API
whomingbird 17191c9
update API examples for the new attribute `ror_id` attribute
whomingbird 4b07a17
fix the javascript to enable/disable "submit" button
whomingbird ef4fb71
add more help text
whomingbird 411c9fe
remove unused javascript
whomingbird aff30dc
improve CSS
whomingbird 81dba73
more javascript twist
whomingbird bf3dacb
enable to save ror_id when creating an institution along with a project
whomingbird ef485f0
disable the input fields when the institution metadata are loaded loc…
whomingbird ba5f0ed
fix test
whomingbird 60bbdb5
disable the input fields when insitution is selected from locally sav…
whomingbird ac3807f
remove the typo
whomingbird 9dea67d
add "clear-fields" button when creating institutions
whomingbird 36f9f2b
Rewrite the toggleUserInput method to disable input fields when insti…
whomingbird 5fc578c
ror_id should be unique.
whomingbird 2bf6b5c
when editing institution, if ror ID exists, inputs fields should be d…
whomingbird a0e76d2
bug fix for the readonly fields
whomingbird 38ae0a6
remove unnecessary field 'institution_name'
whomingbird 50059ae
update checkSubmitButtonEnabled() function
whomingbird 826e822
bug fix #2123
whomingbird 92de4b9
minor javascript fix
whomingbird 5ad6f0d
Merge branch 'main' into add-ror-support
whomingbird 910c6c7
bug fix: the new institution country is not saved
whomingbird 4e3f857
find the existing institutions by title and ror id
whomingbird d184613
remove "new_institution_reminder"
whomingbird 2043078
bug fix for showing ROR ID link
whomingbird 9c46f93
add tests for administer create request project with an existing inst…
whomingbird 15c2afa
update "request create" project by not_admin and add tests
whomingbird 669fbd9
mini bug
whomingbird 93ffa5e
update according to the PR review
whomingbird 2288a69
use select dropdown for country instead of free text
whomingbird 22a912a
accept "query" as a parameter and dynamically fetch data based on use…
whomingbird d587587
fix response json
whomingbird 5310c79
remove debug log
whomingbird e0ce5ce
does not require css
whomingbird 7f7fdec
remove old => syntax
whomingbird 2f7d1e8
add validator for ror_id to check if ID matches any existing ROR orga…
whomingbird 2951e06
fix tests
whomingbird 4dccf95
fix institution api tests
whomingbird 8620b17
fix more institution related tests
whomingbird 04cbde3
use bootstrap sass variables
whomingbird 35adcc5
Refactor ROR API query to route through server to protect user IP
whomingbird 13759f3
rename ROR client
whomingbird 99c8191
rename Ror client
whomingbird ae94cba
use the routes helper method in case of suburl
whomingbird 61e3ade
Add fetch_ror_details to retrieve all ROR metadata before validation.…
whomingbird 245a202
valid institution when creating it through project
whomingbird 25d6653
add tests for ror client
whomingbird 2a2692f
add tests for invalid id or name
whomingbird b2871e3
fix tests
whomingbird 2b02352
refactoring ror_mock
whomingbird b4c82a4
Validate institution before triggering logs or emails in request_crea…
whomingbird b4f8406
add functional tests for "ror_search"
whomingbird fce5321
add tests to make sure that fetch_ror_details is called before valida…
whomingbird 3542193
remove duplicated ror_id validation
whomingbird 2491dc5
merge with main
whomingbird ebcfa33
update schema.rb because Rails 7.2.2.1 does not recognize :size as a …
whomingbird File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
276 changes: 276 additions & 0 deletions
276
app/assets/javascripts/institution-ror-typeahead.js.erb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,276 @@ | ||
<% environment.context_class.instance_eval { include Seek::Util.routes } %> | ||
|
||
function toggleUserInput(disabled) { | ||
const action = disabled ? 'addClass' : 'removeClass'; | ||
const elements = [ | ||
'#institution_title', | ||
'#institution_city', | ||
'#institution_ror_id', | ||
'#institution_web_page', | ||
'.tt-input' | ||
]; | ||
|
||
elements.forEach(selector => { | ||
$j(selector)[action]('institution-input-disable'); | ||
$j(selector).prop("readonly", disabled); | ||
$j('#institution_country').prop('disabled', disabled); | ||
}); | ||
} | ||
|
||
function extractRorId(rorUrl) { | ||
const regex = /https:\/\/ror\.org\/([^\/]+)/; | ||
const match = rorUrl.match(regex); | ||
if (match) { | ||
return match[1]; | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
function fetchRorData(rorId) { | ||
|
||
//use /institutions/ror_search endpoint instead of the direct ROR API | ||
var url = '<%= ror_search_institutions_path %>?ror_id=' + encodeURIComponent(rorId); | ||
|
||
fetch(url) | ||
.then(response => { | ||
if (!response.ok) { | ||
return response.json().then(err => { | ||
throw new Error(err.error || "Unknown error occurred"); | ||
}); | ||
} | ||
return response.json(); | ||
}) | ||
.then(data => { | ||
if (!data || data.error) { | ||
throw new Error(data.error || "Invalid response from server"); | ||
} | ||
|
||
$j('#ror-response').html(JSON.stringify(data, null, 4)); | ||
$j('#institution_title').val(data.name || 'N/A'); | ||
$j('#institution_city').val(data.addresses?.[0]?.city || 'N/A'); | ||
$j('#institution_country').val(data.country?.country_code || 'N/A'); | ||
$j('#institution_ror_id').val(extractRorId(data.id) || ''); | ||
$j('#institution_web_page').val(data.links?.[0] || 'N/A'); | ||
$j('#ror-error-message').text('').hide(); | ||
$j('#institution_ror_id').removeClass("field_with_errors"); | ||
$j("#ror-error-message").closest(".form-group").removeClass("field_with_errors"); | ||
toggleUserInput(true); | ||
}) | ||
.catch(error => { | ||
$j('#ror-error-message').text(error.message).show(); | ||
$j('#institution_ror_id').addClass("field_with_errors"); | ||
$j("#ror-error-message").closest(".form-group").addClass("field_with_errors"); | ||
}); | ||
} | ||
|
||
// ROR API source logic | ||
function rorQuerySource(query, processSync, processAsync) { | ||
if (query.length < 4) { | ||
return processAsync([]); | ||
} | ||
|
||
//use /institutions/ror_search endpoint instead of the direct ROR API | ||
var url = '<%= ror_search_institutions_path %>?query=' + encodeURIComponent(query); | ||
return $j.ajax({ | ||
url: url, | ||
type: 'GET', | ||
dataType: 'json', | ||
success: function (json) { | ||
const orgs = json.items || []; | ||
return processAsync(orgs); | ||
}, | ||
error: function (xhr, status, error) { | ||
processAsync([]); | ||
} | ||
}); | ||
} | ||
|
||
// Template for Local Institution suggestions | ||
function localSuggestionTemplate(data) { | ||
return ` | ||
<div> | ||
<strong>${data.text}</strong> | ||
<small>${data.hint || ''}</small> | ||
</div>`; | ||
} | ||
|
||
// Template for ROR suggestions | ||
function rorSuggestionTemplate(data) { | ||
var altNames = ""; | ||
if (data.aliases.length > 0) { | ||
altNames += data.aliases.join(", ") + ", "; | ||
} | ||
if (data.acronyms.length > 0) { | ||
altNames += data.acronyms.join(", ") + ", "; | ||
} | ||
if (data.labels.length > 0) { | ||
data.labels.forEach(label => { | ||
altNames += label.label + ", "; | ||
}); | ||
} | ||
altNames = altNames.replace(/,\s*$/, ""); | ||
return ` | ||
<div> | ||
<p> | ||
${data.name}<br> | ||
<small>${data.types[0]}, ${data.country.country_code}<br> | ||
<i>${altNames}</i></small> | ||
</p> | ||
</div>`; | ||
} | ||
|
||
|
||
|
||
function initializeLocalInstitutions(query = '', cache = false) { | ||
const url = `<%= typeahead_institutions_path %>.json?q=${encodeURIComponent(query)}`; | ||
|
||
return new Bloodhound({ | ||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('text'), | ||
queryTokenizer: Bloodhound.tokenizers.whitespace, | ||
remote: { | ||
url: url, | ||
wildcard: '%QUERY', | ||
cache: cache, | ||
transform: response => response.results | ||
} | ||
}); | ||
} | ||
|
||
function clearInstitutionFields() { | ||
$j('#institution_title').val(''); | ||
$j('#institution_id').val(''); | ||
$j('#institution_ror_id').val(''); | ||
$j('#institution_city').val(''); | ||
$j('#institution_country').val(''); | ||
$j('#institution_web_page').val(''); | ||
$j('#institution_address').val(''); | ||
} | ||
|
||
|
||
$j(document).ready(function () { | ||
var $j = jQuery.noConflict(); | ||
|
||
$j('#fetch-ror-data-with-id').on('click', function () { | ||
fetchRorData($j('#institution_ror_id').val()); | ||
}); | ||
|
||
// if the institution title is not selected from the local list or ROR, but entered manually by user | ||
$j('#institution_title').on('change', function () { | ||
const inputValue = $j(this).val(); | ||
$j('#institution_title').val(inputValue); | ||
checkSubmitButtonEnabled(); | ||
}); | ||
|
||
|
||
$j('#combined_typeahead .typeahead').typeahead( | ||
{ | ||
hint: true, | ||
highlight: true, | ||
minLength: 4 | ||
}, | ||
// First Dataset: Local Institutions | ||
{ | ||
name: 'institutions', | ||
display: 'text', // Display the 'text' field in the dropdown | ||
source: function (query, syncResults, asyncResults) { | ||
const bloodhound = initializeLocalInstitutions(query); | ||
bloodhound.search(query, syncResults, asyncResults); | ||
}, // Local data source | ||
templates: { | ||
header: '<div class="league-name">Institutions saved locally</div>', | ||
suggestion: localSuggestionTemplate | ||
} | ||
}, | ||
// Second Dataset: Remote ROR Query | ||
{ | ||
name: 'ror-query', | ||
limit: 50, | ||
async: true, | ||
source: rorQuerySource, | ||
templates: { | ||
header: '<div class="league-name">Institutions fetched from ROR</div>', | ||
pending: '<div class="empty-message">Fetching from ROR API ...</div>', | ||
suggestion: rorSuggestionTemplate | ||
}, | ||
display: function (data) { | ||
return data.name; | ||
}, | ||
value: function (data) { | ||
return data.identifier; | ||
} | ||
} | ||
); | ||
|
||
|
||
$j('#ror_query_name .typeahead').typeahead({ | ||
hint: true, | ||
highlight: true, | ||
minLength: 3 | ||
}, | ||
{ | ||
limit: 50, | ||
async: true, | ||
source: rorQuerySource, | ||
templates: { | ||
pending: [ | ||
'<div class="empty-message">', | ||
'Fetching list ...', | ||
'</div>' | ||
].join('\n'), | ||
suggestion: rorSuggestionTemplate | ||
}, | ||
display: function (data) { | ||
return data.name; | ||
}, | ||
value: function (data) { | ||
return data.identifier; | ||
} | ||
}); | ||
|
||
$j('#combined_typeahead .typeahead').bind('typeahead:select', function (ev, data) { | ||
$j('#combined_typeahead .typeahead').typeahead('close'); | ||
|
||
if (data.hasOwnProperty("text")) { | ||
$j('#institution_title').val(data.text); | ||
$j('#institution_id').val(data.id); | ||
$j('#institution_ror_id').val(data.ror_id); | ||
$j('#institution_city').val(data.city); | ||
$j('#institution_country').val(data.country); | ||
$j('#institution_web_page').val(data.web_page); | ||
} | ||
else | ||
{ | ||
$j('#institution_title').val(data.name); | ||
$j('#institution_ror_id').val(data.id); | ||
$j('#institution_city').val(data.addresses[0]['city']); | ||
$j('#institution_country').val(data.country.country_code); | ||
$j('#institution_ror_id').val(extractRorId(data.id)); | ||
$j('#institution_web_page').val(data.links[0]); | ||
} | ||
toggleUserInput(true); | ||
checkSubmitButtonEnabled(); | ||
}); | ||
|
||
$j('#ror_query_name .typeahead').bind('typeahead:select', function (ev, suggestion) { | ||
$j('#ror-response').html(JSON.stringify(suggestion, undefined, 4)); | ||
$j('#institution_city').val(suggestion.addresses[0]['city']); | ||
$j('#institution_country').val(suggestion.country.country_code); | ||
$j('#institution_ror_id').val(extractRorId(suggestion.id)); | ||
$j('#institution_web_page').val(suggestion.links[0]); | ||
toggleUserInput(true); | ||
}); | ||
|
||
$j('#clear-fields').on('click', function(event) { | ||
event.preventDefault(); | ||
clearInstitutionFields(); | ||
toggleUserInput(false); | ||
checkSubmitButtonEnabled(); | ||
}); | ||
|
||
|
||
if ($j('#institution_ror_id').val()!== '') { | ||
toggleUserInput(true); | ||
} | ||
|
||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
@import 'bootstrap/variables'; | ||
|
||
.institution-input-disable { | ||
background-color: $input-bg-disabled !important; | ||
cursor: not-allowed; | ||
} | ||
|
||
.tt-menu { | ||
width: 422px; | ||
margin: 12px 0; | ||
padding: 8px 0; | ||
background-color: $input-bg; | ||
border: 1px solid $gray-base; | ||
max-height: 400px; | ||
overflow-y: auto; | ||
border-radius: $border-radius-large; | ||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); | ||
} | ||
|
||
.league-name { | ||
margin: 0 20px 5px 0; | ||
padding: 3px 6px; | ||
background-color: $btn-default-border; | ||
transition: background-color 0.5s ease-in-out; | ||
} | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.