From 0707d0a5faa2ccc80f1649032aed6e0e6a28f135 Mon Sep 17 00:00:00 2001 From: andrewbrazzatti Date: Fri, 11 Aug 2023 11:10:40 +0930 Subject: [PATCH 01/13] Updated api documentation to fix error in list users endpoint. Currently users page and page size --- views/default/default/apidocsapib.ejs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/views/default/default/apidocsapib.ejs b/views/default/default/apidocsapib.ejs index 17c3e0d23c..e5e720e9c1 100644 --- a/views/default/default/apidocsapib.ejs +++ b/views/default/default/apidocsapib.ejs @@ -528,11 +528,11 @@ The ReDBox Portal API provides authorized access to manage functions. ## User Management Actions [/<%= branding %>/<%= portal %>/api/users] -### List users in the system [GET /<%= branding %>/<%= portal %>/api/users{?searchBy,query,start,rows}] +### List users in the system [GET /<%= branding %>/<%= portal %>/api/users{?searchBy,query,page,pageSize}] + Parameters - + start: `0` (number, optional) - The index number for the first value to return from the result set. - + Default: 0 - + rows: `10` (number,optional) - The number of records to return in the request + + page: `1` (number, optional) - The page index of the result set to show. + + Default: 1 + + pageSize: `10` (number,optional) - The number of records to return in a page + Default: 10 + searchBy: `type` (string, optional) - The attribute to search by. e.g. email + query: `local` (string, optional) - The value to query. Only exact matches. From fea334c28d2436c6c3b6a9fc1f3a12c063423396 Mon Sep 17 00:00:00 2001 From: andrewbrazzatti Date: Wed, 30 Aug 2023 10:11:00 +0930 Subject: [PATCH 02/13] Handled setting the title of the vocab component when the field isn't visible and becomes visible using the setVisibility functionality (#1504) --- angular/shared/form/field-vocab.component.ts | 85 +++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/angular/shared/form/field-vocab.component.ts b/angular/shared/form/field-vocab.component.ts index 08499cba6f..20036a06c1 100644 --- a/angular/shared/form/field-vocab.component.ts +++ b/angular/shared/form/field-vocab.component.ts @@ -67,8 +67,10 @@ export class VocabField extends FieldBase { public isEmbedded: boolean; public groupClass: string; public inputClass: string; + storedEventData: null; @Output() onItemSelect: EventEmitter = new EventEmitter(); + constructor(options: any, injector: any) { super(options, injector); @@ -215,6 +217,12 @@ export class VocabField extends FieldBase { public getTitle(data: any): string { let title = ''; + if(!data) { + if(this.storedEventData != null) { + data = _.clone(this.storedEventData); + this.storedEventData == null; + } + } if (data) { if (_.isString(data)) { return data; @@ -283,7 +291,11 @@ export class VocabField extends FieldBase { public setValue(value: any, emitEvent: boolean = true, updateTitle: boolean = true) { this.formModel.setValue(value, { emitEvent: emitEvent }); if (updateTitle) { - this.component.ngCompleter.ctrInput.nativeElement.value = this.getTitle(value); + if(!_.isUndefined(this.component.ngCompleter)) { + this.component.ngCompleter.ctrInput.nativeElement.value = this.getTitle(value); + } else { + this.storedEventData = _.clone(value); + } } } @@ -330,6 +342,66 @@ export class VocabField extends FieldBase { } } + public setVisibility(data, eventConf:any = {}) { + let newVisible = this.visible; + if (_.isArray(this.visibilityCriteria)) { + // save the value of this data in a map, so we can run complex conditional logic that depends on one or more fields + if (!_.isEmpty(eventConf) && !_.isEmpty(eventConf.srcName)) { + this.subscriptionData[eventConf.srcName] = data; + } + // only run the function set if we have all the data... + if (_.size(this.subscriptionData) == _.size(this.visibilityCriteria)) { + newVisible = true; + _.each(this.visibilityCriteria, (visibilityCriteria) => { + const dataEntry = this.subscriptionData[visibilityCriteria.fieldName]; + newVisible = newVisible && this.execVisibilityFn(dataEntry, visibilityCriteria); + }); + + } + } else + if (_.isObject(this.visibilityCriteria) && _.get(this.visibilityCriteria, 'type') == 'function') { + newVisible = this.execVisibilityFn(data, this.visibilityCriteria); + } else { + newVisible = _.isEqual(data, this.visibilityCriteria); + } + const that = this; + setTimeout(() => { + if (!newVisible) { + if (that.visible) { + // remove validators + if (that.formModel) { + if(that['disableValidators'] != null && typeof(that['disableValidators']) == 'function') { + that['disableValidators'](); + } else { + that.formModel.clearValidators(); + } + that.formModel.updateValueAndValidity(); + } + } + } else { + if (!that.visible) { + // restore validators + if (that.formModel) { + if(that['enableValidators'] != null && typeof(that['enableValidators']) == 'function') { + that['enableValidators'](); + } else { + that.formModel.setValidators(that.validators); + } + that.formModel.updateValueAndValidity(); + setTimeout(() => { + that.component.ngCompleter.ctrInput.nativeElement.value = that.getTitle(null); + }); + } + } + } + that.visible = newVisible; + }); + if(eventConf.returnData == true) { + return data; + } + + } + } export function objectRequired(): ValidationErrors|null { @@ -343,6 +415,7 @@ export function objectRequired(): ValidationErrors|null { class ExternalLookupDataService extends Subject implements CompleterData { + storedEventData:any = null; constructor(private url: string, private http: Http, @@ -384,7 +457,14 @@ class ExternalLookupDataService extends Subject implements Comp getTitle(data: any): string { let title = ''; - if (data) { + if (data == null) { + if(this.storedEventData != null) { + data = _.clone(this.storedEventData); + } + this.storedEventData = null; + } + + if(data){ if (_.isString(this.titleFieldDelim)) { _.forEach(this.titleFieldArr, (titleFld: string) => { const titleVal = _.get(data, titleFld); @@ -393,6 +473,7 @@ class ExternalLookupDataService extends Subject implements Comp } }); } else { + // // expecting a delim pair array, 'prefix', 'suffix' // _.forEach(this.titleFieldArr, (titleFld: string, idx) => { // const delimPair = this.titleFieldDelim[idx]; From 4056762dfc1af1a54aa51f78d3e32796f8990e8d Mon Sep 17 00:00:00 2001 From: andrewbrazzatti Date: Wed, 30 Aug 2023 10:13:08 +0930 Subject: [PATCH 03/13] Added map initialisation when the map is made visible (#1505) --- angular/shared/form/field-map.component.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/angular/shared/form/field-map.component.ts b/angular/shared/form/field-map.component.ts index 28096d3655..6700b9979d 100644 --- a/angular/shared/form/field-map.component.ts +++ b/angular/shared/form/field-map.component.ts @@ -258,6 +258,13 @@ export class MapField extends FieldBase { }); } + public setVisibility(data, eventConf:any = {}) { + let that = this; + super.setVisibility(data,eventConf); + setTimeout(function() { + that.initMap(that.map,that); + }) + } postInit(value: any) { if (!_.isEmpty(value)) { From 9b68c597b6c8dabeb18bbed52643a4ec845d2842 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Aug 2023 10:03:18 +0930 Subject: [PATCH 04/13] Bump mongodb from 4.16.0 to 4.17.0 (#1507) Bumps [mongodb](https://github.com/mongodb/node-mongodb-native) from 4.16.0 to 4.17.0. - [Release notes](https://github.com/mongodb/node-mongodb-native/releases) - [Changelog](https://github.com/mongodb/node-mongodb-native/blob/v4.17.0/HISTORY.md) - [Commits](https://github.com/mongodb/node-mongodb-native/compare/v4.16.0...v4.17.0) --- updated-dependencies: - dependency-name: mongodb dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 40 +++++++++++++++++++++++++++++----------- package.json | 2 +- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a327f3ec0..a43d017dde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,7 @@ "lucene-escape-query": "^1.0.1", "luxon": "^3.3.0", "moment": "^2.29.4", - "mongodb": "^4.16.0", + "mongodb": "^4.17.0", "node-cache": "^5.1.2", "node-schedule": "^2.1.1", "nodemailer": "^6.9.3", @@ -4469,6 +4469,15 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.0.tgz", + "integrity": "sha512-Xfijy7HvfzzqiOAhAepF4SGN5e9leLkMvg/OPOF97XemjfVCYN/oWa75wnkc6mltMSTwY+XlbhWgUOJmkFspSw==", + "optional": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -13900,12 +13909,12 @@ } }, "node_modules/mongodb": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.16.0.tgz", - "integrity": "sha512-0EB113Fsucaq1wsY0dOhi1fmZOwFtLOtteQkiqOXGklvWMnSH3g2QS53f0KTP+/6qOkuoXE2JksubSZNmxeI+g==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.17.0.tgz", + "integrity": "sha512-LZGMIPjPfWEfhPJATk1s9IvVTD18tyfKdT/0blCMih5vGagk2SwA9wFAUPMdtJpTrhXmyfGgwAaMkvneX2bn2A==", "dependencies": { "bson": "^4.7.2", - "mongodb-connection-string-url": "^2.5.4", + "mongodb-connection-string-url": "^2.6.0", "socks": "^2.7.1" }, "engines": { @@ -13913,7 +13922,7 @@ }, "optionalDependencies": { "@aws-sdk/credential-providers": "^3.186.0", - "saslprep": "^1.0.3" + "@mongodb-js/saslprep": "^1.1.0" } }, "node_modules/mongodb-connection-string-url": { @@ -26807,6 +26816,15 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "@mongodb-js/saslprep": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.0.tgz", + "integrity": "sha512-Xfijy7HvfzzqiOAhAepF4SGN5e9leLkMvg/OPOF97XemjfVCYN/oWa75wnkc6mltMSTwY+XlbhWgUOJmkFspSw==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -34149,14 +34167,14 @@ } }, "mongodb": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.16.0.tgz", - "integrity": "sha512-0EB113Fsucaq1wsY0dOhi1fmZOwFtLOtteQkiqOXGklvWMnSH3g2QS53f0KTP+/6qOkuoXE2JksubSZNmxeI+g==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.17.0.tgz", + "integrity": "sha512-LZGMIPjPfWEfhPJATk1s9IvVTD18tyfKdT/0blCMih5vGagk2SwA9wFAUPMdtJpTrhXmyfGgwAaMkvneX2bn2A==", "requires": { "@aws-sdk/credential-providers": "^3.186.0", + "@mongodb-js/saslprep": "^1.1.0", "bson": "^4.7.2", - "mongodb-connection-string-url": "^2.5.4", - "saslprep": "^1.0.3", + "mongodb-connection-string-url": "^2.6.0", "socks": "^2.7.1" }, "dependencies": { diff --git a/package.json b/package.json index 7ee1ab55d8..e0da83b36a 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "lucene-escape-query": "^1.0.1", "luxon": "^3.3.0", "moment": "^2.29.4", - "mongodb": "^4.16.0", + "mongodb": "^4.17.0", "node-cache": "^5.1.2", "node-schedule": "^2.1.1", "nodemailer": "^6.9.3", From 2e5823a7135a7bdb5e25e0d2f5b6740498ec2f46 Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Mon, 4 Sep 2023 00:42:09 +0000 Subject: [PATCH 05/13] Fixed validators that become enabled when made visible even when the field isn't configured to be required --- angular/shared/form/field-contributor.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/angular/shared/form/field-contributor.component.ts b/angular/shared/form/field-contributor.component.ts index 19ab54105f..ef48ba6f44 100644 --- a/angular/shared/form/field-contributor.component.ts +++ b/angular/shared/form/field-contributor.component.ts @@ -143,6 +143,7 @@ export class ContributorField extends FieldBase implements CustomValidation this.validators['family_name'] = [Validators.required]; this.validators['given_name'] = [Validators.required]; } + // Resolves: #605 // now that we've set the default validators... we read the config to override if (!_.isEmpty(this.activeValidators)) { @@ -158,6 +159,7 @@ export class ContributorField extends FieldBase implements CustomValidation } }); } + this.findRelationshipFor = options['findRelationshipFor'] || ''; this.findRelationship = options['findRelationship'] || null; this.relationshipFor = options['relationshipFor'] || ''; @@ -214,6 +216,8 @@ export class ContributorField extends FieldBase implements CustomValidation if (this.required) { this.enableValidators(); } else { + // disable the validators otherwise they'll get enabled on show/hide + this.validators = {}; // if splitting names, attach handler to individual input form control if (this.splitNames) { const reqFields = ['family_name', 'given_name']; From 48dd5d5ebe1b2da8979af669b81e1ba5757da8ba Mon Sep 17 00:00:00 2001 From: Andrew Brazzatti Date: Mon, 4 Sep 2023 01:07:15 +0000 Subject: [PATCH 06/13] Changed map visibility to use display attribute as removing and adding the map to the dom causes initialisation issues --- angular/shared/form/field-map.component.ts | 137 ++++++++++++--------- angular/shared/form/field-map.html | 4 +- 2 files changed, 80 insertions(+), 61 deletions(-) diff --git a/angular/shared/form/field-map.component.ts b/angular/shared/form/field-map.component.ts index 28096d3655..d45c70f3b5 100644 --- a/angular/shared/form/field-map.component.ts +++ b/angular/shared/form/field-map.component.ts @@ -22,11 +22,11 @@ import { FieldBase } from './field-base'; import { FormGroup, FormControl, Validators } from '@angular/forms'; import * as _ from "lodash"; import { RecordsService } from './records.service'; -import { Map, GeoJSON, } from 'leaflet'; +import { Map, GeoJSON, } from 'leaflet'; declare var omnivore: any; declare var L: any; declare var jQuery: any; -declare function require(name:string); +declare function require(name: string); /** * Map Model @@ -44,7 +44,7 @@ export class MapField extends FieldBase { layerGeoJSON: any = {}; map: Map; - importFailed:boolean = false; + importFailed: boolean = false; layers = []; drawnItems: any = new L.FeatureGroup(); googleMaps = L.tileLayer('http://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}', { @@ -150,10 +150,10 @@ export class MapField extends FieldBase { this.layerGeoJSON = options.value; this.mainTabId = options['mainTabId'] || null; - this.coordinatesHelp = this.getTranslated(options.coordinatesHelp, undefined); + this.coordinatesHelp = this.getTranslated(options.coordinatesHelp, undefined); this.kmlGeoJsonLabel = this.getTranslated(options.kmlGeoJsonLabel, "Enter KML or GeoJSON"); - this.importButtonLabel = this.getTranslated(options.importButtonLabel, "Import"); - this.invalidDataMessage = this.getTranslated(options.invalidDataMessage, "Entered text is not valid KML or GeoJSON"); + this.importButtonLabel = this.getTranslated(options.importButtonLabel, "Import"); + this.invalidDataMessage = this.getTranslated(options.invalidDataMessage, "Entered text is not valid KML or GeoJSON"); } onMapReady(map: Map) { @@ -167,8 +167,15 @@ export class MapField extends FieldBase { if (this.tabId === null) { - map.invalidateSize(); - map.fitBounds(this.drawnItems.getBounds()); + if (this.visible) { + map.invalidateSize(); + try { + // if there are no layers present this will throw an error + map.fitBounds(this.drawnItems.getBounds()); + } catch (e) { + + } + } } else { if (this.editMode) { // Note: this assumes the tabId is unqiue in the page, which may not be when there are multiple tab layouts. @@ -193,18 +200,18 @@ export class MapField extends FieldBase { public initMap(map, that) { map.invalidateSize(); - try { - // if there are no layers present this will throw an error - map.fitBounds(this.drawnItems.getBounds()); - } catch (e) { + try { + // if there are no layers present this will throw an error + map.fitBounds(this.drawnItems.getBounds()); + } catch (e) { - } + } } registerMapEventHandlers(map: Map) { let that = this; - map.on(L.Draw.Event.CREATED, function(e: any) { + map.on(L.Draw.Event.CREATED, function (e: any) { var type = e.layerType, layer = e.layer; that.layers.push(layer); @@ -213,12 +220,12 @@ export class MapField extends FieldBase { return false; }); - map.on('draw:edited', function(e: any) { + map.on('draw:edited', function (e: any) { let layers = e.layers; let that2 = that; - layers.eachLayer(function(layer) { - let layerIndex = _.findIndex(that2.layers, function(o) { return o._leaflet_id == layer._leaflet_id; }); - if(layerIndex == -1) { + layers.eachLayer(function (layer) { + let layerIndex = _.findIndex(that2.layers, function (o) { return o._leaflet_id == layer._leaflet_id; }); + if (layerIndex == -1) { that2.layers.push(layer); } else { that2.layers[layerIndex] = layer; @@ -227,21 +234,21 @@ export class MapField extends FieldBase { }); }); - map.on('draw:editstop', function(e: any) { + map.on('draw:editstop', function (e: any) { that.layerGeoJSON = L.featureGroup(that.layers).toGeoJSON(); that.setValue(that.layerGeoJSON); }); - map.on('draw:deletestop', function(e: any) { + map.on('draw:deletestop', function (e: any) { that.layerGeoJSON = L.featureGroup(that.layers).toGeoJSON(); that.setValue(that.layerGeoJSON); }); - map.on('draw:deleted', function(e: any) { + map.on('draw:deleted', function (e: any) { let layers = e.layers; let that2 = that; - layers.eachLayer(function(layer) { - _.remove(that2.layers, function(o) { return o._leaflet_id == layer._leaflet_id; }); + layers.eachLayer(function (layer) { + _.remove(that2.layers, function (o) { return o._leaflet_id == layer._leaflet_id; }); }); }); } @@ -258,6 +265,15 @@ export class MapField extends FieldBase { }); } + public setVisibility(data, eventConf: any = {}) { + let that = this; + super.setVisibility(data, eventConf); + setTimeout(function () { + if (that.visible) { + that.initMap(that.map, that); + } + }, 100) + } postInit(value: any) { if (!_.isEmpty(value)) { @@ -283,6 +299,9 @@ export class MapField extends FieldBase { this.formModel.patchValue(this.layerGeoJSON, { emitEvent: emitEvent, emitModelToViewChange: true }); this.formModel.markAsTouched(); this.formModel.markAsDirty(); + } else { + this.setEmptyValue(); + this.drawnItems.clearLayers(); } } @@ -290,9 +309,9 @@ export class MapField extends FieldBase { this.layerGeoJSON = {}; return this.layerGeoJSON; } - + reactEvent(eventName: string, eventData: any, origData: any) { - super.reactEvent(eventName, eventData,origData); + super.reactEvent(eventName, eventData, origData); if (!_.isEmpty(this.formModel.value)) { this.layerGeoJSON = this.formModel.value; this.drawLayers(); @@ -303,43 +322,43 @@ export class MapField extends FieldBase { } importData() { - if(this.importDataString.length > 0) { + if (this.importDataString.length > 0) { try { - if(this.importDataString.indexOf("<") == 0) { - //probably KML - let parsedLayers = omnivore.kml.parse(this.importDataString); - if(parsedLayers.getLayers().length == 0) { - this.importFailed = true; - return false; + if (this.importDataString.indexOf("<") == 0) { + //probably KML + let parsedLayers = omnivore.kml.parse(this.importDataString); + if (parsedLayers.getLayers().length == 0) { + this.importFailed = true; + return false; + } + let that = this; + parsedLayers.eachLayer(layer => { + layer.addTo(that.drawnItems); + that.layers.push(layer); + that.layerGeoJSON = L.featureGroup(that.layers).toGeoJSON(); + this.drawLayers(); + that.map.fitBounds(that.drawnItems.getBounds()); + }); + this.importDataString = ""; + this.importFailed = false; + } else { + let parsedLayers = L.geoJSON(JSON.parse(this.importDataString)); + let that = this; + parsedLayers.eachLayer(layer => { + layer.addTo(that.drawnItems); + that.layers.push(layer); + that.layerGeoJSON = L.featureGroup(that.layers).toGeoJSON(); + this.drawLayers(); + that.map.fitBounds(that.drawnItems.getBounds()); + }); + this.importDataString = ""; + this.importFailed = false; } - let that = this; - parsedLayers.eachLayer(layer => { - layer.addTo(that.drawnItems); - that.layers.push(layer); - that.layerGeoJSON = L.featureGroup(that.layers).toGeoJSON(); - this.drawLayers(); - that.map.fitBounds(that.drawnItems.getBounds()); - }); - this.importDataString = ""; - this.importFailed = false; - } else { - let parsedLayers = L.geoJSON(JSON.parse(this.importDataString)); - let that = this; - parsedLayers.eachLayer(layer => { - layer.addTo(that.drawnItems); - that.layers.push(layer); - that.layerGeoJSON = L.featureGroup(that.layers).toGeoJSON(); - this.drawLayers(); - that.map.fitBounds(that.drawnItems.getBounds()); - }); - this.importDataString = ""; - this.importFailed = false; + this.layerGeoJSON = L.featureGroup(this.layers).toGeoJSON(); + this.setValue(this.layerGeoJSON); + } catch (e) { + this.importFailed = true; } - this.layerGeoJSON = L.featureGroup(this.layers).toGeoJSON(); - this.setValue(this.layerGeoJSON); - } catch (e) { - this.importFailed = true; - } } diff --git a/angular/shared/form/field-map.html b/angular/shared/form/field-map.html index 684df5c582..66436e8c36 100644 --- a/angular/shared/form/field-map.html +++ b/angular/shared/form/field-map.html @@ -1,5 +1,5 @@ - -
+ +