Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Survey user extra props #3595

Merged
merged 12 commits into from
Oct 11, 2024
2 changes: 1 addition & 1 deletion .github/workflows/test.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:

jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04

services:
postgres:
Expand Down
9 changes: 9 additions & 0 deletions core/i18n/resources/en/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ Thank you and enjoy **$t(common.appNameFull)**!`,
},
surveyDeleted: 'Survey {{surveyName}} has been deleted',
surveyInfo: {
basic: 'Basic info',
configuration: {
title: 'Configuration',
filesTotalSpace: 'Files total space (GB)',
Expand All @@ -453,6 +454,7 @@ If there are records associated to this cycle, they will be deleted.`,
viewInfo: 'View info',
preferredLanguage: 'Preferred language',
sampleBasedImageInterpretation: 'Sample-based image interpretation',
sampleBasedImageInterpretationEnabled: 'Sample-based image interpretation enabled',
srsPlaceholder: 'Type code or label',
unpublish: 'Unpublish and delete data',
unpublishSurveyDialog: {
Expand All @@ -462,6 +464,9 @@ If there are records associated to this cycle, they will be deleted.`,
$t(common.cantUndoWarning)`,
confirmName: 'Enter this survey’s name to confirm:',
},
userExtraProps: {
title: 'User extra properties',
},
},
deleteSurveyDialog: {
confirmDelete: 'Are you sure you want to delete this survey?',
Expand Down Expand Up @@ -902,6 +907,10 @@ you can copy the invitation link to the clipboard and share it with him in other

Copy the invitation link to the clipboard?`,
invitationLinkCopiedToClipboard: 'Invitation link copied to your clipboard',
surveyExtraProp: {
label: 'Survey extra property',
label_other: 'Survey extra properties',
},
},

usersAccessRequestView: {
Expand Down
61 changes: 0 additions & 61 deletions core/numberUtils.js
Original file line number Diff line number Diff line change
@@ -1,69 +1,8 @@
import BigNumber from 'bignumber.js'

import * as A from '@core/arena'
import { Objects } from '@openforis/arena-core'

BigNumber.config({
ERRORS: false,
FORMAT: {
decimalSeparator: '.',
groupSeparator: ',',
groupSize: 3,
},
})

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

export const isInteger = A.pipe(toNumber, Number.isInteger)

export const isFloat = A.pipe(toNumber, Number.isFinite)

/**
* Formats the given value to the specified fixed dicimal digits.
*
* @param {!number} value - The value to format.
* @param {number} [decimalDigits=2] - Number of fixed decimal digits.
* @returns {string} - The formatted value or null if the value was null.
*/
export const formatDecimal = (value, decimalDigits = NaN) => {
if (Number.isNaN(value) || value === null) return null
const num = new BigNumber(value)

if (decimalDigits >= 0) {
// round to fixed number of decimal digits
return num.toFormat(decimalDigits)
}
return num.toFormat()
}

export const roundToPrecision = (value, precision = NaN) => {
const num = toNumber(value)
if (Number.isNaN(num)) return NaN
if (Number.isNaN(precision)) return num
const exp = Math.pow(10, precision)
return Math.round(num * exp) / exp
}

/**
* Formats the given value to a rounded integer.
*
* @param {!number} value - The value to format.
* @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 limit =
({ minValue = null, maxValue = null }) =>
(value) => {
let result = Number(value)
if (minValue) result = Math.max(minValue, result)
if (maxValue) result = Math.min(maxValue, result)
return result
}
6 changes: 6 additions & 0 deletions core/survey/_survey/surveyInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as R from 'ramda'
import { DEFAULT_SRS, Objects } from '@openforis/arena-core'

import * as AuthGroup from '@core/auth/authGroup'
import { ExtraPropDef } from '@core/survey/extraPropDef'

import * as ObjectUtils from '@core/objectUtils'
import * as StringUtils from '@core/stringUtils'
Expand Down Expand Up @@ -37,6 +38,7 @@ export const keys = {
steps: 'steps',
template: 'template',
temporary: 'temporary',
userExtraPropDefs: 'userExtraPropDefs',
}

export const collectReportKeys = {
Expand Down Expand Up @@ -163,6 +165,10 @@ export const isTemplate = R.propEq(keys.template, true)

export const getFieldManualLinks = ObjectUtils.getProp(keys.fieldManualLinks, {})

export const getUserExtraPropDefs = ObjectUtils.getProp(keys.userExtraPropDefs, {})

export const getUserExtraPropDefsArray = R.pipe(getUserExtraPropDefs, ExtraPropDef.extraDefsToArray)

// ====== UPDATE
export const markDraft = R.assoc(keys.draft, true)

Expand Down
11 changes: 1 addition & 10 deletions core/survey/category.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,16 +119,7 @@ export const getItemExtraDefKeys = (category) => {
const itemExtraDef = getItemExtraDef(category)
return Object.keys(itemExtraDef)
}
export const getItemExtraDefsArray = (category) =>
// add uuid and name to each extra def item definition and put them in a array
Object.entries(getItemExtraDef(category))
.map(([name, item], index) => ({
...item,
uuid: uuidv4(),
name,
index: ExtraPropDef.getIndex(item) ?? index,
}))
.sort((itemA, itemB) => ExtraPropDef.getIndex(itemA) - ExtraPropDef.getIndex(itemB))
export const getItemExtraDefsArray = R.pipe(getItemExtraDef, ExtraPropDef.extraDefsToArray)

export const assocItemExtraDef = (extraDef) => ObjectUtils.setProp(keysProps.itemExtraDef, extraDef)

Expand Down
21 changes: 21 additions & 0 deletions core/survey/extraPropDef.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { UUIDs } from '@openforis/arena-core'

import * as A from '@core/arena'

const keys = {
dataType: 'dataType',
index: 'index',
name: 'name',
uuid: 'uuid',
}

const dataTypes = {
Expand All @@ -21,11 +24,29 @@ const getDataType = A.propOr(dataTypes.text, keys.dataType)
const getIndex = A.propOr(0, keys.index)
const getName = A.prop(keys.name)

// UPDATE
const assocIndex = A.assoc(keys.index)

// UTILS
const extraDefsToArray = (extraDefs) =>
// add uuid and name to each extra def item definition and put them in a array
Object.entries(extraDefs)
.map(([name, item], index) => ({
...item,
[keys.uuid]: UUIDs.v4(),
[keys.name]: name,
[keys.dataType]: getDataType(item),
[keys.index]: getIndex(item) ?? index,
}))
.sort((itemA, itemB) => getIndex(itemA) - getIndex(itemB))

export const ExtraPropDef = {
keys,
dataTypes,
newItem,
getDataType,
getIndex,
getName,
assocIndex,
extraDefsToArray,
}
2 changes: 2 additions & 0 deletions core/survey/survey.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ export const {
getDescription,
getDescriptions,
getFieldManualLinks,
getUserExtraPropDefs,
getUserExtraPropDefsArray,
isSampleBasedImageInterpretationEnabled,
getSamplingPolygon,
getSRS,
Expand Down
13 changes: 2 additions & 11 deletions core/survey/taxonomy.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,8 @@ export const { getDescriptions, getDescription } = ObjectUtils
export const getVernacularLanguageCodes = ObjectUtils.getProp(keysProps.vernacularLanguageCodes, [])
export const getExtraPropsDefs = ObjectUtils.getProp(keysProps.extraPropsDefs, {})
export const getExtraPropKeys = (taxonomy) => Object.keys(getExtraPropsDefs(taxonomy))
export const getExtraPropsDefsArray = (taxonomy) =>
// add uuid and name to each extra prop definition and put them in a array
Object.entries(getExtraPropsDefs(taxonomy))
.sort(([, prop1], [, prop2]) => ExtraPropDef.getIndex(prop1) - ExtraPropDef.getIndex(prop2))
.map(([name, extraPropDef]) => ({
...extraPropDef,
uuid: uuidv4(),
dataType: ExtraPropDef.getDataType(extraPropDef),
index: ExtraPropDef.getIndex(extraPropDef),
name,
}))
export const getExtraPropsDefsArray = R.pipe(getExtraPropsDefs, ExtraPropDef.extraDefsToArray)

export const getTaxaCount = R.prop(keys.taxaCount)

// UPDATE
Expand Down
1 change: 1 addition & 0 deletions core/user/_user/userKeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ export const keys = {

// Used only when editing user auth groups
authGroupsUuids: 'authGroupsUuids',
authGroupExtraProps: 'authGroupExtraProps',
}
4 changes: 4 additions & 0 deletions core/user/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export const hasProfilePicture = R.propEq(keys.hasProfilePicture, true)
export const getStatus = R.prop(keys.status)
export const { getValidation } = Validation
export const getAuthGroupsUuids = R.propOr([], keys.authGroupsUuids)
export const getAuthGroupExtraProps = R.propOr({}, keys.authGroupExtraProps)
export const getAuthGroupExtraProp = (prop) => R.pipe(getAuthGroupExtraProps, R.prop(prop))
export const getLastLoginTime = R.prop(keys.lastLoginTime)

// ====== UPDATE
Expand Down Expand Up @@ -87,6 +89,8 @@ export const assocAuthGroup = (authGroup) => _updateAuthGroups(R.append(authGrou

export const dissocAuthGroup = (authGroup) => _updateAuthGroups(R.reject(AuthGroup.isEqual(authGroup)))

export const assocAuthGroupExtraProps = R.assoc(keys.authGroupExtraProps)

// PREFS
export const {
newPrefs,
Expand Down
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@
"@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.207",
"@openforis/arena-server": "^0.1.35",
"@openforis/arena-core": "^0.0.209",
"@openforis/arena-server": "^0.1.36",
"@reduxjs/toolkit": "^2.2.5",
"@sendgrid/mail": "^8.1.3",
"@shopify/draggable": "^1.1.3",
Expand All @@ -122,7 +122,6 @@
"archiver": "^7.0.1",
"axios": "^1.6.7",
"bcryptjs": "^2.4.3",
"bignumber.js": "^9.1.2",
"buffer": "^6.0.3",
"camelize": "^1.0.0",
"circle-to-polygon": "^2.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import * as ArenaSurveyFileZip from '../model/arenaSurveyFileZip'

const _associateToGroup = async ({ userUuid, groupName }, client) => {
const group = await AuthGroupRepository.fetchGroupByName({ name: groupName }, client)
await AuthGroupRepository.insertUserGroup(AuthGroup.getUuid(group), userUuid, client)
await AuthGroupRepository.insertUserGroup({ groupUuid: AuthGroup.getUuid(group), userUuid }, client)
}

const _associateToSurveyGroup = async ({ survey, arenaSurvey, user, userAlreadyExisting }, client) => {
Expand All @@ -34,7 +34,7 @@ const _associateToSurveyGroup = async ({ survey, arenaSurvey, user, userAlreadyE
if (groupToAssociateToUser && !AuthGroup.isEqual(groupToAssociateToUser)(userGroupInExistingUser)) {
const surveyGroupUuid = AuthGroup.getUuid(groupToAssociateToUser)
const userUuid = userAlreadyExisting ? User.getUuid(userAlreadyExisting) : User.getUuid(user)
await AuthGroupRepository.insertUserGroup(surveyGroupUuid, userUuid, client)
await AuthGroupRepository.insertUserGroup({ groupUuid: surveyGroupUuid, userUuid }, client)
}
}

Expand Down
27 changes: 14 additions & 13 deletions server/modules/auth/repository/authGroupRepository.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as camelize from 'camelize'
import { db } from '@server/db/db'

import * as A from '@core/arena'

import * as AuthGroup from '@core/auth/authGroup'
import * as UserRepository from '../../user/repository/userRepository'

const dbTransformCallback = camelize
const dbTransformCallback = A.camelizePartial({ limitToLevel: 1 })

// ==== CREATE

Expand All @@ -28,13 +29,13 @@ const insertGroup = async (authGroup, surveyId, client = db) =>
export const createSurveyGroups = async (surveyId, surveyGroups, client = db) =>
Promise.all(surveyGroups.map((authGroup) => insertGroup(authGroup, surveyId, client)))

export const insertUserGroup = async (groupUuid, userUuid, client = db) =>
export const insertUserGroup = async ({ groupUuid, userUuid, props = null }, client = db) =>
client.one(
`
INSERT INTO auth_group_user (group_uuid, user_uuid)
VALUES ($1, $2)
INSERT INTO auth_group_user (group_uuid, user_uuid, props)
VALUES ($1, $2, $3)
RETURNING *`,
[groupUuid, userUuid],
[groupUuid, userUuid, props],
dbTransformCallback
)

Expand Down Expand Up @@ -88,7 +89,7 @@ export const fetchSurveyGroups = async (surveyId, client = db) =>
export const fetchUserGroups = async (userUuid, client = db) =>
client.map(
`
SELECT g.*
SELECT g.*, gu.props
FROM auth_group_user gu
JOIN auth_group g
ON g.uuid = gu.group_uuid
Expand All @@ -102,7 +103,7 @@ export const fetchUserGroups = async (userUuid, client = db) =>
export const fetchUsersGroups = async (userUuids, client = db) =>
client.map(
`
SELECT gu.user_uuid, g.*
SELECT gu.user_uuid, gu.props, g.*
FROM auth_group_user gu
JOIN auth_group g
ON g.uuid = gu.group_uuid
Expand Down Expand Up @@ -139,22 +140,22 @@ export const fetchSurveyIdsOfExpiredInvitationUsers = async (client = db) =>

// ==== UPDATE

export const updateUserGroup = async (surveyId, userUuid, groupUuid, client = db) => {
export const updateUserGroup = async ({ surveyId, userUuid, groupUuid, props = null }, client = db) => {
await client.one(
`
UPDATE auth_group_user gu
SET group_uuid = $1
SET group_uuid = $/groupUuid/, props = $/props/
FROM auth_group g
JOIN survey s
ON s.id = $3
WHERE gu.user_uuid = $2
ON s.id = $/surveyId/
WHERE gu.user_uuid = $/userUuid/
AND (
(g.survey_uuid = s.uuid AND g.uuid = gu.group_uuid)
OR
(gu.group_uuid = g.uuid AND g.name = '${AuthGroup.groupNames.systemAdmin}')
)
RETURNING 1`,
[groupUuid, userUuid, surveyId],
{ groupUuid, userUuid, surveyId, props },
dbTransformCallback
)
}
Expand Down
Loading
Loading