Skip to content

Commit 93ad03d

Browse files
committed
- fixes NPE when calculation convex hull - new site for edited site
1 parent e815077 commit 93ad03d

File tree

7 files changed

+264
-36
lines changed

7 files changed

+264
-36
lines changed

grails-app/controllers/au/org/ala/ecodata/AdminController.groovy

+1-1
Original file line numberDiff line numberDiff line change
@@ -737,7 +737,7 @@ class AdminController {
737737
def reSubmitDataSet() {
738738
String projectId = params.id
739739
String dataSetId = params.dataSetId
740-
String userId = params.userId ?: userService.getCurrentUser().userId
740+
String userId = params.userId ?: userService.currentUser()?.userId
741741
if (!projectId || !dataSetId || !userId) {
742742
render text: [message: "Bad request"] as JSON, status: HttpStatus.SC_BAD_REQUEST
743743
return

grails-app/services/au/org/ala/ecodata/ParatooService.groovy

+30-9
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,14 @@ class ParatooService {
279279

280280
String siteName = null
281281
if (!dataSet.siteId) {
282-
Map site = createSiteFromSurveyData(surveyDataAndObservations, collection, surveyId, project.project, config, form)
283-
dataSet.siteId = site.siteId
284-
siteName = site.name
282+
try {
283+
Map site = createSiteFromSurveyData(surveyDataAndObservations, collection, surveyId, project.project, config, form)
284+
dataSet.siteId = site.siteId
285+
siteName = site.name
286+
}
287+
catch (Exception ex) {
288+
log.error("Error creating site for ${collection.orgMintedUUID}: ${ex.message}")
289+
}
285290
}
286291

287292
// plot layout is of type geoMap. Therefore, expects a site id.
@@ -584,6 +589,7 @@ class ParatooService {
584589

585590
private Map createSiteFromSurveyData(Map observation, ParatooCollection collection, ParatooCollectionId surveyId, Project project, ParatooProtocolConfig config, ActivityForm form) {
586591
String siteId = null
592+
Date updatedPlotLayoutDate
587593
// Create a site representing the location of the collection
588594
Map siteProps = null
589595
Map geoJson = config.getGeoJson(observation, form)
@@ -605,16 +611,22 @@ class ParatooService {
605611
Site site
606612
// create new site for every non-plot submission
607613
if (config.usesPlotLayout) {
614+
updatedPlotLayoutDate = config.getPlotLayoutUpdatedAt(observation)
608615
List sites = Site.findAllByExternalId(ExternalId.IdType.MONITOR_PLOT_GUID, externalId, [sort: "lastUpdated", order: "desc"])
609616
if (sites)
610617
site = sites.first()
611618
}
612619

613620
Map result
614621
// If the plot layout has been updated, create a new site
615-
if (!site || isUpdatedPlotLayout(site, observation, config)) {
622+
if (!site) {
616623
result = siteService.create(siteProps)
617-
} else {
624+
}
625+
else if(isUpdatedPlotLayout(site.lastUpdated, updatedPlotLayoutDate)){
626+
siteProps.name = "${siteProps.name} - ${DateUtil.formatAsDisplayDateTime(updatedPlotLayoutDate)}"
627+
result = siteService.create(siteProps)
628+
}
629+
else {
618630
result = [siteId: site.siteId]
619631
}
620632

@@ -627,10 +639,19 @@ class ParatooService {
627639
[siteId:siteId, name:siteProps?.name]
628640
}
629641

630-
private static boolean isUpdatedPlotLayout (Site site, Map observation, ParatooProtocolConfig config) {
631-
Date localSiteUpdated = site.lastUpdated
632-
Date plotLayoutUpdated = config.getPlotLayoutUpdatedAt(observation)
633-
plotLayoutUpdated?.after(localSiteUpdated) ?: false
642+
/**
643+
* check if the plot layout has been updated after site has been updated. This means user has edited plot layout and
644+
* a new site should be created.
645+
* @param siteLastUpdated
646+
* @param plotLayoutLastUpdated
647+
* @return
648+
*/
649+
static boolean isUpdatedPlotLayout (Date siteLastUpdated, Date plotLayoutLastUpdated) {
650+
if ((siteLastUpdated != null) && (plotLayoutLastUpdated != null)) {
651+
return plotLayoutLastUpdated.after(siteLastUpdated)
652+
}
653+
654+
return false
634655
}
635656

636657
private Map syncParatooProtocols(List<Map> protocols) {

src/main/groovy/au/org/ala/ecodata/converter/FeatureConverter.groovy

+15-15
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,27 @@ class FeatureConverter implements RecordFieldConverter {
44

55
List<Map> convert(Map data, Map metadata = [:]) {
66
Map record = [:]
7+
if (data[metadata.name]) {
8+
Double latitude = getDecimalLatitude(data[metadata.name])
9+
Double longitude = getDecimalLongitude(data[metadata.name])
710

11+
// Don't override decimalLongitud or decimalLatitude in case they are null, site info could've already set them
12+
if (latitude != null) {
13+
record.decimalLatitude = latitude
14+
}
815

9-
Double latitude = getDecimalLatitude(data[metadata.name])
10-
Double longitude = getDecimalLongitude(data[metadata.name])
11-
12-
// Don't override decimalLongitud or decimalLatitude in case they are null, site info could've already set them
13-
if(latitude != null) {
14-
record.decimalLatitude = latitude
15-
}
16-
17-
if (longitude != null) {
18-
record.decimalLongitude = longitude
19-
}
16+
if (longitude != null) {
17+
record.decimalLongitude = longitude
18+
}
2019

2120

22-
Map dwcMappings = extractDwcMapping(metadata)
21+
Map dwcMappings = extractDwcMapping(metadata)
2322

24-
record << getDwcAttributes(data, dwcMappings)
23+
record << getDwcAttributes(data, dwcMappings)
2524

26-
if (data.dwcAttribute) {
27-
record[data.dwcAttribute] = data.value
25+
if (data.dwcAttribute) {
26+
record[data.dwcAttribute] = data.value
27+
}
2828
}
2929

3030
[record]

src/main/groovy/au/org/ala/ecodata/paratoo/ParatooProtocolConfig.groovy

+2-2
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ class ParatooProtocolConfig {
335335
plotGeoJson
336336
}
337337

338-
static Map createConvexHullGeoJSON (List features, String name, String externalId = "", String notes = "") {
338+
static Map createConvexHullGeoJSON (List features, String name, String externalId = "", String notes = "", String description = "") {
339339
features = features.findAll { it.geometry != null }
340340
List featureGeometries = features.collect { it.geometry }
341341
Geometry geometry = GeometryUtils.getFeatureCollectionConvexHull(featureGeometries)
@@ -346,7 +346,7 @@ class ParatooProtocolConfig {
346346
name: name,
347347
externalId: externalId,
348348
notes: notes,
349-
description: "${name} (convex hull of all features)",
349+
description: "${description?:name}",
350350
],
351351
features: features
352352
]

src/test/groovy/au/org/ala/ecodata/ParatooServiceSpec.groovy

+117-4
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ import org.codehaus.jackson.map.ObjectMapper
1212
import org.grails.web.converters.marshaller.json.CollectionMarshaller
1313
import org.grails.web.converters.marshaller.json.MapMarshaller
1414

15-
import java.time.format.DateTimeTextProvider
16-
import java.time.temporal.TemporalField
17-
1815
import static grails.async.Promises.waitAll
1916
/**
2017
* Tests for the ParatooService.
@@ -82,6 +79,7 @@ class ParatooServiceSpec extends MongoSpec implements ServiceUnitTest<ParatooSer
8279
Service.findAll().each { it.delete() }
8380
UserPermission.findAll().each { it.delete() }
8481
Program.findAll().each { it.delete() }
82+
Site.findAll().each { it.delete() }
8583
}
8684

8785
def cleanup() {
@@ -339,7 +337,7 @@ class ParatooServiceSpec extends MongoSpec implements ServiceUnitTest<ParatooSer
339337

340338
and:
341339
site.name == "SATFLB0001 - Control (100 x 100)"
342-
site.description == "SATFLB0001 - Control (100 x 100) (convex hull of all features)"
340+
site.description == "SATFLB0001 - Control (100 x 100)"
343341
site.notes == "Core monitoring plot some comment"
344342
site.type == "compound"
345343
site.publicationStatus == "published"
@@ -350,6 +348,121 @@ class ParatooServiceSpec extends MongoSpec implements ServiceUnitTest<ParatooSer
350348

351349
}
352350

351+
void "The service will create a new site if ploy layout has been updated or use existing site" () {
352+
setup:
353+
String projectId = 'p1'
354+
String orgMintedId = 'd1'
355+
Date afterSubmissionDate = DateUtil.parseWithMilliseconds("2023-09-15T06:00:11.996Z")
356+
Date beforeSubmissionDate = DateUtil.parseWithMilliseconds("2023-09-14T06:00:11.996Z")
357+
new Site(
358+
name: "SATFLB0001 - Control (100 x 100)",
359+
siteId: "s0",
360+
extent: [geometry: DUMMY_POLYGON],
361+
description: "SATFLB0001 - Control (100 x 100)",
362+
notes: "Core monitoring plot some comment",
363+
type: "compound",
364+
externalIds: [new ExternalId(externalId: "2", idType: ExternalId.IdType.MONITOR_PLOT_GUID)],
365+
dateCreated: afterSubmissionDate,
366+
lastUpdated: afterSubmissionDate
367+
).save(flush: true)
368+
ParatooProtocolId protocol = new ParatooProtocolId(id: "1", version: 1)
369+
ParatooCollection collection = new ParatooCollection(
370+
orgMintedUUID:orgMintedId,
371+
coreProvenance: [
372+
"system_core": "<system-core>",
373+
"version_core": "<core-version>"
374+
]
375+
)
376+
ParatooCollectionId paratooCollectionId = buildCollectionId("mintCollectionIdBasalAreaPayload","guid-3")
377+
Map dataSet = [dataSetId:'d1', grantId:'g1', surveyId:paratooCollectionId.toMap()]
378+
ParatooProject project = new ParatooProject(id: projectId, project: new Project(projectId: projectId, custom: [dataSets: [dataSet]]))
379+
Map surveyData = readSurveyData('basalAreaDbhReverseLookup')
380+
Map site
381+
382+
when:
383+
Map result = service.submitCollection(collection, project)
384+
waitAll(result.promise)
385+
println ("finished waiting")
386+
387+
then:
388+
1 * webService.doPost(*_) >> [resp: surveyData]
389+
1 * tokenService.getAuthToken(true) >> Mock(AccessToken)
390+
2 * projectService.updateDataSet(projectId, _) >> [status: 'ok']
391+
0 * siteService.create(_)
392+
1 * activityService.create({
393+
it.startDate == "2023-09-22T00:59:47Z" && it.endDate == "2023-09-23T00:59:47Z" &&
394+
it.plannedStartDate == "2023-09-22T00:59:47Z" && it.plannedEndDate == "2023-09-23T00:59:47Z" &&
395+
it.externalIds[0].externalId == "d1" && it.externalIds[0].idType == ExternalId.IdType.MONITOR_MINTED_COLLECTION_ID
396+
}) >> [activityId: '123']
397+
1 * recordService.getAllByActivity('123') >> []
398+
1 * settingService.getSetting('paratoo.surveyData.mapping') >> {
399+
(["guid-3": [
400+
"name" : "Basal Area - DBH",
401+
"usesPlotLayout": true,
402+
"tags" : ["survey"],
403+
"apiEndpoint" : "basal-area-dbh-measure-surveys",
404+
"overrides" : [
405+
"dataModel": null,
406+
"viewModel": null
407+
]
408+
]] as JSON).toString()
409+
}
410+
1 * userService.getCurrentUserDetails() >> [userId: userId]
411+
1 * userService.setCurrentUser(userId)
412+
413+
when:
414+
String date = DateUtil.format(new Date())
415+
date = date.replace("Z", ".999Z")
416+
surveyData["collections"]["basal-area-dbh-measure-survey"]["plot_visit"]["plot_layout"]["updatedAt"] = [date]
417+
result = service.submitCollection(collection, project)
418+
419+
then:
420+
1 * webService.doPost(*_) >> [resp: surveyData]
421+
1 * tokenService.getAuthToken(true) >> Mock(AccessToken)
422+
2 * projectService.updateDataSet(projectId, _) >> [status: 'ok']
423+
1 * siteService.create(_) >> { site = it[0]; [siteId: 's1'] }
424+
1 * activityService.create({
425+
it.startDate == "2023-09-22T00:59:47Z" && it.endDate == "2023-09-23T00:59:47Z" &&
426+
it.plannedStartDate == "2023-09-22T00:59:47Z" && it.plannedEndDate == "2023-09-23T00:59:47Z" &&
427+
it.externalIds[0].externalId == "d1" && it.externalIds[0].idType == ExternalId.IdType.MONITOR_MINTED_COLLECTION_ID
428+
}) >> [activityId: '123']
429+
1 * recordService.getAllByActivity('123') >> []
430+
1 * settingService.getSetting('paratoo.surveyData.mapping') >> {
431+
(["guid-3": [
432+
"name" : "Basal Area - DBH",
433+
"usesPlotLayout": true,
434+
"tags" : ["survey"],
435+
"apiEndpoint" : "basal-area-dbh-measure-surveys",
436+
"overrides" : [
437+
"dataModel": null,
438+
"viewModel": null
439+
]
440+
]] as JSON).toString()
441+
}
442+
1 * userService.getCurrentUserDetails() >> [userId: userId]
443+
1 * userService.setCurrentUser(userId)
444+
result.updateResult == [status: 'ok']
445+
}
446+
447+
void "isUpdatedPlotLayout should check plot layout has been updated after site has been updated" () {
448+
given:
449+
def date1 = DateUtil.parseWithMilliseconds("2023-09-22T00:59:47.111Z")
450+
def date2 = DateUtil.parseWithMilliseconds("2023-09-23T00:59:47.111Z")
451+
def date3 = DateUtil.parseWithMilliseconds("2023-09-24T00:59:47.111Z")
452+
453+
when:
454+
def result = service.isUpdatedPlotLayout(date1, date2)
455+
456+
then:
457+
result
458+
459+
when:
460+
result = service.isUpdatedPlotLayout(date3, date2)
461+
462+
then:
463+
!result
464+
}
465+
353466
private Map getProject(){
354467
[
355468
projectId:"p1",

src/test/groovy/au/org/ala/ecodata/SiteServiceSpec.groovy

+20-3
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@ import com.mongodb.BasicDBObject
44
import grails.converters.JSON
55
import grails.test.mongodb.MongoSpec
66
import grails.testing.services.ServiceUnitTest
7+
import org.grails.web.converters.marshaller.json.CollectionMarshaller
78

89
/*import grails.test.mixin.TestMixin
910
import grails.test.mixin.mongodb.MongoDbTestMixin*/
10-
import org.grails.web.converters.marshaller.json.CollectionMarshaller
11-
import org.grails.web.converters.marshaller.json.MapMarshaller
12-
import spock.lang.Specification
1311

12+
import org.grails.web.converters.marshaller.json.MapMarshaller
1413
/**
1514
* Specification / tests for the SiteService
1615
*/
@@ -288,6 +287,24 @@ class SiteServiceSpec extends MongoSpec implements ServiceUnitTest<SiteService>
288287
289288
}
290289
290+
def "Sites can be listed by externalId and sorted"() {
291+
when:
292+
def result
293+
Site.withSession { session ->
294+
result = service.create([name:'Site 1', siteId:"s1", externalIds:[new ExternalId(externalId:'e1', idType:ExternalId.IdType.MONITOR_PLOT_GUID)]])
295+
session.flush()
296+
result = service.create([name:'Site 2', siteId:"s2", externalIds:[new ExternalId(externalId:'e1', idType:ExternalId.IdType.MONITOR_PLOT_GUID)]])
297+
session.flush()
298+
}
299+
then:
300+
def sites = Site.findAllByExternalId(ExternalId.IdType.MONITOR_PLOT_GUID, 'e1', ['sort': "lastUpdated", 'order': "desc"])
301+
sites.size() == 2
302+
sites[0].name == 'Site 2'
303+
sites[0].externalIds.size() == 1
304+
sites.externalIds.externalId == [['e1'], ['e1']]
305+
sites.externalIds.idType == [[ExternalId.IdType.MONITOR_PLOT_GUID], [ExternalId.IdType.MONITOR_PLOT_GUID]]
306+
}
307+
291308
292309
private Map buildExtent(source, type, coordinates, pid = '') {
293310
return [source:source, geometry:[type:type, coordinates: coordinates, pid:pid]]

0 commit comments

Comments
 (0)