Skip to content

Commit

Permalink
File name expression (#240)
Browse files Browse the repository at this point in the history
* Node def props: added File name expression

* Record.getFileName: add original file name extension to name

* update file name as dependency (WIP)

* code cleanup

* added multiple attribute node index

* code cleanup

* code cleanup

---------

Co-authored-by: Stefano Ricci <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 25, 2024
1 parent fc251f7 commit ecc1390
Show file tree
Hide file tree
Showing 20 changed files with 278 additions and 49 deletions.
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export type {
export { SurveyBuilder, SurveyObjectBuilders } from './tests/builder/surveyBuilder'
export { RecordBuilder, RecordNodeBuilders } from './tests/builder/recordBuilder'

export { Arrays, Dates, DateFormats, UnitOfTime, Numbers, Objects, Promises, Strings, UUIDs } from './utils'
export { Arrays, Dates, DateFormats, FileNames, Numbers, Objects, Promises, Strings, UnitOfTime, UUIDs } from './utils'

export {
FieldValidators,
Expand Down
2 changes: 1 addition & 1 deletion src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export type { Node, NodesMap } from './node'

export type { NodePointer } from './nodePointer'

export type { NodeValueCode, NodeValueCoordinate, NodeValueTaxon } from './nodeValue'
export type { NodeValueCode, NodeValueCoordinate, NodeValueFile, NodeValueTaxon } from './nodeValue'

export { NodeFactory } from './factory'

Expand Down
8 changes: 8 additions & 0 deletions src/node/nodeValue/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { NodeValueComposite } from './nodeValueComposite'

export interface NodeValueFile extends NodeValueComposite {
fileName?: string
fileNameCalculated?: string
fileSize?: number
fileUuid: string
}
1 change: 1 addition & 0 deletions src/node/nodeValue/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export type { NodeValueCode } from './code'
export type { NodeValueCoordinate } from './coordinate'
export type { NodeValueFile } from './file'
export type { NodeValueTaxon } from './taxon'
1 change: 1 addition & 0 deletions src/node/nodeValueProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export enum valuePropsDate {
export enum valuePropsFile {
fileUuid = 'fileUuid',
fileName = 'fileName',
fileNameCalculated = 'fileNameCalculated',
fileSize = 'fileSize',
}

Expand Down
3 changes: 3 additions & 0 deletions src/node/nodeValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ const _datePropGetters: { [key in valuePropsDate]: (node: Node) => number } = {

// File
const getFileName = (node: Node): string | undefined => getNodeValuePropRaw({ node, prop: valuePropsFile.fileName })
const getFileNameCalculated = (node: Node): string | undefined =>
getNodeValuePropRaw({ node, prop: valuePropsFile.fileNameCalculated })
const getFileSize = (node: Node): string | undefined => getNodeValuePropRaw({ node, prop: valuePropsFile.fileSize })
const getFileUuid = (node: Node): string | undefined => getNodeValuePropRaw({ node, prop: valuePropsFile.fileUuid })

Expand Down Expand Up @@ -298,6 +300,7 @@ export const NodeValues = {

// file
getFileName,
getFileNameCalculated,
getFileSize,
getFileUuid,

Expand Down
5 changes: 4 additions & 1 deletion src/nodeDef/nodeDef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,11 @@ export interface NodeDefPropsAdvanced {
defaultValueEvaluatedOneTime?: boolean
excludedInClone?: boolean
formula?: Array<NodeDefExpression>
itemsFilter?: string
validations?: NodeDefValidations
// file attribute
fileNameExpression?: string
// code and taxon attribute
itemsFilter?: string

// Analysis
script?: string
Expand Down
9 changes: 9 additions & 0 deletions src/nodeDef/nodeDefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
NodeDefEntityLayoutChildItem,
NodeDefEntityRenderType,
} from './types/entity'
import { NodeDefFile, NodeDefFileType } from './types/file'
import { NodeDefTaxon } from './types/taxon'
import { NodeDefText } from './types/text'

Expand Down Expand Up @@ -106,6 +107,11 @@ const getMaxNumberDecimalDigits = (nodeDef: NodeDefDecimal) => {
return Objects.isEmpty(decimalDigits) ? NaN : Number(decimalDigits)
}

// file
const getFileType = (nodeDef: NodeDefFile): NodeDefFileType | undefined => nodeDef.props.fileType
const getFileNameExpression = (nodeDef: NodeDefFile): string | undefined => nodeDef.propsAdvanced?.fileNameExpression
const getFileMaxSize = (nodeDef: NodeDefFile): number | undefined => nodeDef.props.maxFileSize

// taxon
const getTaxonomyUuid = (nodeDef: NodeDefTaxon): string | undefined => nodeDef.props.taxonomyUuid

Expand Down Expand Up @@ -230,6 +236,9 @@ export const NodeDefs = {
isAllowOnlyDeviceCoordinate,
getCoordinateAdditionalFields,
getMaxNumberDecimalDigits,
getFileNameExpression,
getFileType,
getFileMaxSize,
getTaxonomyUuid,
getTextTransform,
getItemsFilter,
Expand Down
8 changes: 8 additions & 0 deletions src/record/_records/recordGetters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,14 @@ export const getNodeSiblings = (params: {
)
}

export const getNodeIndex = (params: { record: Record; node: Node }) => {
const { record, node } = params
const parentEntity = getParent(node)(record)
if (!parentEntity) return 0 // root entity
const siblings = getChildren(parentEntity, node.nodeDefUuid)(record)
return siblings.indexOf(node)
}

export const findEntityByKeyValues = (params: {
survey: Survey
cycle: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { RecordUpdateResult } from './recordUpdateResult'
import { Records } from '../records'
import { RecordExpressionEvaluator } from '../recordExpressionEvaluator'

const recordExpressionEvaluator = new RecordExpressionEvaluator()

export const updateSelfAndDependentsApplicable = (params: {
survey: Survey
record: Record
Expand Down Expand Up @@ -40,7 +42,7 @@ export const updateSelfAndDependentsApplicable = (params: {
// nodeCtx could have been updated in a previous iteration
const nodeCtx = updateResult.getNodeByUuid(nodeCtxUuid) ?? nodeCtxNodePointer

const exprEval = new RecordExpressionEvaluator().evalApplicableExpression({
const exprEval = recordExpressionEvaluator.evalApplicableExpression({
survey,
record: updateResult.record,
nodeCtx,
Expand All @@ -62,7 +64,7 @@ export const updateSelfAndDependentsApplicable = (params: {

const nodeCtxChildren = Records.getChildren(nodeCtx, nodeDefUuid)(updateResult.record)
nodeCtxChildren.forEach((nodeCtxChild) => {
// 6. add nodeCtxChild and its descendants to nodesUpdated
// add nodeCtxChild and its descendants to nodesUpdated
Records.visitDescendantsAndSelf({
record: updateResult.record,
node: nodeCtxChild,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { SystemError } from '../../error'
import { NodeDef, NodeDefExpression, NodeDefProps, NodeDefs, NodeDefType } from '../../nodeDef'
import { NodeDef, NodeDefs } from '../../nodeDef'
import { Record } from '../record'
import { Survey } from '../../survey'
import { Node, NodePointer, Nodes } from '../../node'
Expand All @@ -10,27 +9,9 @@ import { Records } from '../records'
import { RecordExpressionValueConverter } from './recordExpressionValueConverter'
import { RecordExpressionEvaluator } from '../recordExpressionEvaluator'
import { NodePointers } from '../nodePointers'
import { throwError } from './recordNodesDependentsUpdaterCommons'

const _throwError = (params: {
error: any
expressionType: SurveyDependencyType
survey: Survey
nodeDef: NodeDef<NodeDefType, NodeDefProps>
expressionsToEvaluate: NodeDefExpression[]
}) => {
const { error, expressionType, survey, nodeDef, expressionsToEvaluate } = params
const nodeDefName = nodeDef.props.name
const expressionsString = JSON.stringify(expressionsToEvaluate)

throw new SystemError('record.updateSelfAndDependentsDefaultValues', {
surveyName: survey.props.name,
nodeDefName,
expressionType,
expressionsString,
error: error.toString(),
errorJson: error instanceof SystemError ? error.toJSON() : null,
})
}
const recordExpressionEvaluator = new RecordExpressionEvaluator()

const shouldResetDefaultValue = (params: { record: Record; node: Node }): boolean => {
const { record, node } = params
Expand Down Expand Up @@ -66,7 +47,7 @@ const updateDefaultValuesInNodes = (params: {

try {
// 1. evaluate applicable default value expression
const exprEval = new RecordExpressionEvaluator().evalApplicableExpression({
const exprEval = recordExpressionEvaluator.evalApplicableExpression({
survey,
record,
nodeCtx,
Expand Down Expand Up @@ -115,8 +96,9 @@ const updateDefaultValuesInNodes = (params: {
updateResult.addNode(nodeUpdated, { sideEffect })
})
} catch (error) {
_throwError({
throwError({
error,
errorKey: 'record.updateSelfAndDependentsDefaultValues',
expressionType: SurveyDependencyType.defaultValues,
survey,
nodeDef,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { Node, NodePointer, Nodes, NodeValueFile, NodeValues } from '../../node'
import { NodeDefFile, NodeDefs } from '../../nodeDef'
import { NodeDefExpressionFactory } from '../../nodeDef/nodeDef'
import { Survey, Surveys } from '../../survey'
import { SurveyDependencyType } from '../../survey/survey'
import { Dates, FileNames, Objects } from '../../utils'
import { NodePointers } from '../nodePointers'
import { Record } from '../record'
import { RecordExpressionEvaluator } from '../recordExpressionEvaluator'
import { Records } from '../records'
import { throwError } from './recordNodesDependentsUpdaterCommons'
import { RecordUpdateResult } from './recordUpdateResult'

const recordExpressionEvaluator = new RecordExpressionEvaluator()

const fileNameWithPositionSuffixRegExp = /^.+\s\[\d+\]$/ // file name like "name [1].test"

const addPositionSuffix = (params: { fileName: string; position: number }) => {
const { fileName, position } = params
return `${fileName} [${position}]`
}

const calculateFileName = (params: { survey: Survey; record: Record; node: Node }): string | undefined => {
const { survey, record, node } = params

const nodeDef: NodeDefFile = Surveys.getNodeDefByUuid({ survey, uuid: node.nodeDefUuid }) as NodeDefFile
const fileNameExpression = NodeDefs.getFileNameExpression(nodeDef)
if (!fileNameExpression) return undefined

const { value } = node

let fileNameCalculated = recordExpressionEvaluator.evalExpression({ survey, record, node, query: fileNameExpression })
if (!fileNameCalculated) return undefined

fileNameCalculated = String(fileNameCalculated)
if (NodeDefs.isMultiple(nodeDef) && !fileNameWithPositionSuffixRegExp.test(fileNameCalculated)) {
const index = Records.getNodeIndex({ record, node })
fileNameCalculated = addPositionSuffix({ fileName: fileNameCalculated, position: index + 1 })
}

// add extension from original file name
const originalFilaName = value ? (value as NodeValueFile).fileName : undefined
if (Objects.isNotEmpty(originalFilaName)) {
const originalExtension = FileNames.getExtension(originalFilaName)
fileNameCalculated = FileNames.addExtensionIfMissing(fileNameCalculated, originalExtension)
}
return fileNameCalculated
}

const updateFileNamesInNodes = (params: {
survey: Survey
nodePointer: NodePointer
updateResult: RecordUpdateResult
sideEffect?: boolean
}) => {
const { survey, nodePointer, updateResult, sideEffect = false } = params

const { nodeCtx, nodeDef } = nodePointer

if (nodeCtx.deleted) return

const expressionToEvaluate = NodeDefs.getFileNameExpression(nodeDef as NodeDefFile)
if (!expressionToEvaluate) return

const { record } = updateResult

const nodes = NodePointers.getNodesFromNodePointers({ record, nodePointers: [nodePointer] })

nodes.forEach((node) => {
try {
// evaluate file name expression
const fileNameCalculated = calculateFileName({ survey, record: updateResult.record, node })
const oldFileNameCalculated = NodeValues.getFileNameCalculated(node)
if (fileNameCalculated !== oldFileNameCalculated) {
const nodeUpdated = Nodes.mergeNodes(node, {
value: { [NodeValues.valuePropsFile.fileNameCalculated]: fileNameCalculated },
updated: true,
dateModified: Dates.nowFormattedForStorage(),
})
updateResult.addNode(nodeUpdated, { sideEffect })
}
} catch (error) {
const expressionsToEvaluate = [NodeDefExpressionFactory.createInstance({ expression: expressionToEvaluate })]
throwError({
error,
errorKey: 'record.updateSelfAndDependentsFileNames',
expressionType: SurveyDependencyType.fileName,
survey,
nodeDef,
expressionsToEvaluate,
})
}
})
}

export const updateSelfAndDependentsFileNames = (params: {
survey: Survey
record: Record
node: Node
sideEffect?: boolean
}) => {
const { survey, record, node, sideEffect = false } = params

const updateResult = new RecordUpdateResult({ record })

// 1. get dependent node pointers

const nodePointersToUpdate = Records.getDependentNodePointers({
survey,
record,
node,
dependencyType: SurveyDependencyType.fileName,
includeSelf: true,
})

// 2. update expr to node and dependent nodes
nodePointersToUpdate.forEach((nodePointer) => {
updateFileNamesInNodes({ survey, nodePointer, updateResult, sideEffect })
})

return updateResult
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { SystemError } from '../../error'
import { NodeDef, NodeDefExpression, NodeDefProps, NodeDefType } from '../../nodeDef'
import { Survey, SurveyDependencyType } from '../../survey'

export const throwError = (params: {
error: any
errorKey: string
expressionType: SurveyDependencyType
survey: Survey
nodeDef: NodeDef<NodeDefType, NodeDefProps>
expressionsToEvaluate: NodeDefExpression[]
}) => {
const { error, errorKey, expressionType, survey, nodeDef, expressionsToEvaluate } = params
const nodeDefName = nodeDef.props.name
const expressionsString = JSON.stringify(expressionsToEvaluate)

throw new SystemError(errorKey, {
surveyName: survey.props.name,
nodeDefName,
expressionType,
expressionsString,
error: error.toString(),
errorJson: error instanceof SystemError ? error.toJSON() : null,
})
}
Loading

0 comments on commit ecc1390

Please sign in to comment.