Skip to content

Commit 3c57f8a

Browse files
committed
Merge remote-tracking branch 'origin/dev' into feature/issue3421
2 parents 396cca9 + 3acaf69 commit 3c57f8a

File tree

7 files changed

+182
-47
lines changed

7 files changed

+182
-47
lines changed

grails-app/assets/javascripts/projects.js

+67-21
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,14 @@ function ProjectViewModel(project) {
180180
self.geographicInfo = {
181181
nationwide: ko.observable(project.geographicInfo.nationwide || false),
182182
statewide: ko.observable(project.geographicInfo.statewide || false),
183-
isDefault: ko.observable(project.geographicInfo.isDefault || false),
183+
overridePrimaryElectorate: ko.observable(project.geographicInfo.overridePrimaryElectorate || false),
184+
overridePrimaryState: ko.observable(project.geographicInfo.overridePrimaryState || false),
184185
primaryState: ko.observable(project.geographicInfo.primaryState),
185186
primaryElectorate: ko.observable(project.geographicInfo.primaryElectorate),
186187
otherStates: ko.observableArray(project.geographicInfo.otherStates || []),
187-
otherElectorates: ko.observableArray(project.geographicInfo.otherElectorates || [])
188+
otherExcludedStates: ko.observableArray(project.geographicInfo.otherExcludedStates || []),
189+
otherElectorates: ko.observableArray(project.geographicInfo.otherElectorates || []),
190+
otherExcludedElectorates: ko.observableArray(project.geographicInfo.otherExcludedElectorates || [])
188191
};
189192
self.geographicInfo.nationwide.subscribe(function(newValue) {
190193
if (newValue) {
@@ -193,6 +196,8 @@ function ProjectViewModel(project) {
193196
self.geographicInfo.primaryElectorate("");
194197
self.geographicInfo.otherStates([]);
195198
self.geographicInfo.otherElectorates([]);
199+
self.geographicInfo.otherExcludedStates([]);
200+
self.geographicInfo.otherExcludedElectorates([]);
196201
}
197202
});
198203

@@ -202,6 +207,8 @@ function ProjectViewModel(project) {
202207
self.geographicInfo.primaryElectorate("");
203208
self.geographicInfo.otherStates([]);
204209
self.geographicInfo.otherElectorates([]);
210+
self.geographicInfo.otherExcludedStates([]);
211+
self.geographicInfo.otherExcludedElectorates([]);
205212
}
206213
});
207214

@@ -462,30 +469,66 @@ function ProjectViewModel(project) {
462469
});
463470
self.associatedProgram(project.associatedProgram); // to trigger the computation of sub-programs
464471
};
465-
466-
self.transients.states = ko.observableArray([]);
467-
self.transients.states.filteredStates = ko.computed(function() {
468-
var primaryState = self.geographicInfo.primaryState(), states = self.transients.states().concat([]);
469-
if (primaryState) {
470-
var index = states.indexOf(primaryState);
472+
self.transients.removeAll = function (source, toRemove) {
473+
for(var i = 0; i < toRemove.length; i++) {
474+
var index = source.indexOf(toRemove[i]);
471475
if (index > -1) {
472-
states.splice(index, 1);
476+
source.splice(index, 1);
473477
}
474478
}
475479

476-
return states;
480+
return source
481+
}
482+
483+
self.transients.states = ko.observableArray([]);
484+
self.transients.statesUnavailableForSelection = function (included) {
485+
var result = [],
486+
primaryState = self.geographicInfo.primaryState(),
487+
otherStates = self.geographicInfo.otherStates() || [],
488+
excludedStates = self.geographicInfo.otherExcludedStates() || [];
489+
490+
if (primaryState)
491+
result.push(primaryState)
492+
if (!included)
493+
result.push.apply(result, otherStates)
494+
else
495+
result.push.apply(result, excludedStates)
496+
return result
497+
}
498+
self.transients.states.statesToIncludeOrExclude = function(include) {
499+
var toRemove = self.transients.statesUnavailableForSelection(include), states = self.transients.states().concat([]);
500+
return self.transients.removeAll(states, toRemove);
501+
};
502+
self.transients.states.statesToInclude = ko.computed(function() {
503+
return self.transients.states.statesToIncludeOrExclude(true);
504+
});
505+
self.transients.states.statesToExclude = ko.computed(function() {
506+
return self.transients.states.statesToIncludeOrExclude(false);
477507
});
478508
self.transients.electorates = ko.observableArray([]);
479-
self.transients.electorates.filteredElectorates = ko.computed(function() {
480-
var primaryElectorate = self.geographicInfo.primaryElectorate(), electorates = self.transients.electorates().concat([]);
481-
if (primaryElectorate) {
482-
var index = electorates.indexOf(primaryElectorate);
483-
if (index > -1) {
484-
electorates.splice(index, 1);
485-
}
486-
}
487-
488-
return electorates;
509+
self.transients.electoratesUnavailableForSelection = function (included) {
510+
var result = [],
511+
primaryElectorate = self.geographicInfo.primaryElectorate(),
512+
otherElectorates = self.geographicInfo.otherElectorates() || [],
513+
excludedElectorates = self.geographicInfo.otherExcludedElectorates() || [];
514+
515+
if (primaryElectorate)
516+
result.push(primaryElectorate)
517+
if (!included)
518+
result.push.apply(result, otherElectorates)
519+
else
520+
result.push.apply(result, excludedElectorates)
521+
return result
522+
}
523+
self.transients.electorates.electoratesToIncludeOrExclude = function(include) {
524+
var toRemove = self.transients.electoratesUnavailableForSelection(include), source = self.transients.electorates().concat([]);
525+
return self.transients.removeAll(source, toRemove);
526+
};
527+
self.transients.electorates.electoratesToInclude = ko.computed(function() {
528+
return self.transients.electorates.electoratesToIncludeOrExclude(true);
529+
});
530+
self.transients.electorates.electoratesToExclude = ko.computed(function() {
531+
return self.transients.electorates.electoratesToIncludeOrExclude(false);
489532
});
490533

491534
self.loadStatesAndElectorates = function() {
@@ -514,11 +557,14 @@ function ProjectViewModel(project) {
514557
self.loadGeographicInfo = function () {
515558
self.geographicInfo.nationwide(project.geographicInfo.nationwide);
516559
self.geographicInfo.statewide(project.geographicInfo.statewide);
517-
self.geographicInfo.isDefault(project.geographicInfo.isDefault);
560+
self.geographicInfo.overridePrimaryElectorate(project.geographicInfo.overridePrimaryElectorate);
561+
self.geographicInfo.overridePrimaryElectorate(project.geographicInfo.overridePrimaryElectorate);
518562
self.geographicInfo.primaryState(project.geographicInfo.primaryState);
519563
self.geographicInfo.primaryElectorate(project.geographicInfo.primaryElectorate);
520564
self.geographicInfo.otherStates(project.geographicInfo.otherStates);
521565
self.geographicInfo.otherElectorates(project.geographicInfo.otherElectorates);
566+
self.geographicInfo.otherExcludedStates(project.geographicInfo.otherExcludedStates);
567+
self.geographicInfo.otherExcludedElectorates(project.geographicInfo.otherExcludedElectorates);
522568
}
523569

524570
self.toJS = function() {

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -1175,13 +1175,15 @@ class ProjectController {
11751175
}
11761176

11771177
@PreAuthorise(accessLevel = 'editor')
1178-
def getSpeciesRecordsFromActivity (String activityId) {
1178+
def getSpeciesRecordsFromActivity (String activityId, String groupBy, String operator) {
11791179
if(!activityId) {
11801180
render status: HttpStatus.SC_BAD_REQUEST, text: [message: 'Activity ID must be supplied'] as JSON
11811181
return
11821182
}
11831183

1184-
render projectService.getSpeciesRecordsFromActivity(activityId) as JSON
1184+
groupBy = groupBy ?: ProjectService.DEFAULT_GROUP_BY
1185+
operator = operator ?: ProjectService.FLATTEN_BY_SUM
1186+
render projectService.getSpeciesRecordsFromActivity(activityId, groupBy, operator) as JSON
11851187
}
11861188

11871189
@PreAuthorise(accessLevel = 'editor')

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

+60-3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ class ProjectService {
3838
static final String PLAN_SUBMITTED = 'submitted'
3939
static final String PLAN_UNLOCKED = 'unlocked for correction'
4040
public static final String DOCUMENT_ROLE_APPROVAL = 'approval'
41+
public static final String FLATTEN_BY_SUM = "SUM"
42+
public static final String FLATTEN_BY_COUNT = "COUNT"
43+
public static final String DEFAULT_GROUP_BY = 'scientificName,vernacularName,scientificNameID,individualsOrGroups'
4144

4245
// All projects can use the Plot Selection and Layout, Plot Description and Opportune modules, but
4346
// we don't want users recording data sets for Plot Selection and Layout so it's not included here.
@@ -2234,12 +2237,12 @@ class ProjectService {
22342237
result
22352238
}
22362239

2237-
List getSpeciesRecordsFromActivity (String activityId) {
2240+
List getSpeciesRecordsFromActivity (String activityId, String groupBy = DEFAULT_GROUP_BY, String operator = FLATTEN_BY_SUM) {
22382241
if (activityId) {
22392242
String displayFormat = 'SCIENTIFICNAME(COMMONNAME)'
22402243
String url = "${grailsApplication.config.getProperty('ecodata.baseUrl')}record/listForActivity/${activityId}"
2241-
def records = webService.getJson(url)?.records
2242-
2244+
List records = webService.getJson(url)?.records
2245+
records = groupRecords(records, groupBy, operator)
22432246
records?.each { record ->
22442247
record.species = [
22452248
scientificName: record.scientificName,
@@ -2254,4 +2257,58 @@ class ProjectService {
22542257
records
22552258
}
22562259
}
2260+
2261+
/**
2262+
* Groups records by the user defined attributes and applies the operator to the numeric values.
2263+
* @param records - dwc records
2264+
* @param groupBy - scientificName, individualsOrGroups, etc.
2265+
* @param operator - SUM
2266+
* @return
2267+
*/
2268+
List groupRecords (List records, String groupBy = DEFAULT_GROUP_BY, String operator = FLATTEN_BY_SUM) {
2269+
if (records && groupBy) {
2270+
List groupByAttributes = groupBy.tokenize(',')
2271+
// Group the records by the user defined attributes such as scientificName, individualsOrGroups, etc.
2272+
Map recordsByGroup = records.groupBy { dwcRecord ->
2273+
groupByAttributes.collect { dwcRecord[it] }
2274+
}
2275+
2276+
// For each group, summarize the records by applying the operator
2277+
Map groupsAndTheirSummary= recordsByGroup.collectEntries { groupKey, groupedRecords ->
2278+
// iterate over the records in the group and summarize them
2279+
Map summaryOfGroupedRecords = groupedRecords.inject([:], { newRecord, recordInGroup ->
2280+
// iterate over the attributes of the record and apply the operator
2281+
recordInGroup.each { dwcAttribute, dwcValue ->
2282+
// sum or count all numeric values that are not used for grouping
2283+
if (dwcAttribute !in groupByAttributes && dwcValue instanceof Number) {
2284+
switch (operator) {
2285+
case FLATTEN_BY_COUNT:
2286+
// implement count operator
2287+
break
2288+
2289+
case FLATTEN_BY_SUM:
2290+
newRecord[dwcAttribute] = (newRecord[dwcAttribute] ?: 0) + dwcValue
2291+
break
2292+
default:
2293+
log.error "Unsupported operator: ${operator}"
2294+
}
2295+
}
2296+
else {
2297+
// if not a numeric value, just copy the value to the new record
2298+
newRecord[dwcAttribute] = dwcValue
2299+
}
2300+
}
2301+
2302+
newRecord
2303+
})
2304+
2305+
// flattens groupedRecords (list) to a map
2306+
[(groupKey): summaryOfGroupedRecords]
2307+
}
2308+
2309+
return groupsAndTheirSummary.values().toList()
2310+
}
2311+
2312+
records
2313+
}
22572314
}

grails-app/views/project/_editProject.gsp

+26-13
Original file line numberDiff line numberDiff line change
@@ -293,45 +293,58 @@
293293
</div>
294294
</td>
295295
</tr>
296-
<tr>
297-
<td></td>
298-
<td>
299-
<div class="form-group form-check">
300-
<input type="checkbox" class="form-check-input" id="geographicInfoBehaviour" data-bind="checked: geographicInfo.isDefault">
301-
<label class="form-check-label" for="geographicInfoBehaviour">Override calculated primary state and electorate with selections below.</label>
302-
</div>
303-
</td>
304-
</tr>
305296
<tr>
306297
<td>
307298
<label for="primaryElectorate">Primary electorate</label>
308299
</td>
309300
<td>
310-
<select id="primaryElectorate" data-bind="options:transients.electorates, value:geographicInfo.primaryElectorate, optionsCaption: 'Select an electorate', disable: geographicInfo.nationwide() || geographicInfo.statewide()" class="select form-control"></select>
301+
<select id="primaryElectorate" data-bind="options:transients.electorates, value:geographicInfo.primaryElectorate, optionsCaption: 'Select an electorate', disable: geographicInfo.nationwide() || geographicInfo.statewide() || !geographicInfo.overridePrimaryElectorate()" class="select form-control" data-validation-engine="validate[required]"></select>
302+
<div class="form-group form-check">
303+
<input type="checkbox" class="form-check-input" id="geographicInfoElectorateBehaviour" name="geographicInfoElectorateBehaviour" data-bind="checked: geographicInfo.overridePrimaryElectorate">
304+
<label class="form-check-label" for="geographicInfoElectorateBehaviour">Override primary electorate with above selection.</label>
305+
</div>
311306
</td>
312307
</tr>
313308
<tr>
314309
<td>
315310
<label for="otherElectorates">Other electorates</label>
316311
</td>
317312
<td>
318-
<select id="otherElectorates" multiple="multiple" data-bind="options:transients.electorates.filteredElectorates, multiSelect2:{value:geographicInfo.otherElectorates, placeholder:''}, disable: geographicInfo.nationwide() || geographicInfo.statewide()" class="select form-control"></select>
313+
<div class="form-group">
314+
<label for="otherElectorates">Include</label>
315+
<select id="otherElectorates" multiple="multiple" data-bind="options:transients.electorates.electoratesToInclude, multiSelect2:{value:geographicInfo.otherElectorates, placeholder:'', tags: false}, disable: geographicInfo.nationwide() || geographicInfo.statewide()" class="select form-control"></select>
316+
</div>
317+
<div class="form-group">
318+
<label for="otherExcludedElectorates">Exclude</label>
319+
<select id="otherExcludedElectorates" multiple="multiple" data-bind="options:transients.electorates.electoratesToExclude, multiSelect2:{value:geographicInfo.otherExcludedElectorates, placeholder:'', tags: false}, disable: geographicInfo.nationwide() || geographicInfo.statewide()" class="select form-control"></select>
320+
</div>
319321
</td>
320322
</tr>
321323
<tr>
322324
<td>
323325
<label for="primaryState">Primary state</label>
324326
</td>
325327
<td>
326-
<select id="primaryState" data-bind="options:transients.states, value:geographicInfo.primaryState, optionsCaption: 'Select a state', disable: geographicInfo.nationwide()" class="select form-control"></select>
328+
<select id="primaryState" data-bind="options:transients.states, value:geographicInfo.primaryState, optionsCaption: 'Select a state', disable: geographicInfo.nationwide() || !geographicInfo.overridePrimaryState()" class="select form-control" data-validation-engine="validate[required]"></select>
329+
<div class="form-group form-check">
330+
<input type="checkbox" class="form-check-input" id="geographicInfoStateBehaviour" name="geographicInfoStateBehaviour" data-bind="checked: geographicInfo.overridePrimaryState">
331+
<label class="form-check-label" for="geographicInfoStateBehaviour">Override primary state with above selection.</label>
332+
</div>
327333
</td>
328334
</tr>
329335
<tr>
330336
<td>
331337
<label for="otherStates">Other states</label>
332338
</td>
333339
<td>
334-
<select id="otherStates" multiple="multiple" data-bind="options:transients.states.filteredStates, multiSelect2:{value:geographicInfo.otherStates, placeholder:''}, disable: geographicInfo.nationwide()" class="select form-control"></select>
340+
<div class="form-group">
341+
<label for="otherStates">Include</label>
342+
<select id="otherStates" multiple="multiple" data-bind="options:transients.states.statesToInclude, multiSelect2:{value:geographicInfo.otherStates, placeholder:'', tags: false}, disable: geographicInfo.nationwide()" class="select form-control"></select>
343+
</div>
344+
<div class="form-group">
345+
<label for="otherExcludedStates">Exclude</label>
346+
<select id="otherExcludedStates" multiple="multiple" data-bind="options:transients.states.statesToExclude, multiSelect2:{value:geographicInfo.otherExcludedStates, placeholder:'', tags: false}, disable: geographicInfo.nationwide()" class="select form-control"></select>
347+
</div>
335348
</td>
336349
</tr>
337350
</tbody>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
load( "../../utils/audit.js");
2+
var count = 0;
3+
db.project.find({isMERIT: true, "geographicInfo.isDefault": true}).forEach(function (project) {
4+
project.geographicInfo.overridePrimaryElectorate = true
5+
project.geographicInfo.overridePrimaryState = true
6+
db.project.updateOne({projectId: project.projectId}, {$set: {geographicInfo: project.geographicInfo}});
7+
audit(project, project.projectId, 'au.org.ala.ecodata.Project', "system")
8+
print("updated " + project.projectId);
9+
count ++;
10+
});
11+
12+
print("total updates " + count);

src/test/groovy/au/org/ala/merit/ProjectControllerSpec.groovy

+2-2
Original file line numberDiff line numberDiff line change
@@ -949,10 +949,10 @@ class ProjectControllerSpec extends Specification implements ControllerUnitTest<
949949
def "Get species record for an activity id" (String activityId, int statusCode, int numberOfCalls, def data) {
950950
when:
951951
request.method = 'GET'
952-
controller.getSpeciesRecordsFromActivity (activityId)
952+
controller.getSpeciesRecordsFromActivity (activityId, null, null)
953953
954954
then:
955-
numberOfCalls * projectService.getSpeciesRecordsFromActivity (activityId) >> data
955+
numberOfCalls * projectService.getSpeciesRecordsFromActivity (activityId, _, _) >> data
956956
controller.response.status == statusCode
957957
958958
where:

0 commit comments

Comments
 (0)