-
Notifications
You must be signed in to change notification settings - Fork 52
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
Add ROR Support for Institutions #2135
Open
whomingbird
wants to merge
50
commits into
main
Choose a base branch
from
add-ror-support
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 46 commits
Commits
Show all changes
50 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 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 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 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,272 @@ | ||
var ROR_API_URL = "https://api.ror.org/organizations"; | ||
|
||
|
||
function toggleUserInput(disabled) { | ||
const action = disabled ? 'addClass' : 'removeClass'; | ||
const elements = [ | ||
'#institution_title', | ||
'#institution_city', | ||
'#institution_country', | ||
'#institution_ror_id', | ||
'#institution_web_page', | ||
'.tt-input' | ||
]; | ||
|
||
elements.forEach(selector => { | ||
$j(selector)[action]('institution-input-disable'); | ||
$j(selector).prop("readonly", 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) { | ||
var url = ROR_API_URL + '/' + rorId; | ||
console.log(url); | ||
fetch(url) | ||
.then(response => { | ||
if (!response.ok) { | ||
return response.json().then(err => { | ||
throw new Error(err.errors ? err.errors[0] : "Unknown error occurred"); | ||
}); | ||
} | ||
return response.json(); | ||
}) | ||
.then(data => { | ||
$j('#ror-response').html(JSON.stringify(data, undefined, 4)); | ||
$j('#institution_title').val(data.name); | ||
$j('#institution_city').val(data.addresses[0]['city']); | ||
$j('#institution_country').val(data.country.country_name); | ||
$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 < 3) { | ||
return processAsync([]); // Trigger only for 3+ characters | ||
} | ||
var url = ROR_API_URL + '?query=' + encodeURIComponent(query); | ||
return $j.ajax({ | ||
url: url, | ||
type: 'GET', | ||
dataType: 'json', | ||
success: function (json) { | ||
const orgs = json.items; | ||
return processAsync(orgs); | ||
} | ||
}); | ||
} | ||
|
||
// 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*$/, ""); // Trim trailing comma | ||
return ` | ||
<div> | ||
<p> | ||
${data.name}<br> | ||
<small>${data.types[0]}, ${data.country.country_name}<br> | ||
<i>${altNames}</i></small> | ||
</p> | ||
</div>`; | ||
} | ||
|
||
|
||
function initializeLocalInstitutions(endpoint = '/institutions/typeahead.json', cache = false) { | ||
|
||
const baseUrl = `${window.location.protocol}//${window.location.host}`; | ||
const fullUrl = `${baseUrl}${endpoint}`; | ||
console.log("Institutions URL: " + fullUrl); | ||
|
||
// Initialize and return the Bloodhound instance | ||
return new Bloodhound({ | ||
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('text'), | ||
queryTokenizer: Bloodhound.tokenizers.whitespace, | ||
prefetch: { | ||
url: fullUrl, | ||
cache: cache, // Use the provided cache option | ||
transform: function (response) { | ||
// Map the results array to the structure Bloodhound expects | ||
return 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 () { | ||
console.log("Fetching ROR data by ID..."); | ||
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: 1 // Start suggestions after typing at least 1 character | ||
}, | ||
// First Dataset: Local Institutions | ||
{ | ||
name: 'institutions', | ||
display: 'text', // Display the 'text' field in the dropdown | ||
source: initializeLocalInstitutions(), // 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) { | ||
console.log("Fetching ROR data by name remotely..."); | ||
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_name); | ||
$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_name); | ||
$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_name); | ||
$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); | ||
} | ||
$j('#basic #name-01').bind('change', function() { | ||
$j('#ror-response').html(''); | ||
}); | ||
}); |
Large diffs are not rendered by default.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can be removed. typeahead.js.js in
vendor
is referenced above