Skip to content

Commit

Permalink
Merge branch 'master' of github.com:openforis/arena into feat/form-de…
Browse files Browse the repository at this point in the history
…signer-full-tree-view
  • Loading branch information
SteRiccio committed Sep 26, 2024
2 parents 26f65cd + 367cd62 commit 5d646ad
Show file tree
Hide file tree
Showing 44 changed files with 829 additions and 264 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
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
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
16 changes: 14 additions & 2 deletions core/i18n/resources/en/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,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 @@ -1153,8 +1159,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 @@ -1355,6 +1361,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 @@ -1423,6 +1430,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 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
2 changes: 2 additions & 0 deletions core/survey/nodeDef.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ export const isCode = isType(nodeDefType.code)
export const isCoordinate = isType(nodeDefType.coordinate)
export const isDate = isType(nodeDefType.date)
export const isDecimal = isType(nodeDefType.decimal)
export const isGeo = isType(nodeDefType.geo)
export const isFile = isType(nodeDefType.file)
export const isInteger = isType(nodeDefType.integer)
export const isTaxon = isType(nodeDefType.taxon)
Expand Down Expand Up @@ -618,6 +619,7 @@ export const canHaveDefaultValue = (nodeDef) =>
nodeDefType.coordinate,
nodeDefType.date,
nodeDefType.decimal,
nodeDefType.geo,
nodeDefType.integer,
nodeDefType.taxon,
nodeDefType.text,
Expand Down
1 change: 1 addition & 0 deletions core/survey/nodeDefType.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const nodeDefType = {
boolean: 'boolean',
code: 'code',
coordinate: 'coordinate',
geo: 'geo',
taxon: 'taxon',
file: 'file',
entity: 'entity',
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,14 @@
"@mui/x-data-grid": "^7.14.0",
"@mui/x-date-pickers": "^7.14.0",
"@mui/x-tree-view": "^7.7.1",
"@openforis/arena-core": "^0.0.203",
"@openforis/arena-core": "^0.0.204",
"@openforis/arena-server": "^0.1.34",
"@reduxjs/toolkit": "^2.2.5",
"@sendgrid/mail": "^8.1.3",
"@shopify/draggable": "^1.1.3",
"@turf/area": "^7.1.0",
"@turf/centroid": "^7.1.0",
"@turf/length": "^7.1.0",
"ace-builds": "^1.32.6",
"adm-zip": "^0.5.10",
"archiver": "^7.0.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ const convert = ({ survey, nodeDefCurrent, expression, advancedExpressionEditor
// geo namespace
// idm:distance is deprecated in Collect
{ pattern: '(geo|idm):distance', replace: 'distance' },
{ pattern: 'geo:polygon', replace: 'geoPolygon' },
// math namespace
{ pattern: /math:PI\(\)/, replace: 'Math.PI' },
// convert directly some functions from math:fnName to Math.fnName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const validateNodesAndPersistValidation = async (survey, record, nodes, v
// 1. validate node values
const nodesValueValidation = await RecordValidator.validateNodes({ survey, record, nodes })
const nodesValueValidationsByUuid = Validation.getFieldValidations(nodesValueValidation)
// 1.a. workaraound: always define value field validation even when validation is valid to allow cleaning up errors later
// 1.a. workaround: always define value field validation even when validation is valid to allow cleaning up errors later
Object.entries(nodesValueValidationsByUuid).forEach(([nodeUuid, nodeValueValidation]) => {
if (Validation.isValid(nodeValueValidation)) {
const nodeValueValidationUpdated = Validation.setField('value', Validation.newInstance())(nodeValueValidation)
Expand Down
6 changes: 4 additions & 2 deletions webapp/components/Map/Map.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import './Map.scss'

import React from 'react'
import { MapContainer, ScaleControl } from 'react-leaflet'
import { GeoJSON, MapContainer, ScaleControl } from 'react-leaflet'
import PropTypes from 'prop-types'

import { ButtonSave } from '@webapp/components'
Expand Down Expand Up @@ -32,7 +32,7 @@ L.Marker.prototype.options.icon = L.icon({
const INITIAL_ZOOM_LEVEL = 3

export const Map = (props) => {
const { editable = false, layers = [], markerPoint, markerTitle, showOptions = true } = props
const { editable = false, geoJson = null, layers = [], markerPoint, markerTitle, showOptions = true } = props
const { centerPositionLatLon, markerPointUpdated, markerPointUpdatedToString, onMarkerPointUpdated, onSaveClick } =
useMap(props)

Expand All @@ -54,6 +54,7 @@ export const Map = (props) => {
onPointUpdated={onMarkerPointUpdated}
title={markerTitle}
/>
{geoJson && <GeoJSON data={geoJson} />}
{showOptions && (
<>
<MapOptionsEditor />
Expand Down Expand Up @@ -85,6 +86,7 @@ export const Map = (props) => {
Map.propTypes = {
centerPoint: PropTypes.object,
editable: PropTypes.bool,
geoJson: PropTypes.object,
layers: PropTypes.array,
markerPoint: PropTypes.object,
markerTitle: PropTypes.string,
Expand Down
12 changes: 10 additions & 2 deletions webapp/components/ResizableModal/ResizableModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,15 @@ export const ResizableModal = (props) => {

return (
<div className={classNames('resizable-modal', className)}>
<ReactModal initHeight={initHeight} initWidth={initWidth} isOpen={isOpen} left={left} ref={modalRef} top={top}>
<ReactModal
initHeight={initHeight}
initWidth={initWidth}
isOpen={isOpen}
left={left}
onRequestClose={onRequestClose}
ref={modalRef}
top={top}
>
<div className="resizable-modal__header">
<h3>{header}</h3>
{onDetach && (
Expand Down Expand Up @@ -68,6 +76,6 @@ ResizableModal.propTypes = {
left: PropTypes.number,
onClose: PropTypes.func,
onDetach: PropTypes.func,
onRequestClose: PropTypes.func,
onRequestClose: PropTypes.func.isRequired,
top: PropTypes.number,
}
31 changes: 1 addition & 30 deletions webapp/components/expression/codemirrorArenaExpressionHint.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,10 @@
import CodeMirror from 'codemirror/lib/codemirror'

import * as Survey from '@core/survey/survey'
import * as Expression from '@core/expressionParser/expression'
import * as NodeDefExpressionValidator from '@core/survey/nodeDefExpressionValidator'

import * as ExpressionVariables from './expressionVariables'

const functionExamples = {
[Expression.modes.json]: {
[Expression.functionNames.categoryItemProp]:
`cateoryItemProp('category_name', 'prop_name', 'codeLevel1', 'codeLevel2', ...)`,
[Expression.functionNames.distance]: 'distance(coordinate_attribute_1, coordinate_attribute_2)',
[Expression.functionNames.first]:
'first(multiple_attribute_name), first(multiple_entity_name).entity_attribute_name, ...',
[Expression.functionNames.includes]: `includes(multiple_attribute_name, 'value') = true/false`,
[Expression.functionNames.index]: `index(node_name), index(this), index($context), index(parent(this))`,
[Expression.functionNames.isEmpty]: `isEmpty(attribute_name) = true/false`,
[Expression.functionNames.last]:
'last(multiple_entity_name).entity_attribute_name, last(multiple_attribute_name), ...',
[Expression.functionNames.ln]: 'ln(10) = 2.302…',
[Expression.functionNames.log10]: 'log10(100) = 2',
[Expression.functionNames.max]: 'max(3,1,2) = 3',
[Expression.functionNames.min]: 'min(3,1) = 1',
[Expression.functionNames.now]: 'now()',
[Expression.functionNames.parent]: `parent(this), parent($context), parent(node_name)`,
[Expression.functionNames.pow]: 'pow(2,3) = 2³ = 8',
[Expression.functionNames.taxonProp]: `taxonProp('taxonomy_name', 'extra_prop', 'taxon_code')`,
[Expression.functionNames.uuid]: 'uuid()',
},
[Expression.modes.sql]: {
[Expression.functionNames.avg]: 'avg(variable_name)',
[Expression.functionNames.count]: 'count(variable_name)',
[Expression.functionNames.sum]: 'sum(variable_name)',
},
}
import functionExamples from './functionExamples'

const _findCharIndex = ({ value, end, matchingRegEx }) => {
for (let i = end; i >= 0; i -= 1) {
Expand Down
Loading

0 comments on commit 5d646ad

Please sign in to comment.