Commit 7f690612 authored by LAVENIER's avatar LAVENIER
Browse files

[enh] RDF Taxon search: display result, during fetching

[enh] RDF Taxon search: Add a "Create missing" button, and open a toast when click
parent 9296d687
......@@ -50,6 +50,20 @@
<div class="container" id="main" >
<!-- Toast -->
<div id="create-missing-toast" class="toast" role="alert" aria-live="assertive" aria-atomic="true"
style="position: absolute; top: 40px; right: 40px; z-index: 9999;">
<div class="toast-header">
<strong class="mr-auto">Information</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
Your request has been sent.
</div>
</div>
<h1 class="d-flex justify-content-center">Taxon search</h1>
<form class="form-group" style="padding-top: 15px;" onsubmit="return app.doSubmit(event)" href="#">
......@@ -142,7 +156,7 @@
</div>
<div class="col-12 col-sm-5 col-lg-3" >
<div class="float-right">
<div class="dropdown" style="display: inline;">
<div class="dropdown import-dropdown" style="display: inline;">
<button class="btn btn-primary dropdown-toggle" data-target="#" id="importMenu" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="true">
<span>Search</span>
<span class="caret"></span>
......@@ -157,6 +171,9 @@
<li><a href="#" class="import-remote-aphiaid">By AphiaID</a></li>
</ul>
</div>
<button class="btn btn-danger create-missing d-none">
<span>Create missing</span>
</button>
<button data-dz-remove class="btn btn-warning cancel ">
<span>Cancel</span>
</button>
......@@ -168,6 +185,8 @@
</div>
</div>
<script type="text/javascript" src="/core/js/jquery.slim.min.js"></script>
<script type="text/javascript" src="/core/js/popper.min.js"></script>
<script type="text/javascript" src="/core/js/bootstrap.min.js"></script>
......
......@@ -723,7 +723,7 @@ function AppTaxonSearch(config) {
reject("Response content type '{}' not implemented yet".format(res.type));
}
console.debug("[taxon-search] Search on '{0}' give results:".format(searchText), res.body);
console.debug("[taxon-search] Search on '{0}' give {1} results:".format(searchText, res.body && res.body.results && res.body.results.bindings.length || 0));
resolve(res);
}
})
......@@ -1020,7 +1020,8 @@ function AppTaxonSearch(config) {
// Chain search on each value
let res,
lookupVar,
lookupVar = 'lookup',
counter = 0,
matchCount = 0;
for (value of validValues) {
......@@ -1034,53 +1035,86 @@ function AppTaxonSearch(config) {
const curRes = await searchAsPromise(value, opts);
const hasResult = curRes.body && curRes.body.results && curRes.body.results.bindings && curRes.body.results.bindings.length > 0;
matchCount += hasResult ? 1 : 0;
const bindings = hasResult ? curRes.body.results.bindings : [{}/*empty binding*/];
// If first response, init the result
if (!res) {
// Copy the first result, to init final response
res = {...curRes};
res = {...curRes,
body: {
head: {
// Add lookup var
vars: [lookupVar].concat(curRes.body.head.vars)
},
results: {
// Reset bindings (will be add later)
bindings: []
}
}
};
}
// Reset bindings (will be add later)
res.body.results.bindings = [];
let bindings;
if (hasResult) {
bindings = curRes.body.results.bindings;
// Compute and add a new var, to store the lookup value
lookupVar = valueType;
while (res.body.head.vars.findIndex(v => v === lookupVar) !== -1) {
lookupVar = '_' + lookupVar;
}
res.body.head.vars.push(lookupVar);
// Add the lookup value to each bindings
bindings.forEach(binding => {
binding[lookupVar] = {value, type: 'literal', found: true};
});
}
else {
// Create a fake binding, for the missing value
const binding = {};
binding[lookupVar] = {value, type: 'literal', found: false};
switch(valueType.toLowerCase()) {
case 'code':
binding.sourceUri = {value, type: 'literal'};
break;
case 'name':
binding.scientificName = {value, type: 'literal'};
break;
case 'aphiaid':
binding.exactMatch = {value: 'urn:lsid:marinespecies.org:taxname:' + value, type: 'uri'};
break;
default:
console.warn('Unknown value type: ' + valueType);
}
// Add the lookup value to each bindings
bindings.forEach(b => b[lookupVar] = {value, type: 'literal'});
bindings = [binding];
}
// Append final bindings
res.body.results.bindings = res.body.results.bindings.concat(bindings);
// Update counter
matchCount += hasResult ? 1 : 0;
// Update progression
progression += progressionStep;
setProgression(progression);
// Display result, every 5 value
if (progression < 100 && counter % 5 === 0) {
displayResponse(this.yasgui, res, 0);
}
}
// All search has been executed
setProgression(100);
const duration = Date.now() - now;
console.debug("[taxon-search] All {0} imported in {1} ms".format(valueType, duration), res);
console.debug("[taxon-search] All {0} imported in {1} ms".format(valueType, duration));
if (res) {
file.response = res;
file.duration = duration;
displayFileResponse(file);
}
else {
hideResult();
}
displayFileResponse(file);
displayFileResult(file, {
displayFileExecutionResult(file, {
totalCount: values.length,
matchCount: matchCount,
ignoreCount : values.length - validValues.length,
......@@ -1108,62 +1142,85 @@ function AppTaxonSearch(config) {
})
}
function displayFileResult(file, {totalCount, matchCount, ignoreCount, missingCount}) {
function displayFileExecutionResult(file, {totalCount, matchCount, ignoreCount, missingCount}) {
if (!file || !file.previewElement) return;
const el = file.previewElement.querySelector(".result");
if (!el) return;
const resultElement = file.previewElement.querySelector(".result");
if (resultElement) {
// Total element
{
const totalEl = document.createElement("a");
totalEl.setAttribute('href', '#');
totalEl.innerText = totalCount + ' row' + (totalCount > 1 ? 's' : '');
totalEl.onclick = () => displayFileResponse(file);
resultElement.appendChild(totalEl);
}
{
const totalEl = document.createElement("a");
totalEl.setAttribute('href', '#');
totalEl.innerText = totalCount + ' row' + (totalCount > 1 ? 's' : '');
totalEl.onclick = () => displayFileResponse(file);
el.appendChild(totalEl);
}
{
const span = resultElement.appendChild(document.createElement("span"));
span.innerText = ' (';
resultElement.appendChild(span);
}
{
const span = el.appendChild(document.createElement("span"));
span.innerText = ' (';
el.appendChild(span);
}
// Ignore count
if (ignoreCount > 0) {
const ignoreEl = resultElement.appendChild(document.createElement("span"));
ignoreEl.innerText = ignoreCount + ' ignored';
resultElement.appendChild(ignoreEl);
}
if (ignoreCount > 0) {
const ignoreEl = el.appendChild(document.createElement("span"));
ignoreEl.innerText = ignoreCount + ' ignored';
el.appendChild(ignoreEl);
}
// Match count
if (matchCount > 0) {
if (ignoreCount > 0) {
const span = resultElement.appendChild(document.createElement("span"));
span.innerText = ', ';
resultElement.appendChild(span);
}
const matchEl = document.createElement("a");
matchEl.setAttribute('href', '#');
matchEl.innerText = matchCount + ' found';
matchEl.onclick = () => displayFileResponse(file, {valid: true});
resultElement.appendChild(matchEl);
}
if (matchCount > 0) {
if (ignoreCount > 0) {
const span = el.appendChild(document.createElement("span"));
span.innerText = ', ';
el.appendChild(span);
// Not found count
if (missingCount > 0) {
if (ignoreCount > 0 || matchCount > 0) {
const span = resultElement.appendChild(document.createElement("span"));
span.innerText = ', ';
resultElement.appendChild(span);
}
const missingEl = document.createElement("a");
missingEl.setAttribute('href', '#');
missingEl.innerText = missingCount + ' not found';
missingEl.onclick = () => displayFileResponse(file, {valid: false});
resultElement.appendChild(missingEl);
}
{
const span = resultElement.appendChild(document.createElement("span"));
span.innerHTML = ')';
resultElement.appendChild(span);
}
const matchEl = document.createElement("a");
matchEl.setAttribute('href', '#');
matchEl.innerText = matchCount + ' found';
matchEl.onclick = () => displayFileResponse(file, {valid: true});
el.appendChild(matchEl);
}
// Update buttons
if (missingCount > 0) {
if (ignoreCount > 0 || matchCount > 0) {
const span = el.appendChild(document.createElement("span"));
span.innerText = ', ';
el.appendChild(span);
const createMissingButton = file.previewElement.querySelector(".btn.create-missing");
if (createMissingButton) {
$('#create-missing-toast').toast({autohide: true, delay: 2500});
createMissingButton.classList.remove('d-none');
createMissingButton.onclick = () => {
console.debug('[taxon-search] Asking creation of missing references...');
$('#create-missing-toast').toast('show');
}
}
const missingEl = document.createElement("a");
missingEl.setAttribute('href', '#');
missingEl.innerText = missingCount + ' not found';
missingEl.onclick = () => displayFileResponse(file, {valid: false});
el.appendChild(missingEl);
}
{
const span = el.appendChild(document.createElement("span"));
span.innerHTML = ')';
el.appendChild(span);
}
// Hide the import button
//const importButton = file.previewElement.querySelector(".import-dropdown");
//if (importButton) {
// importButton.classList.add('d-none')
//}
}
function hideFileResult(file) {
......@@ -1176,26 +1233,31 @@ function AppTaxonSearch(config) {
function displayFileResponse(file, opts) {
if (!file || !file.response) return; // Skip
// Filter response
let res;
// Create a filter function, from options
let filterFn;
if (opts && opts.valid === true) {
res = {
...file.response
};
res.body.results.bindings = res.body.results.bindings.slice().filter(binding => !!binding.sourceUri);
filterFn = (b) => !!b.sourceUri;
}
else if (opts && opts.valid === false) {
res = {
...file.response
filterFn = (b) => !b.sourceUri;
}
if (filterFn) {
const response = {
...file.response,
body: {
head: file.response.body.head,
results: {
bindings: file.response.body.results.bindings.filter(b => !filterFn || filterFn(b))
}
}
};
res.body.results.bindings = res.body.results.bindings.slice().filter(binding => !binding.sourceUri);
displayResponse(this.yasqe, response, file.duration);
}
else {
res = { ...file.response };
res.body.results.bindings = res.body.results.bindings.slice();
displayResponse(this.yasqe, file.response, file.duration);
}
displayResponse(this.yasqe, res, file.duration);
}
function getFileProgressionFn(file) {
......
......@@ -46,55 +46,41 @@ class YasrTaxonPlugin {
getTaxonsFromBindings(bindings) {
const taxonsByUri = {};
bindings.forEach(binding => {
let uri = binding.sourceUri && binding.sourceUri.value;
// If no match found (can occur n file import)
if (!uri) {
const lookupVars = Object.keys(binding);
const lookupVar = lookupVars.length === 1 ? lookupVars[0] : lookupVars[lookupVars.length -1];
const lookupValue = binding[lookupVar].value;
uri = lookupValue;
if (!taxonsByUri[uri]) {
taxonsByUri[uri] = {
uri: lookupVar === 'code' ? lookupValue : undefined,
scientificName: lookupVar === 'name' ? lookupValue : undefined,
missing: true,
exactMatch: lookupVar === 'aphiaid' ? ['urn:lsid:marinespecies.org:taxname:' + lookupValue] : [],
seeAlso: []
};
}
}
else {
if (!taxonsByUri[uri]) {
taxonsByUri[uri] = {
uri,
scientificName: binding.scientificName.value,
const missing = binding.lookup && binding.lookup.found === false;
const uniqueKey = (binding.sourceUri && binding.sourceUri.value) || (missing && binding.lookup.value);
if (uniqueKey) {
if (!taxonsByUri[uniqueKey]) {
taxonsByUri[uniqueKey] = {
uri: binding.sourceUri && binding.sourceUri.value,
scientificName: binding.scientificName && binding.scientificName.value,
author: binding.author && binding.author.value,
rank: binding.rank && binding.rank.value,
created: binding.created && binding.created.value,
modified: binding.modified && binding.modified.value,
exactMatch : [],
seeAlso : [],
parentUri: binding.parent && binding.parent.value
parentUri: binding.parent && binding.parent.value,
missing
};
// Remove modified date, if same as created
if (taxonsByUri[uri].modified && taxonsByUri[uri].modified === taxonsByUri[uri].created) {
taxonsByUri[uri].modified = undefined;
if (taxonsByUri[uniqueKey].modified && taxonsByUri[uniqueKey].modified === taxonsByUri[uniqueKey].created) {
taxonsByUri[uniqueKey].modified = undefined;
}
}
}
if (binding.exactMatch && binding.exactMatch.value
// Exclude if already present
&& !taxonsByUri[uri].exactMatch.includes(binding.exactMatch.value)
// Exclude is same as source
&& binding.exactMatch.value.trim() !== uri) {
taxonsByUri[uri].exactMatch.push(binding.exactMatch.value.trim())
}
if (binding.seeAlso && binding.seeAlso.value
// Exclude if already present
&& !taxonsByUri[uri].seeAlso.includes(binding.seeAlso.value)
// Exclude is same as source
&& binding.seeAlso.value.trim() !== uri) {
taxonsByUri[uri].seeAlso.push(binding.seeAlso.value.trim())
if (binding.exactMatch && binding.exactMatch.value
// Exclude if already present
&& !taxonsByUri[uniqueKey].exactMatch.includes(binding.exactMatch.value)
// Exclude is same as source
&& binding.exactMatch.value.trim() !== uniqueKey) {
taxonsByUri[uniqueKey].exactMatch.push(binding.exactMatch.value.trim())
}
if (binding.seeAlso && binding.seeAlso.value
// Exclude if already present
&& !taxonsByUri[uniqueKey].seeAlso.includes(binding.seeAlso.value)
// Exclude is same as source
&& binding.seeAlso.value.trim() !== uniqueKey) {
taxonsByUri[uniqueKey].seeAlso.push(binding.seeAlso.value.trim())
}
}
});
return Object.keys(taxonsByUri).map(key => taxonsByUri[key]).sort((t1, t2) => t1.scientificName === t2.scientificName ? 0 : (t1.scientificName > t2.scientificName ? 1 : -1));
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment