Skip to content

Commit

Permalink
Release/3.0.3 (#433)
Browse files Browse the repository at this point in the history
* Feature/duplicate record (#429)

Add duplicate assertion type to flag an issue

* enhanced duplicate record flag (#431)

* release 3.0.3
  • Loading branch information
alexhuang091 authored Mar 31, 2021
1 parent fe6c6dc commit 891c67b
Show file tree
Hide file tree
Showing 8 changed files with 327 additions and 17 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ buildscript {
}
}

version "3.0.2"
version "3.0.3"
group "au.org.ala.plugins.grails"

apply plugin:"eclipse"
Expand Down
194 changes: 185 additions & 9 deletions grails-app/assets/javascripts/show.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,130 @@ $(document).ready(function() {
$('.missingPropResult').toggle();
});

$('#copyRecordIdToClipboard').on('click', function(e) {
var copyText = document.querySelector("#hidden-uuid");
copyText.type = 'text';
copyText.select();
document.execCommand("copy");
copyText.type = 'hidden';
var $parent = $('#copyRecordIdToClipboard-parent');
$parent.tooltip('show');
setTimeout(function() {
$parent.tooltip('hide');
}, 1000);
// alert("Copied");
});
var recordIdValid = false;
function validateIssueForm() {
var issueCode = $('#issue').val();
var relatedRecordReason = $('#relatedRecordReason').val();
if (issueCode == '20020') {
return recordIdValid && relatedRecordReason;
}
return true;
}
function setIssueFormButtonState() {
$('#issueForm input[type=submit]').prop('disabled', !validateIssueForm());
}
$('#issue').on('change', function(e) {
var $this = $(this);
var val = $this.val();
var $submit = $('#issueForm input[type=submit]');
var $p = $('#related-record-p, #related-record-reason-p');
// if duplicate record
if (val === '20020') {
$('#relatedRecordId').val('');
recordIdValid = false;
$p.show();
} else {
$p.hide();
$('#related-record-id-not-found').hide();
$('#related-record-id-found').hide();
$('#related-record-id-loading').hide();
// hide the records table
$('#records_comparison_table').hide();
$('#records_comparison_heading').hide();
}
setIssueFormButtonState();
});

$('#relatedRecordReason').on('change', function(e) {
var col_reason = $('#col_duplicate_reason');
var relatedRecordReason = $('#relatedRecordReason').val();
if (relatedRecordReason === '') {
$(col_reason).text('');
} else {
$(col_reason).text(jQuery.i18n.prop('related.record.reason.description.' + relatedRecordReason));
}
setIssueFormButtonState();
})

$('#relatedRecordId').on('change', function(e) {
var $this = $(this);
var $submit = $('#issueForm input[type=submit]');
var val = $this.val().trim();
if (val === OCC_REC.recordUuid) {
alert("You can't mark this record as a duplicate of itself!");
recordIdValid = false;
} else if (val === '') {
$('#related-record-id-not-found').hide();
$('#related-record-id-found').hide();
$('#related-record-id-loading').hide();
$('#records_comparison_table').hide();
$('#records_comparison_heading').hide();
$('#relatedRecordReason').val("");
$('#col_duplicate_reason').text('');
recordIdValid = false;
} else {
$('#related-record-id-loading').show();
$.get( OCC_REC.contextPath + "/occurrence/exists/" + val).success(function(data) {
$('#related-record-id-loading').hide();
if (data.error) {
// show error
$('#related-record-id-not-found').text('More than 1 record found with specified id, please use a more specific id').show();
// hide compare table
$('#records_comparison_heading').hide();
$('#records_comparison_table').hide();
recordIdValid = false;
} else {
// hide error
$('#related-record-id-not-found').hide();
// show compare table
$('#related-record-id-found').show();
$('#records_comparison_table').show();
$('#records_comparison_heading').show();
// populate the table
$('#t_scientificName').text(data.scientificName ? data.scientificName : '');
$('#t_stateProvince').text(data.stateProvince ? data.stateProvince : '');
$('#t_decimalLongitude').text(data.decimalLongitude ? data.decimalLongitude : '');
$('#t_decimalLatitude').text(data.decimalLatitude ? data.decimalLatitude : '');
$('#t_eventDate').text(data.eventDate ? data.eventDate : '');
recordIdValid = true;
}
}).error(function () {
$('#related-record-id-not-found').text("The record id can't be found.").show();
$('#related-record-id-found').hide();
$('#related-record-id-loading').hide();
$('#records_comparison_table').hide();
$('#records_comparison_heading').hide();
$('#relatedRecordReason').val("");
$('#col_duplicate_reason').text('');
}).always(function() {
setIssueFormButtonState();
});
}
setIssueFormButtonState();
});

refreshUserAnnotations();

// bind to form submit for assertions
$("form#issueForm").submit(function(e) {
e.preventDefault();
var comment = $("#issueComment").val();
var code = $("#issue").val();
var relatedRecordId = $('#relatedRecordId').val();
var relatedRecordReason = $('#relatedRecordReason').val();
var userDisplayName = OCC_REC.userDisplayName //'${userDisplayName}';
var recordUuid = OCC_REC.recordUuid //'${ala:escapeJS(record.raw.rowKey)}';
if(code!=""){
Expand All @@ -54,22 +171,32 @@ $(document).ready(function() {
if (bPreventAddingIssue) {
alert("You cannot flag an issue with the same type that has already been verified.");
return;
} else if (code == '20020' && !relatedRecordId) {
alert("You must provide a duplicate record id to mark this as a duplicate");
return;
} else if (code == '20020' && !relatedRecordReason) {
alert("You must select a reason to mark this record as a duplicate");
return;
} else if (code == '20020' && relatedRecordId == recordUuid) {
alert("You can't mark a record as a duplicate of itself");
return;
} else {

$.post(OCC_REC.contextPath + "/occurrences/assertions/add",
{
recordUuid: recordUuid,
code: code,
comment: comment,
userAssertionStatus: 'Open issue',
userId: OCC_REC.userId,
userDisplayName: userDisplayName
userDisplayName: userDisplayName,
relatedRecordId: relatedRecordId,
relatedRecordReason: relatedRecordReason,
},
function (data) {
$('#assertionSubmitProgress').css({'display': 'none'});
$("#submitSuccess").html("Thanks for flagging the problem!");
$("#issueFormSubmit").hide();
$("input:reset").hide();
$("input#cancel").hide();
$("input#close").show();
//retrieve all assertions
$.get(OCC_REC.contextPath + '/assertions/' + OCC_REC.recordUuid, function (data) { // recordUuid=${record.raw.uuid}
Expand Down Expand Up @@ -293,7 +420,6 @@ function getMessage(userAssertionCode) {
function refreshUserAnnotations(){

$.get( OCC_REC.contextPath + "/assertions/" + OCC_REC.recordUuid, function(data) {

if (data.assertionQueries.length == 0 && data.userAssertions.length == 0) {
$('#userAnnotationsDiv').hide('slow');
$('#userAssertionsContainer').hide("slow");
Expand All @@ -309,7 +435,9 @@ function refreshUserAnnotations(){
var $clone = $('#userAnnotationTemplate').clone();
$clone.find('.issue').text(data.assertionQueries[i].assertionType);
$clone.find('.user').text(data.assertionQueries[i].userName);
$clone.find('.comment').text('Comment: ' + data.assertionQueries[i].comment);
if (data.assertionQueries[i].hasOwnProperty('comment')) {
$clone.find('.comment').text('Comment: ' + data.assertionQueries[i].comment);
}
$clone.find('.created').text('Date created: ' + (moment(data.assertionQueries[i].createdDate).format('YYYY-MM-DD')));
if(data.assertionQueries[i].recordCount > 1){
$clone.find('.viewMore').css({display:'block'});
Expand All @@ -332,10 +460,59 @@ function refreshUserAnnotations(){
$clone.prop('id', "userAnnotation_" + userAssertion.uuid);
$clone.find('.issue').text(jQuery.i18n.prop(userAssertion.name));
$clone.find('.user').text(userAssertion.userDisplayName);
$clone.find('.comment').text('Comment: ' + userAssertion.comment);
if (userAssertion.hasOwnProperty('comment')) {
$clone.find('.comment').text('Comment: ' + userAssertion.comment);
}
$clone.find('.userRole').text(userAssertion.userRole != null ? userAssertion.userRole : '');
$clone.find('.userEntity').text(userAssertion.userEntityName != null ? userAssertion.userEntityName : '');
$clone.find('.created').text('Date created: ' + (moment(userAssertion.created, "YYYY-MM-DDTHH:mm:ssZ").format('YYYY-MM-DD HH:mm:ss')));
if (userAssertion.relatedRecordId) {
$clone.find('.related-record').show();
// show related record id
$clone.find('.related-record-id').show();
$clone.find('.related-record-id-span').text(userAssertion.relatedRecordId);
var href = $clone.find('.related-record-link').attr('href');
$clone.find('.related-record-link').attr('href', href.replace('replace-me', userAssertion.relatedRecordId));
if (userAssertion.code === 20020) {
$clone.find('.related-record-span-user-duplicate').show();

$.get( OCC_REC.contextPath + "/occurrence/exists/" + userAssertion.relatedRecordId).success(function(data) {
if (!data.error) {
if (data.scientificName) {
$clone.find('.related-record-name').show();
$clone.find('.related-record-name-span').text(data.scientificName);
}

if (data.stateProvince) {
$clone.find('.related-record-state').show();
$clone.find('.related-record-state-span').text(data.stateProvince);
}

if (data.decimalLongitude) {
$clone.find('.related-record-latitude').show();
$clone.find('.related-record-latitude-span').text(data.decimalLongitude);
}

if (data.decimalLatitude) {
$clone.find('.related-record-longitude').show();
$clone.find('.related-record-longitude-span').text(data.decimalLatitude);
}

if (data.eventDate) {
$clone.find('.related-record-eventdate').show();
$clone.find('.related-record-eventdate-span').text(data.eventDate);
}
}
})
} else {
$clone.find('.related-record-span-default').show();
}
}
if (userAssertion.relatedRecordReason) {
$clone.find('.related-record-reason').show();
$clone.find('.related-record-reason-span').text(jQuery.i18n.prop('related.record.reason.' + userAssertion.relatedRecordReason));
$clone.find('.related-record-reason-explanation').text(jQuery.i18n.prop('related.record.reason.explanation.' + userAssertion.relatedRecordReason)).show();
}
if (userAssertion.userRole != null) {
$clone.find('.userRole').text(', ' + userAssertion.userRole);
}
Expand Down Expand Up @@ -423,7 +600,7 @@ function deleteAssertionPrompt(event) {
var isConfirmed = confirm('Are you sure you want to delete this flagged issue?');
if (isConfirmed === true) {
$('#' + event.data.qa_uuid + ' .deleteAssertionSubmitProgress').css({'display':'inline'});
console.log(event.data.qa_uuid);
//console.log(event.data.qa_uuid);
deleteAssertion(event.data.rec_uuid, event.data.qa_uuid);
}
}
Expand Down Expand Up @@ -481,8 +658,7 @@ function updateConfirmVerificationEvents(occUuid, assertionUuid, userDisplayName
return false;
}

console.log("Submitting an assertion with userAssertionStatus: " + userAssertionStatus)

//console.log("Submitting an assertion with userAssertionStatus: " + userAssertionStatus)
$.post(OCC_REC.contextPath + "/occurrences/assertions/add",
{ recordUuid: occUuid,
code: code,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,22 @@ class AssertionsController {
String comment = params.comment?:''
String userAssertionStatus = params.userAssertionStatus?: ""
String assertionUuid = params.assertionUuid?: ""
String relatedRecordId = params.relatedRecordId ?: ''
String relatedRecordReason = params.relatedRecordReason ?: ''
UserDetails userDetails = authService?.userDetails() // will return null if not available/not logged in

if (recordUuid && code && userDetails) {

if (code == '20020' && !relatedRecordId) {
render(status: 400, text: 'Duplicate record id not provided')
}

if (code == '20020' && !relatedRecordReason) {
render(status: 400, text: 'Duplicate record reason not provided')
}

log.info("Adding assertion to UUID: ${recordUuid}, code: ${code}, comment: ${comment}, userAssertionStatus: ${userAssertionStatus}, userId: ${userDetails.userId}, userEmail: ${userDetails.email}")
Map postResponse = webServicesService.addAssertion(recordUuid, code, comment, userDetails.userId, userDetails.displayName, userAssertionStatus, assertionUuid)
Map postResponse = webServicesService.addAssertion(recordUuid, code, comment, userDetails.userId, userDetails.displayName, userAssertionStatus, assertionUuid, relatedRecordId, relatedRecordReason)

if (postResponse.statusCode == 201) {
log.info("Called REST service. Assertion should be added" )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import javax.servlet.http.HttpServletRequest
import java.text.SimpleDateFormat

import static au.org.ala.biocache.hubs.TimingUtils.time
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND

/**
* Controller for occurrence searches and records
Expand Down Expand Up @@ -662,6 +663,25 @@ class OccurrenceController {
render webServicesService.facetCSVDownload(requestParams), contentType: 'text/csv', fileName: 'data.csv'
}

def exists(String id) {
// getRecord can return either 1 record or a list of records
// if a list returned, asking user to be more specific
def record = webServicesService.getRecord(id, false)
if (record.occurrences) {
render ([error: 'id not unique'] as JSON)
} else if (record.keySet()) {
def rslt = [:]
rslt.scientificName = record?.processed?.classification?.scientificName ?: (record?.raw?.classification?.scientificName ?: '')
rslt.stateProvince = record?.processed?.location?.stateProvince ?: (record?.raw?.location?.stateProvince ?: '')
rslt.decimalLongitude = record?.processed?.location?.decimalLongitude ?: (record?.raw?.location?.decimalLongitude ?: '')
rslt.decimalLatitude = record?.processed?.location?.decimalLatitude ?: (record?.raw?.location?.decimalLatitude ?: '')
rslt.eventDate = record?.processed?.event?.eventDate ?: (record?.raw?.event?.eventDate ?: '')
render rslt as JSON
} else {
render status: SC_NOT_FOUND, text: ''
}
}

/**
* JSON webservices for debugging/testing
*/
Expand Down
25 changes: 25 additions & 0 deletions grails-app/i18n/messages_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ show.loginorflag.div01.navigator = Click here
show.loginorflag.div02.label = You are logged in as
show.issueform.label01 = Issue type:
show.issueform.label02 = Comment:
show.issueform.label03 = Duplicate Record ID:
show.issueform.label04 = Duplicate Reason:
show.issueform.button.submit = Submit
show.issueform.button.cancel = Cancel
show.issueform.button.close = Close
Expand Down Expand Up @@ -198,6 +200,8 @@ show.userannotationtemplate.p01.navigator = View more with this annotation
show.userannotationtemplate.p02.navigator = Delete this annotation
show.userannotationtemplate.p03.navigator = Verify this annotation
show.userannotationtemplate.p04.navigator = Delete this verification
show.userannotationtemplate.relatedrecord.userduplicate.a = View duplicated record
show.userannotationtemplate.relatedrecord.default.a = View related record
show.headingbar02.title = Record Not Found
show.headingbar02.p01 = The requested record ID
show.headingbar02.p02 = was not found
Expand Down Expand Up @@ -825,6 +829,7 @@ unrecognisedInstitutionCode=Institution code not recognised
invalidImageUrl=Image URL invalid
temporalIssue=Temporal issue
userAssertionOther=Other issue
userDuplicateRecord=Duplicate record
idPreOccurrence=Identification date before occurrence date
georefPostDate=Georeferenced after occurrence date
firstOfMonth=First of the month
Expand Down Expand Up @@ -1168,3 +1173,23 @@ dq.warning.dataprofile.buttonright.text = Got it
dq.userpref.defaultprofile = -- Select a profile --
dq.data.profiles.disabled = Data profiles have been disabled for this search
dq.warning.failedtosave = Failed to save user preferences. Please try again
record.compare_table.heading = You are indicating that
record.compare_table.source_record.heading = This record
record.compare_table.target_record.heading = This record ID provided
related.record.reason.select=-- Select a reason --
related.record.reason.sameoccurrence=Duplicate occurrence
related.record.reason.tissuesample=Tissue sample
related.record.reason.splitspecimen=Split specimen
related.record.reason.description.sameoccurrence=Is a duplicate occurrence of
related.record.reason.description.tissuesample=Is a tissue sample of
related.record.reason.description.splitspecimen=Is a split specimen of
show.userannotationtemplate.relatedrecord.reason.label=Reason:
related.record.id.label=record ID
related.record.name.label=scientific name
related.record.state.label=state
related.record.latitude.label=latitude
related.record.longitude.label=longitude
related.record.eventdate.label=eventDate
related.record.reason.explanation.sameoccurrence=This record is a duplicate occurrence of this record:
related.record.reason.explanation.tissuesample=This record is a tissue sample of this record:
related.record.reason.explanation.splitspecimen=This record is a split specimen of this record:
Loading

0 comments on commit 891c67b

Please sign in to comment.