Skip to content

Commit 3774ca0

Browse files
committed
DigiVol Release 6.1.11
- DG-87, DG-142 (#471) Fixed issue where image dimensions were being misred - DG-96 Fixed error with Elastic Search throwing errors when attempting to query more than 10000 user transcriptions - DG-82 Updated user notebook to display map pins for user transcriptions with analog geo coordinates - DG-144 Fixed broken link for Admin user list - DG-131 Fixed search bar in main top menu bar - DG-66, DG-67 (#530) Fixed issue with 'See Similar Expeditions' button on finished expedition displaying no results - DG-90 (#518) Fixed broken images in Wildlife Spotter expeditions - DG-70 (#577) Updated expedition list summary cards to display short description instead of long description truncated - DG-94 Added a character counter to short description field in expedition settings - DG-146 Removed view button/link from user's notebook
2 parents f77bc56 + cb94e99 commit 3774ca0

File tree

20 files changed

+282
-73
lines changed

20 files changed

+282
-73
lines changed

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ branches:
77
only:
88
- master
99
- develop
10+
- qa
1011
- /^feature\/.*$/
1112
- /^hotfix\/.*$/
1213
services:

build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ plugins {
2424
id "com.github.erdi.webdriver-binaries" version "3.2"
2525
}
2626

27-
version "6.1.10"
27+
version "6.1.11"
2828
group "au.org.ala"
2929
description "Digivol application"
3030

@@ -161,7 +161,7 @@ dependencies {
161161
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.3' // old: 2.8.0
162162
implementation 'org.apache.commons:commons-compress:1.11'
163163
implementation 'org.apache.commons:commons-pool2:2.11.1' // old: 2.4.2
164-
implementation group: 'commons-io', name: 'commons-io', version: '2.11.0' //''2.5'
164+
implementation group: 'commons-io', name: 'commons-io', version: '2.17.0' //''2.5'
165165
implementation 'org.elasticsearch:elasticsearch:2.4.6'
166166
// optional for elastic search, version should match elastic search optional dep
167167
implementation group: 'org.apache.lucene', name: 'lucene-expressions', version: '4.9.1' //6.0.0 for ES 5.0, 8.11.1 for ES 7.17

grails-app/assets/javascripts/digivol-notebook.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,16 @@ var notebook = {
6969
* @param id
7070
*/
7171
function load_content(marker, id) {
72+
const taskViewUrl = $('#map').attr('taskview-url');
7273
$.ajax($('#map').attr('infowindow-url') + "/" + id).done(function(data) {
7374
var content =
74-
"<div style='font-size:12px;line-height:1.3em;'>Catalogue No.: " + data.cat + "<br/>Taxon: " + data.name + "<br/>Transcribed by: " + data.transcriber +
75-
"</div>";
75+
"<div style='font-size:12px;line-height:1.3em;'>" +
76+
"Task: " + id +
77+
"<br />File: ";
78+
if (taskViewUrl !== "") content += "<a href=\"" + taskViewUrl + "/" + id + "\" target=\"_blank\">" + data.filename + "</a>";
79+
else content += data.filename;
80+
if (data.name !== "" && data.name !== undefined && data.name !== null) content += "<br />Taxon: " + data.name
81+
content += "</div>";
7682
notebook.infowindow.close();
7783
notebook.infowindow.setContent(content);
7884
notebook.infowindow.open(notebook.map, marker);

grails-app/controllers/au/org/ala/volunteer/ImageController.groovy

+12-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package au.org.ala.volunteer
22

33
import grails.converters.JSON
44
import org.apache.catalina.connector.ClientAbortException
5+
import org.apache.commons.io.FilenameUtils
56

67
import javax.imageio.ImageIO
78

@@ -27,8 +28,17 @@ class ImageController {
2728

2829
format = format.toLowerCase()
2930
if (!FORMATS.contains(format)) {
30-
render([error: "${format} not supported"] as JSON, status: SC_BAD_REQUEST)
31-
return
31+
// Did the extension get screwed up? Check if the file does exist:
32+
File fileCheck = findImage(encodedPrefix, encodedName)
33+
if (fileCheck) {
34+
// Image is there with a different extension.
35+
log.debug("Found file under different file extension (or broken input): ${fileCheck.name}")
36+
format = FilenameUtils.getExtension(fileCheck.name)?.toLowerCase()
37+
} else {
38+
log.debug("No file found with supported extension: ${encodedName}.${format}")
39+
render([error: "${format} not supported"] as JSON, status: SC_BAD_REQUEST)
40+
return
41+
}
3242
}
3343

3444
if (width < 0 || width > MAX_WIDTH || height < 0 || height > MAX_HEIGHT) {

grails-app/controllers/au/org/ala/volunteer/ProjectController.groovy

+8
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,14 @@ class ProjectController {
801801
return false
802802
}
803803
}
804+
805+
if (params.shortDescription) {
806+
if (params.shortDescription.toString().length() > 500) {
807+
project.errors.rejectValue("shortDescription", "project.shortdescription.toolarge",
808+
"Short Description is too long.")
809+
return false
810+
}
811+
}
804812
}
805813

806814
bindData(project, params)

grails-app/controllers/au/org/ala/volunteer/UserController.groovy

+76-25
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package au.org.ala.volunteer
22

33
import com.google.common.base.Stopwatch
4+
import com.google.common.base.Strings
45
import grails.converters.JSON
56
import grails.gorm.transactions.Transactional
67
import org.elasticsearch.action.search.SearchResponse
78
import org.elasticsearch.action.search.SearchType
89
import org.springframework.dao.DataIntegrityViolationException
910

11+
import java.util.regex.Pattern
12+
1013
import static org.springframework.http.HttpStatus.NO_CONTENT
1114

1215
class UserController {
@@ -387,7 +390,8 @@ class UserController {
387390
if (!user) {
388391
flash.message = message(code: 'default.not.found.message',
389392
args: [message(code: 'user.label', default: 'User'), params.id]) as String
390-
redirect(action: "list")
393+
redirect(action: "adminList")
394+
return
391395
}
392396

393397
def roles = UserRole.findAllByUser(user)
@@ -470,7 +474,7 @@ class UserController {
470474
else {
471475
flash.message = message(code: 'default.not.found.message',
472476
args: [message(code: 'user.label', default: 'User'), params.id]) as String
473-
redirect(action: "list")
477+
redirect(action: "adminList")
474478
}
475479
}
476480

@@ -488,7 +492,7 @@ class UserController {
488492
user.delete(flush: true)
489493
flash.message = message(code: 'default.deleted.message',
490494
args: [message(code: 'user.label', default: 'User'), params.id]) as String
491-
redirect(action: "list")
495+
redirect(action: "adminList")
492496
} catch (DataIntegrityViolationException e) {
493497
String message = message(code: 'default.not.deleted.message',
494498
args: [message(code: 'user.label', default: 'User'), params.id]) as String
@@ -499,7 +503,7 @@ class UserController {
499503
} else {
500504
flash.message = message(code: 'default.not.found.message',
501505
args: [message(code: 'user.label', default: 'User'), params.id]) as String
502-
redirect(action: "list")
506+
redirect(action: "adminList")
503507
}
504508
}
505509

@@ -618,40 +622,74 @@ class UserController {
618622
log.debug("ajaxGetPoints| Transcription.countByFullyTranscribedBy(): ${sw.toString()}")
619623
sw.reset().start()
620624

621-
final query = """{
622-
"constant_score": {
623-
"filter": {
624-
"and": [
625-
{ "term": { "transcriptions.fullyTranscribedBy": "${userInstance.userId}" } },
626-
{ "nested" :
627-
{
628-
"path" : "fields",
629-
"filter" : { "term" : { "name": "decimalLongitude"}}
630-
}
631-
},
632-
{ "nested" :
633-
{
634-
"path" : "fields",
635-
"filter" : { "term" : { "name": "decimalLongitude"}}
636-
}
625+
final query = """
626+
{
627+
"query": {
628+
"bool": {
629+
"must": [
630+
{
631+
"term": {
632+
"transcriptions.fullyTranscribedBy": "${userInstance.userId}"
633+
}
634+
},
635+
{
636+
"term": {
637+
"fields.name": "decimalLongitude"
638+
}
639+
},
640+
{
641+
"term": {
642+
"fields.name": "decimalLatitude"
643+
}
644+
}
645+
]
637646
}
638-
]
639647
}
640-
}
641648
}"""
642-
643-
def searchResponse = fullTextIndexService.rawSearch(query, SearchType.QUERY_THEN_FETCH, taskCount.intValue(), fullTextIndexService.rawResponse)
649+
// Elastic Search max value.
650+
final int MAX_SEARCH = 10000
651+
def searchResponse = fullTextIndexService.rawSearch(query, SearchType.QUERY_THEN_FETCH, MAX_SEARCH, fullTextIndexService.rawResponse)
644652
sw.stop()
645653
log.debug("ajaxGetPoints| fullTextIndexService.rawSearch(): ${sw.toString()}")
646654
sw.reset().start()
647655

656+
// Regex for detecting traditional latitude/longitude. We will convert to decimal for Google Maps.
657+
def regex = Pattern.compile(/(((\d+)°)?)(((\d+)')?)(((\d+)")?)([NnSsEeWw])/)
658+
648659
def data = searchResponse.hits.hits.collect { hit ->
649660
def field = hit.source['fields']
650661

651662
def pt = field.findAll { value ->
652663
value['name'] == 'decimalLongitude' || value['name'] == 'decimalLatitude'
653664
}.collectEntries { value ->
654-
def dVal = value['value']
665+
def dVal = value['value'] as String
666+
log.debug("ajaxGetPoints| dVal: ${dVal}")
667+
668+
def matcher = regex.matcher(dVal)
669+
if (matcher.find()) {
670+
log.debug("ajaxGetPoints| Group count: ${matcher.groupCount()}")
671+
for (int i = 0; i <= matcher.groupCount(); i++) {
672+
log.debug("match[${i}]: ${matcher.group(i)}")
673+
}
674+
try {
675+
BigDecimal minutes = (getBigDecimalFromString(matcher.group(6).toString()) / new BigDecimal(60))
676+
BigDecimal seconds = (getBigDecimalFromString(matcher.group(9).toString()) / new BigDecimal(3600))
677+
BigDecimal degrees = getBigDecimalFromString(matcher.group(3).toString())
678+
log.debug("Conversion: degrees: [${degrees}], minutes: [${minutes}], seconds: [${seconds}]")
679+
degrees += (minutes + seconds)
680+
681+
// Check direction and assign negative if necessary
682+
if (matcher.group(10).equalsIgnoreCase("S") ||
683+
matcher.group(10).equalsIgnoreCase("W")) {
684+
degrees = -degrees
685+
}
686+
687+
dVal = degrees.toString()
688+
log.debug("dVal: ${dVal}")
689+
} catch (Exception e) {
690+
log.error("Error attempting to convert degrees, minutes and seconds to a decimal value ${dVal}, skipping.", e)
691+
}
692+
}
655693

656694
if (value['name'] == 'decimalLongitude') {
657695
[lng: dVal]
@@ -669,6 +707,19 @@ class UserController {
669707
render(data as JSON)
670708
}
671709

710+
/**
711+
* Converts a string value to a BigDecimal, returning 0 if the value isn't parseable.
712+
* @param input the value to convert
713+
* @return a BigDecimal or 0 if null/empty.
714+
*/
715+
def getBigDecimalFromString(String input) {
716+
if (!Strings.isNullOrEmpty(input) && !input.equalsIgnoreCase("null")) {
717+
return new BigDecimal(input.toInteger().intValue())
718+
} else {
719+
return new BigDecimal(0)
720+
}
721+
}
722+
672723
def notebookMainFragment() {
673724
def user = User.get(params.int("id"))
674725
//def simpleTemplateEngine = new SimpleTemplateEngine()

grails-app/i18n/messages.properties

+1
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ status.inactive=Inactive
181181
project.archived.description=Archived expeditions have all their task images removed.
182182
project.template.notcompatible=The selected template is not compatible with the expedition's type.
183183
project.template.notavailable=The template {0} is no longer available.
184+
project.shortdescription.toolarge=You have entered too many characters for the Short Description. It must not exceed 500 characters.
184185
project.institution.required=An institution is required.
185186
image.attribution.prefix=Image
186187
index.heading.line1=Decipher our collections,

grails-app/services/au/org/ala/volunteer/ProjectService.groovy

+5-2
Original file line numberDiff line numberDiff line change
@@ -402,17 +402,20 @@ class ProjectService implements EventPublisher {
402402
// Default, if all else fails
403403
def iconImage = grailsLinkGenerator.resource(file:'/iconLabels.png')
404404
def iconLabel = 'Specimens'
405+
def iconName = 'specimens'
405406

406407
if (project.projectType) {
407408
iconImage = projectTypeService.getIconURL(project.projectType)
408409
iconLabel = project.projectType.label
410+
iconName = project.projectType.name
409411
}
410412

411413
// def volunteer = User.findAll("from User where userId in (select distinct fullyTranscribedBy from Task where project_id = ${project.id})")
412414

413415
def ps = new ProjectSummary(project: project)
414416
ps.iconImage = iconImage
415417
ps.iconLabel = iconLabel
418+
ps.iconName = iconName
416419

417420
ps.taskCount = taskCount.toLong()
418421
ps.transcribedCount = transcribedCount.toLong()
@@ -540,7 +543,7 @@ class ProjectService implements EventPublisher {
540543
// #392 Add Project Type to the tag search (i.e. clicking on Project Type searches by 'tag')
541544
def tagSearch = []
542545
def labelJoinTable = context.select([PROJECT_LABELS.PROJECT_ID]).from(PROJECT_LABELS.leftJoin(LABEL).on(PROJECT_LABELS.LABEL_ID.eq(LABEL.ID))).where(LABEL.VALUE.in(tag))
543-
def typeJoinTable = context.select([PROJECT.ID]).from(PROJECT.leftJoin(PROJECT_TYPE).on(PROJECT.PROJECT_TYPE_ID.eq(PROJECT_TYPE.ID))).where(PROJECT_TYPE.LABEL.in(tag))
546+
def typeJoinTable = context.select([PROJECT.ID]).from(PROJECT.leftJoin(PROJECT_TYPE).on(PROJECT.PROJECT_TYPE_ID.eq(PROJECT_TYPE.ID))).where(PROJECT_TYPE.NAME.in(tag))
544547

545548
tagSearch.add(PROJECT.ID.in(labelJoinTable))
546549
tagSearch.add(PROJECT.ID.in(typeJoinTable))
@@ -580,7 +583,7 @@ class ProjectService implements EventPublisher {
580583
sortCondition = jNvl(INSTITUTION.NAME, PROJECT.FEATURED_OWNER)
581584
break
582585
case 'type':
583-
sortCondition = PROJECT_TYPE.LABEL
586+
sortCondition = PROJECT_TYPE.NAME
584587
break
585588
default:
586589
sortCondition = jLower(PROJECT.FEATURED_LABEL)

grails-app/services/au/org/ala/volunteer/TaskService.groovy

+7-2
Original file line numberDiff line numberDiff line change
@@ -899,8 +899,12 @@ ORDER BY record_idx, name;
899899
fileMap.dir = dir.absolutePath
900900
def file = new File(dir, filename)
901901
file << conn.inputStream
902-
fileMap.raw = file.name
903-
fileMap.localPath = file.getAbsolutePath()
902+
903+
File processedFile = new File(dir, filename)
904+
boolean result = ImageUtils.reorientImage(file, processedFile)
905+
906+
fileMap.raw = processedFile.name
907+
fileMap.localPath = processedFile.getAbsolutePath()
904908
fileMap.localUrlPrefix = urlPrefix + "${projectId}/${taskId}/${multimediaId}/"
905909
fileMap.contentType = conn.contentType
906910
return fileMap
@@ -1486,6 +1490,7 @@ ORDER BY record_idx, name;
14861490
[ id: row.id,
14871491
externalIdentifier: row.external_identifier,
14881492
isFullyTranscribed: row.is_fully_transcribed,
1493+
fullyTranscribedBy: row.fully_transcribed_by,
14891494
//fullyValidatedBy: row.validator_display_name,
14901495
projectId: row.project_id,
14911496
institutionId: row.institution_id,

grails-app/taglib/au/org/ala/volunteer/VolunteerTagLib.groovy

+3-3
Original file line numberDiff line numberDiff line change
@@ -1059,17 +1059,17 @@ class VolunteerTagLib {
10591059
def truncate = { attrs, body ->
10601060
final ELLIPSIS = attrs.ellipse ?: ''
10611061
def maxLength = attrs.maxlength
1062-
final bodyText = body().replaceAll("<[^>]*>", '') // strip out html tags
1062+
final bodyText = body().replaceAll("<[^>]*>", '').trim() // strip out html tags and white space
10631063

10641064
if (maxLength == null || !maxLength.isInteger() || maxLength.toInteger() <= 0) {
1065-
throw new Exception("The attribute 'maxlength' must an integer greater than 3. Provided value: $maxLength")
1065+
throw new Exception("The attribute 'maxlength' must be an integer greater than 3. Provided value: $maxLength")
10661066
} else {
10671067
maxLength = maxLength.toInteger()
10681068
}
10691069
if (maxLength <= ELLIPSIS.size()) {
10701070
throw new Exception("The attribute 'maxlength' must be greater than 3. Provided value: $maxLength")
10711071
}
1072-
if (bodyText.length() > maxLength) {
1072+
if ((bodyText.length() + (ELLIPSIS.size() + 1)) > maxLength) {
10731073
out << bodyText[0..maxLength - (ELLIPSIS.size() + 1)] + ELLIPSIS
10741074
} else {
10751075
out << bodyText

0 commit comments

Comments
 (0)