Skip to content
Open
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
19 changes: 11 additions & 8 deletions src/js/models/AppModel.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
"use strict";

// Remove any trailing slashes from a URL
const NORMALIZE_URL = (url) => url.replace(/\/+$/, "");

/**
* @class AppModel
* @classdesc A utility model that contains top-level configuration and storage for the application
Expand Down Expand Up @@ -761,9 +764,9 @@ define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
/**
* The base URL for the ORCID REST services
* @type {string}
* @default "https:/orcid.org"
* @default "https://pub.orcid.org/"
*/
orcidBaseUrl: "https:/orcid.org",
orcidBaseUrl: "https://pub.orcid.org/",

/**
* The URL for the ORCID search API, which can be used to search for information
Expand Down Expand Up @@ -2577,13 +2580,13 @@ define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
d1CNBaseUrl + this.get("d1CNService") + this.get("formatsUrl"),
);
}
}

//ORCID search
if (typeof this.get("orcidBaseUrl") != "undefined")
this.set(
"orcidSearchUrl",
this.get("orcidBaseUrl") + "/search/orcid-bio?q=",
);
// ORCID search
const orcidBaseUrl = this.get("orcidBaseUrl");
if (orcidBaseUrl) {
const searchUrl = `${NORMALIZE_URL(orcidBaseUrl)}/v3.0/expanded-search/?q=`;
this.set("orcidSearchUrl", searchUrl);
}

// Metadata quality report services
Expand Down
123 changes: 64 additions & 59 deletions src/js/models/LookupModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,77 +264,82 @@ define(["jquery", "jqueryui", "underscore", "backbone"], function (
});
},

orcidGetConcepts: function (uri, callback) {
var people = this.get("concepts")[uri];
/** @deprecated since 0.0.0 */
orcidGetConcepts: function () {},

if (people) {
callback(people);
return;
} else {
people = [];
}

var query =
MetacatUI.appModel.get("orcidBaseUrl") +
uri.substring(uri.lastIndexOf("/"));
var model = this;
$.get(query, function (data, status, xhr) {
// get the orcid info
var profile = $(data).find("orcid-profile");

_.each(profile, function (obj) {
var choice = {};
choice.label =
$(obj).find("orcid-bio > personal-details > given-names").text() +
" " +
$(obj).find("orcid-bio > personal-details > family-name").text();
choice.value = $(obj).find("orcid-identifier > uri").text();
choice.desc = $(obj).find("orcid-bio > personal-details").text();
people.push(choice);
});

model.get("concepts")[uri] = people;

// callback with answers
callback(people);
});
},

/*
/**
* Supplies search results for ORCiDs to autocomplete UI elements
* @param {jQuery} request - The jQuery UI autocomplete request object
* @param {function} response - The jQuery UI autocomplete response function
* @param {Array} [more=[]] - An array of additional results to include
* @param {Array} [ignore=[]] - An array of ORCiD IDs to ignore in the
* search results
* @param {number} [numResults=10] - The number of results to return
* @returns {Promise<void>}
*/
orcidSearch: function (request, response, more, ignore) {
var people = [];
async orcidSearch(
request,
response,
more = [],
ignore = [],
numResults = 10,
) {
const baseUrl = MetacatUI.appModel.get("orcidSearchUrl");
const searchTerm = request.term?.trim();
const rowQuery = `&rows=${numResults || 10}&start=0`;

if (!ignore) var ignore = [];
if (!baseUrl || !searchTerm) return response(more);

var query = MetacatUI.appModel.get("orcidSearchUrl") + request.term;
$.get(query, function (data, status, xhr) {
// get the orcid info
var profile = $(data).find("orcid-profile");
let ignoreQuery = "";

_.each(profile, function (obj) {
var choice = {};
choice.value = $(obj).find("orcid-identifier > uri").text();
if (ignore.length > 0) {
const orcidRegex = /^(https?:\/\/orcid\.org\/)?/i;
// Expected format: +-orcid:(0000-0002-0879-455X+0000-0001-6238-4490)
const ignoreIds = ignore.map((id) =>
id.replace(orcidRegex, "").trim(),
);
ignoreQuery = `+-orcid:(${ignoreIds.join("+")})`;
}

if (_.contains(ignore, choice.value.toLowerCase())) return;
const url = `${baseUrl}${searchTerm}${ignoreQuery}${rowQuery}`;

choice.label =
$(obj).find("orcid-bio > personal-details > given-names").text() +
" " +
$(obj).find("orcid-bio > personal-details > family-name").text();
choice.desc = $(obj).find("orcid-bio > personal-details").text();
people.push(choice);
});
let data;

// add more if called that way
if (more) {
people = more.concat(people);
try {
const orcidResponse = await fetch(url, {
headers: { "Content-Type": "application/json" },
});
if (!orcidResponse?.ok) {
const reason = await orcidResponse.text();
throw new Error(
`ORCiD search request failed: ${orcidResponse.status} ${orcidResponse.statusText} - ${reason}`,
);
}
data = await orcidResponse.json();
} catch (error) {
console.error("Error fetching ORCID data: ", error);
return response(more);
}

// callback with answers
response(people);
if ((data["num-found"] || 0) === 0) return response(more);

const peopleFound = data["expanded-result"] || [];

const choices = peopleFound.map((result) => {
const orcidId = result["orcid-id"];
const givenNames = result["given-names"];
const familyNames = result["family-names"];
const institutionNames = result["institution-name"];
return {
value: `https://orcid.org/${orcidId}`,
label: `${givenNames} ${familyNames}`,
desc: institutionNames.join(", "),
fullName: `${givenNames} ${familyNames}`,
};
});

const allResults = more ? more.concat(choices) : choices;
response(allResults);
},

/** @deprecated since 2.36.0 */
Expand Down
86 changes: 83 additions & 3 deletions test/js/specs/integration/models/LookupModel.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
define(["../../../../../../../../src/js/models/LookupModel"], function (
LookupModel,
) {
define(["models/LookupModel"], function (LookupModel) {
// Configure the Chai assertion library
var should = chai.should();
var expect = chai.expect;
Expand All @@ -11,6 +9,11 @@ define(["../../../../../../../../src/js/models/LookupModel"], function (
"grantsUrl",
"https://arcticdata.io/research.gov/awardapi-service/v1/awards.json",
);
MetacatUI.appModel.set("orcidBaseUrl", "https://pub.orcid.org");
MetacatUI.appModel.set(
"orcidSearchUrl",
"https://pub.orcid.org/v3.0/search/?q=",
);
});

afterEach(function () {
Expand All @@ -27,5 +30,82 @@ define(["../../../../../../../../src/js/models/LookupModel"], function (
expect(awards[0]).to.have.property("title");
});
});

describe("ORCID Search", function () {
let fetchStub;

beforeEach(function () {
fetchStub = sinon.stub(window, "fetch");
});

afterEach(function () {
if (fetchStub && fetchStub.restore) {
fetchStub.restore();
}
});

it("formats ORCID v3 search results and respects the ignore list", async function () {
const lookup = new LookupModel();
const request = { term: "Example" };
const moreResults = [{ value: "existing-user" }];
const ignore = [
"https://orcid.org/0000-0000-0000-0001",
"0000-0000-0000-0002",
];
const numResults = 5;

const apiResponse = {
"num-found": 1,
"expanded-result": [
{
"orcid-id": "0000-0001-7648-6754",
"given-names": "Test",
"family-names": "User",
"institution-name": ["Org A", "Org B"],
},
],
};

fetchStub.resolves({
ok: true,
status: 200,
statusText: "OK",
json: () => Promise.resolve(apiResponse),
});

const responseSpy = sinon.spy();

await lookup.orcidSearch(
request,
responseSpy,
moreResults,
ignore,
numResults,
);

fetchStub.calledOnce.should.equal(true);
const callArgs = fetchStub.firstCall.args;
expect(callArgs[0]).to.equal(
"https://pub.orcid.org/v3.0/search/?q=Example+-orcid:(0000-0000-0000-0001+0000-0000-0000-0002)&rows=5&start=0",
);
expect(callArgs[1]).to.have.property("headers");
expect(callArgs[1].headers).to.deep.equal({
"Content-Type": "application/json",
});

responseSpy.calledOnce.should.equal(true);
const results = responseSpy.firstCall.args[0];
expect(results).to.be.an("array").with.lengthOf(2);
expect(results[0]).to.deep.equal(moreResults[0]);

const newPerson = results[1];
expect(newPerson.value).to.equal(
"https://orcid.org/0000-0001-7648-6754",
);
expect(newPerson.label).to.equal("Test User");
expect(newPerson.fullName).to.equal("Test User");
expect(newPerson.desc).to.equal("Org A, Org B");
});
});
});
});