Skip to content

Commit

Permalink
Merge branch 'master' of github.com:openforis/arena into feature/expr…
Browse files Browse the repository at this point in the history
…ession-editor-functions
  • Loading branch information
SteRiccio committed Sep 27, 2024
2 parents 9305af0 + bc73bd7 commit f54b24b
Show file tree
Hide file tree
Showing 133 changed files with 2,033 additions and 998 deletions.
4 changes: 4 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,7 @@ ADMIN_EMAIL=
# Admin user password: used only when default system admin user is created the first time
# it MUST BE DELETED after the first startup
ADMIN_PASSWORD=

# Experimental features
# if set to true, experimental features will be enabled in the UI
EXPERIMENTAL_FEATURES=false
2 changes: 0 additions & 2 deletions common/analysis/chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export const keys = {
export const keysProps = {
labels: ObjectUtils.keysProps.labels,
descriptions: ObjectUtils.keysProps.descriptions,
cycles: ObjectUtils.keysProps.cycles,
hasSamplingDesign: 'hasSamplingDesign',
samplingDesign: 'samplingDesign',
analysisNodeDefs: 'analysisNodeDefs',
Expand All @@ -46,7 +45,6 @@ export const {
getUuid,
getProps,
getPropsDiff,
getCycles,
getDateCreated,
getDateModified,
getDescriptions,
Expand Down
4 changes: 2 additions & 2 deletions common/analysis/samplingNodeDefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ const determinePlotAreaNodeDefs = ({ survey, chain }) => {
const chainUuid = Chain.getUuid(chain)
const cycleKeys = Survey.getCycleKeys(survey)
const baseUnitNodeDef = Survey.getBaseUnitNodeDef({ chain })(survey)
const descentants = Survey.getDescendantsAndSelf({ nodeDef: baseUnitNodeDef })(survey)
const descendantEntities = descentants.filter(
const descendants = Survey.getNodeDefDescendantsAndSelf({ nodeDef: baseUnitNodeDef })(survey)
const descendantEntities = descendants.filter(
(descendantEntity) =>
NodeDef.isEntity(descendantEntity) && (NodeDef.isMultiple(descendantEntity) || NodeDef.isRoot(descendantEntity))
)
Expand Down
6 changes: 1 addition & 5 deletions common/model/db/tables/chain/select.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import * as Chain from '@common/analysis/chain'

function _getSelectFields({ count, includeScript }) {
if (count) {
return ['count(*)']
Expand All @@ -14,15 +12,14 @@ function _getSelectFields({ count, includeScript }) {
* Generate the select query for the processing_chain table by the given parameters.
*
* @param {!object} params - The query parameters.
* @param {string} [params.cycle=null] - The survey cycle to filter by.
* @param {string} [params.chainUuid=null] - The chain uuid to filter by.
* @param {boolean} [params.count=false] - Whether to count.
* @param {boolean} [params.includeScript=false] - Whether to include R scripts.
*
* @returns {string} - The select query.
*/
export function getSelect(params) {
const { cycle = null, chainUuid = null, count = false, includeScript = false } = params
const { chainUuid = null, count = false, includeScript = false } = params

this.getSelectFields = _getSelectFields.bind(this)

Expand All @@ -31,7 +28,6 @@ export function getSelect(params) {
FROM
${this.nameAliased}
${cycle ? `WHERE (${this.columnProps})->'${Chain.keysProps.cycles}' @> '"${cycle}"'` : ''}
${chainUuid ? `WHERE ${this.columnUuid} = '${chainUuid}'` : ''}
`
}
1 change: 1 addition & 0 deletions common/model/db/tables/dataNodeDef/columnNodeDef.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const colTypesGetterByType = {
},
[nodeDefType.date]: () => [SQL.types.date],
[nodeDefType.decimal]: () => [SQL.types.decimal],
[nodeDefType.geo]: () => [SQL.types.varchar],
[nodeDefType.entity]: () => [SQL.types.uuid],
[nodeDefType.file]: () => [SQL.types.uuid, SQL.types.varchar],
[nodeDefType.integer]: () => [SQL.types.bigint],
Expand Down
6 changes: 6 additions & 0 deletions core/arrayUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ const sortByProps = (props) => (array) =>
return 0
})

const toArray = (value) => {
if (Objects.isNil(value)) return value
return Array.isArray(value) ? value : [value]
}

export const ArrayUtils = {
addOrRemoveItem,
addIfNotEmpty,
Expand All @@ -91,4 +96,5 @@ export const ArrayUtils = {
first,
last,
sortByProps,
toArray,
}
1 change: 1 addition & 0 deletions core/expressionParser/helpers/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const functionNames = {
count: 'count',
distance: 'distance',
first: 'first',
geoPolygon: 'geoPolygon',
includes: 'includes',
index: 'index',
isEmpty: 'isEmpty',
Expand Down
39 changes: 30 additions & 9 deletions core/i18n/resources/en/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@ Do you want to proceed?`,
pageNotFound: 'Page not found',
},

geo: {
area: 'Area',
vertices: 'Vertices',
perimeter: 'Perimeter',
},

files: {
header: 'Files',
missing: ' Missing files: {{count}}',
Expand Down Expand Up @@ -802,8 +808,8 @@ Merge cannot be performed.`,
mapView: {
createRecord: 'Create new record',
editRecord: 'Edit record',
altitude: 'Altitude (m)',
earthMap: 'Earth Map',
elevation: 'Elevation (m)',
locationEditInfo: 'Double click on the map or drag the marker to update the location',
locationUpdated: 'Location updated',
options: {
Expand Down Expand Up @@ -1156,8 +1162,8 @@ $t(common.cantUndoWarning)`,
invalid: 'Invalid expression: {{details}}',
missingFunctionParameters: 'Missing function parameters',
undefinedFunction: 'Undefined function: {{name}}',
functionHasTooFewArguments: 'Function {{fnName}} requires at least {{minArity}} (got {{numArgs}})',
functionHasTooManyArguments: 'Function {{fnName}} only accepts at most {{maxArity}} (got {{numArgs}})',
functionHasTooFewArguments: 'Function {{fnName}} requires at least {{minArity}} arguments (got {{numArgs}})',
functionHasTooManyArguments: 'Function {{fnName}} only accepts at most {{maxArity}} arguments (got {{numArgs}})',
},

// ====== Help views
Expand Down Expand Up @@ -1203,14 +1209,14 @@ $t(common.appNameFull)
isEmpty: 'Returns true if the argument has no value specified',
isNotEmpty: 'Returns true if the argument has some value specified',
last: 'Returns the last value or node of the specified multiple attribute or entity',
ln: 'Take the natural logarithm of x',
log10: 'Take the base 10 logarithm of x',
max: 'Take the maximum of the arguments',
min: 'Take the minimum of the arguments',
ln: 'Returns the natural logarithm of X',
log10: 'Returns the base 10 logarithm of X',
max: 'Returns the maximum of the arguments',
min: 'Returns the minimum of the arguments',
now: 'Returns the current date or time',
parent: 'Returns the parent entity of the specified node',
pow: 'Raise a number X to the power P',
rowIndex: 'Gives the current row index',
pow: 'Returns the value of a base raised to a power',
rowIndex: 'Returns the current table row (or form) index',
taxonProp: 'Returns the value of the specified $t(extraProp.label) of a taxon having the specified code',
uuid: 'Generates a UUID (universally unique identifier) that can be used as identifier (e.g. as a key attribute of an enity)',
// SQL functions
Expand All @@ -1223,6 +1229,10 @@ $t(common.appNameFull)
},
basicProps: {
analysis: 'Analysis',
autoIncrementalKey: {
label: 'Auto incremental',
info: 'Value will be automatically generated',
},
displayAs: 'Display as',
displayIn: 'Display in',
entitySource: 'Entity Source',
Expand All @@ -1247,6 +1257,7 @@ $t(common.appNameFull)
areaBasedEstimate: 'Area-based estimate',
defaultValues: 'Default values',
defaultValueEvaluatedOneTime: 'Default value evaluated only one time',
defaultValuesNotEditableForAutoIncrementalKey: 'Default values not editable because auto incremental key is set',
hidden: 'Hide in entry form',
hiddenInMobile: 'Hidden in Arena Mobile',
hiddenWhenNotRelevant: 'Hidden when not relevant',
Expand Down Expand Up @@ -1361,6 +1372,7 @@ E.g. this.region = region_attribute_name
coordinate: 'Coordinate',
date: 'Date',
decimal: 'Decimal',
geo: 'Geospatial',
entity: 'Table or form',
file: 'File',
integer: 'Integer',
Expand Down Expand Up @@ -1429,6 +1441,11 @@ $t(surveyForm.formEntryActions.confirmPromote)`,
altitude: 'Altitude',
altitudeAccuracy: 'Altitude accuracy',
},
nodeDefGeo: {
confirmDelete: 'Delete this Geospatial value?',
geoJSON: 'GeoJSON',
invalidGeoJsonFileUploaded: 'Invalid GeoJSON file uploaded',
},
nodeDefEntityForm: {
addNewEntity: 'Add new {{name}}',
confirmDelete: 'Are you sure you want to delete this entity?',
Expand All @@ -1446,6 +1463,10 @@ $t(surveyForm.formEntryActions.confirmPromote)`,
fileUuid: 'File uuid',
fileName: 'File name',
},
nodeDefsTreeSelectMode: {
allNodeDefs: 'All nodes',
onlyPages: 'Only pages',
},
step: {
entry: 'Entry',
cleansing: 'Cleansing',
Expand Down
46 changes: 46 additions & 0 deletions core/numberUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,39 @@ BigNumber.config({
},
})

export const areaUnits = {
squareMeter: 'squareMeter',
squareFoot: 'squareFoot',
acre: 'acre',
hectare: 'hectare',
}

const areaUnitToSquareMetersConversionFactor = {
[areaUnits.acre]: 4046.85642199999983859016,
[areaUnits.hectare]: 10000,
[areaUnits.squareMeter]: 1,
[areaUnits.squareFoot]: 0.09290304,
}

export const lengthUnits = {
meter: 'meter',
foot: 'foot',
}

const lengthUnitToMetersConversionFactor = {
[lengthUnits.meter]: 1,
[lengthUnits.foot]: 0.3048,
}

export const abbreviationByUnit = {
[areaUnits.squareMeter]: 'm²',
[areaUnits.squareFoot]: 'ft²',
[areaUnits.acre]: 'ac',
[areaUnits.hectare]: 'ha',
[lengthUnits.meter]: 'm',
[lengthUnits.foot]: 'ft',
}

export const toNumber = (num) => (Objects.isEmpty(num) ? NaN : Number(num))

export const isInteger = A.pipe(toNumber, Number.isInteger)
Expand Down Expand Up @@ -51,3 +84,16 @@ export const roundToPrecision = (value, precision = NaN) => {
* @returns {string} - The formatted value or null if the value was null.
*/
export const formatInteger = (value) => formatDecimal(value, 0)

/**
* Returns the modulus of the specified value. The result will always be a positive number.
* @param {!number} modulus - The modulus to apply.
* @returns {number} - The result of the modulus (always positive or 0).
*/
export const mod = (modulus) => (value) => ((value % modulus) + modulus) % modulus

export const squareMetersToUnit = (unit) => (value) =>
Objects.isNil(value) ? NaN : value / areaUnitToSquareMetersConversionFactor[unit]

export const metersToUnit = (unit) => (value) =>
Objects.isNil(value) ? NaN : value / lengthUnitToMetersConversionFactor[unit]
8 changes: 8 additions & 0 deletions core/objectUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ export const setInPath =

export const dissocTemporary = R.unless(R.isNil, R.dissoc(keys.temporary))

export const keepNonEmptyProps = (obj) =>
Object.entries(obj).reduce((acc, [key, value]) => {
if (!isBlank(value)) {
acc[key] = value
}
return acc
}, {})

// ====== UTILS / uuid
const _getProp = (propNameOrExtractor) => (item) =>
typeof propNameOrExtractor === 'string' ? R.path(propNameOrExtractor.split('.'))(item) : propNameOrExtractor(item)
Expand Down
2 changes: 2 additions & 0 deletions core/processUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ const ENV = {
fileStorageAwsSecretAccessKey: process.env.FILE_STORAGE_AWS_SECRET_ACCESS_KEY,
fileStorageAwsS3BucketName: process.env.FILE_STORAGE_AWS_S3_BUCKET_NAME,
fileStorageAwsS3BucketRegion: process.env.FILE_STORAGE_AWS_S3_BUCKET_REGION,
// Experimental features
experimentalFeatures: isTrue(process.env.EXPERIMENTAL_FEATURES),
}

module.exports = {
Expand Down
5 changes: 4 additions & 1 deletion core/record/_record/recordNodeValueUpdater.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ export const updateAttributeValue = ({
const attributeUpdated = A.pipe(
Node.assocValue(value),
Node.assocUpdated(true),
Node.assocDateModified(dataModifiedParam ?? new Date())
Node.assocDateModified(dataModifiedParam ?? new Date()),
(nodeUpdated) =>
// reset defaultValueApplied meta information
Node.isDefaultValueApplied(attribute) ? Node.assocIsDefaultValueApplied(false)(nodeUpdated) : nodeUpdated
)(attribute)

const updateResult = new RecordUpdateResult({ record })
Expand Down
5 changes: 2 additions & 3 deletions core/record/_record/recordNodesUpdater.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
RecordUpdater as CoreRecordUpdater,
RecordNodesUpdater as CoreRecordNodesUpdater,
RecordUpdateResult,
Promises,
} from '@openforis/arena-core'

import * as Survey from '@core/survey/survey'
Expand Down Expand Up @@ -234,7 +233,7 @@ const updateAttributesInEntityWithValues =
)

// update attribute values
await Promises.each(valuesByDefUuidEntriesInDescendantAttributes, async ([attributeDefUuid, value]) => {
for await (const [attributeDefUuid, value] of valuesByDefUuidEntriesInDescendantAttributes) {
const attributeDef = Survey.getNodeDefByUuid(attributeDefUuid)(survey)

const { record: currentRecord } = updateResult
Expand All @@ -254,7 +253,7 @@ const updateAttributesInEntityWithValues =
})(currentRecord)

await updateDependentNodes(attributeUpdateResult)
})
}
return updateResult
}

Expand Down
41 changes: 19 additions & 22 deletions core/survey/_survey/surveyNodeDefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,16 +311,17 @@ export const getNodeDefPath =
}

export const getHierarchy =
(filterFn = NodeDef.isEntity) =>
(filterFn = NodeDef.isEntity, cycle = undefined) =>
(survey) => {
let length = 1
const h = (array, nodeDef) => {
const childDefs = [
...(NodeDef.isEntity(nodeDef) && !NodeDef.isVirtual(nodeDef)
? R.pipe(getNodeDefChildren(nodeDef), R.filter(filterFn))(survey)
: []),
]

const childDefs = []
if (NodeDef.isEntity(nodeDef) && !NodeDef.isVirtual(nodeDef)) {
const childDefsNotFiltered = cycle
? getNodeDefChildrenSorted({ nodeDef, cycle })(survey)
: getNodeDefChildren(nodeDef)(survey)
childDefs.push(...childDefsNotFiltered.filter(filterFn))
}
length += childDefs.length
const item = { ...nodeDef, children: childDefs.reduce(h, []) }
array.push(item)
Expand Down Expand Up @@ -350,7 +351,7 @@ export const traverseHierarchyItemSync = (nodeDefItem, visitorFn, depth = 0) =>
}

export const visitDescendantsAndSelf =
({ visitorFn, nodeDef = null, cycle = null, traverseMethod = TraverseMethod.bfs }) =>
({ nodeDef = null, cycle = null, visitorFn, traverseMethod = TraverseMethod.bfs }) =>
(survey) => {
const nodeDefToVisit = nodeDef ?? getNodeDefRoot(survey)
return Surveys.visitDescendantsAndSelfNodeDef({
Expand All @@ -377,22 +378,18 @@ export const findDescendants =
return descendants
}

export const getDescendantsAndSelf =
({ nodeDef = null }) =>
export const getNodeDefDescendantsAndSelf =
({ nodeDef = null, cycle = null, traverseMethod = TraverseMethod.bfs } = {}) =>
(survey) => {
const descendants = []
const queue = new Queue()

queue.enqueue(nodeDef || getNodeDefRoot(survey))

while (!queue.isEmpty()) {
const nodeDefCurrent = queue.dequeue()

descendants.push(nodeDefCurrent)

const childrenDefs = getNodeDefChildren(nodeDefCurrent)(survey)
queue.enqueueItems(childrenDefs)
}
visitDescendantsAndSelf({
nodeDef,
cycle,
visitorFn: (visitedNodeDef) => {
descendants.push(visitedNodeDef)
},
traverseMethod,
})(survey)
return descendants
}

Expand Down
Loading

0 comments on commit f54b24b

Please sign in to comment.