Skip to content

Commit

Permalink
Record clone: exclude code attributes having excluded ancestor codes (#…
Browse files Browse the repository at this point in the history
…234)

Co-authored-by: Stefano Ricci <[email protected]>
  • Loading branch information
SteRiccio and SteRiccio authored Jun 28, 2024
1 parent 86c1d13 commit 586ab46
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 18 deletions.
52 changes: 50 additions & 2 deletions src/record/recordCloner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { RecordBuilder, RecordNodeBuilders } from '../tests/builder/recordBuilde
import { TestUtils } from '../tests/testUtils'
import { createTestAdminUser } from '../tests/data'

const { booleanDef, entityDef, integerDef } = SurveyObjectBuilders
const { booleanDef, category, categoryItem, codeDef, entityDef, integerDef } = SurveyObjectBuilders
const { entity, attribute } = RecordNodeBuilders

import { Record } from './record'
Expand All @@ -25,9 +25,34 @@ describe('Record cloner', () => {
integerDef('cluster_id').key(),
booleanDef('cluster_boolean_attribute'),
booleanDef('cluster_attr_excluded').excludeInClone(),
codeDef('parent_code', 'hierarchical_category'),
codeDef('dependent_code', 'hierarchical_category').parentCodeAttribute('parent_code'),
codeDef('parent_code_excluded', 'hierarchical_category').excludeInClone(),
codeDef('dependent_code_excluded', 'hierarchical_category').parentCodeAttribute('parent_code_excluded'),
entityDef('plot', integerDef('plot_id').key(), integerDef('plot_attr_excluded').excludeInClone()).multiple()
)
).build()
)
.categories(
category('hierarchical_category')
.levels('level_1', 'level_2')
.items(
categoryItem('1').items(categoryItem('1a')),
categoryItem('2').items(categoryItem('2a'), categoryItem('2b'), categoryItem('2c')),
categoryItem('3').items(categoryItem('3a'))
)
)
.build()

const categoryItem1a = TestUtils.getCategoryItem({
survey,
categoryName: 'hierarchical_category',
codePaths: ['1', '1a'],
})
const categoryItem2b = TestUtils.getCategoryItem({
survey,
categoryName: 'hierarchical_category',
codePaths: ['2', '2b'],
})

record = new RecordBuilder(
user,
Expand All @@ -37,6 +62,10 @@ describe('Record cloner', () => {
attribute('cluster_id', 10),
attribute('cluster_boolean_attribute', 'true'),
attribute('cluster_attr_excluded', 'true'),
attribute('parent_code', '1'),
attribute('dependent_code', { itemUuid: categoryItem1a.uuid }),
attribute('parent_code_excluded', '2'),
attribute('dependent_code_excluded', { itemUuid: categoryItem2b.uuid }),
entity('plot', attribute('plot_id', 1), attribute('plot_attr_excluded', 10)),
entity('plot', attribute('plot_id', 2), attribute('plot_attr_excluded', 20)),
entity('plot', attribute('plot_id', 3), attribute('plot_attr_excluded', 30))
Expand Down Expand Up @@ -65,6 +94,25 @@ describe('Record cloner', () => {
const { record: clonedRecord } = RecordCloner.cloneRecord({ survey, record, cycleTo: '0', sideEffect: true })
expect(clonedRecord).toBe(record)

const clusterAttrIncluded = TestUtils.findNodeByPath({ survey, record, path: 'cluster_boolean_attribute' })
expect(clusterAttrIncluded?.value).not.toBeUndefined()

const codeAttrIncluded = TestUtils.findNodeByPath({ survey, record, path: 'parent_code' })
expect(codeAttrIncluded?.value).not.toBeUndefined()

const codeAttrDependentIncluded = TestUtils.findNodeByPath({ survey, record, path: 'dependent_code' })
expect(codeAttrDependentIncluded?.value).not.toBeUndefined()

const codeAttrExcluded = TestUtils.findNodeByPath({ survey, record, path: 'parent_code_excluded' })
expect(codeAttrExcluded?.value).toBeUndefined()

const codeAttrDependentExcluded = TestUtils.findNodeByPath({
survey,
record,
path: 'dependent_code_excluded',
})
expect(codeAttrDependentExcluded?.value).toBeUndefined()

const clusterAttrExcluded = TestUtils.findNodeByPath({ survey, record, path: 'cluster_attr_excluded' })
expect(clusterAttrExcluded?.value).toBeUndefined()

Expand Down
35 changes: 24 additions & 11 deletions src/record/recordCloner.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Node, NodeFactory, NodeValues, Nodes } from '../node'
import { NodeKeys, NodeMetaKeys } from '../node/node'
import { NodeDef, NodeDefs } from '../nodeDef'
import { NodeDef, NodeDefCode, NodeDefType, NodeDefs } from '../nodeDef'
import { Survey, Surveys } from '../survey'
import { Dates, Objects, UUIDs } from '../utils'
import { Validation } from '../validation'
Expand Down Expand Up @@ -213,17 +213,30 @@ const removeExcludedNodes = (params: { survey: Survey; record: Record; sideEffec
const { survey, record, sideEffect } = params
const cycle = record.cycle!
const result = new RecordUpdateResult({ record })
Object.values(survey.nodeDefs ?? {}).forEach((nodeDef) => {
if (!NodeDefs.isInCycle(cycle)(nodeDef) || NodeDefs.isExcludedInClone(nodeDef)) {
const nodesToDelete = Records.getNodesByDefUuid(nodeDef.uuid)(result.record)
if (nodesToDelete.length > 0) {
const partialUpdateResult = Records.deleteNodes(
nodesToDelete.map((node) => node.uuid),
{ sideEffect }
)(result.record)
result.merge(partialUpdateResult)
Surveys.visitDescendantsAndSelfNodeDef({
survey,
cycle,
nodeDef: Surveys.getNodeDefRoot({ survey }),
visitor: (nodeDef) => {
const hasAncestorCodeDefExcludedInClone =
nodeDef.type === NodeDefType.code &&
!!Surveys.getNodeDefAncestorCodes({ survey, nodeDef: nodeDef as NodeDefCode }).find(NodeDefs.isExcludedInClone)

if (
!NodeDefs.isInCycle(cycle)(nodeDef) ||
NodeDefs.isExcludedInClone(nodeDef) ||
hasAncestorCodeDefExcludedInClone
) {
const nodesToDelete = Records.getNodesByDefUuid(nodeDef.uuid)(result.record)
if (nodesToDelete.length > 0) {
const partialUpdateResult = Records.deleteNodes(
nodesToDelete.map((node) => node.uuid),
{ sideEffect }
)(result.record)
result.merge(partialUpdateResult)
}
}
}
},
})
return result
}
Expand Down
2 changes: 2 additions & 0 deletions src/survey/surveys/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
getNodeDefKeys,
getNodeDefsIncludedInMultipleEntitySummary,
getNodeDefParentCode,
getNodeDefAncestorCodes,
isNodeDefParentCode,
getNodeDefCategoryLevelIndex,
isNodeDefEnumerator,
Expand Down Expand Up @@ -106,6 +107,7 @@ export const Surveys = {

// node def code
getNodeDefParentCode,
getNodeDefAncestorCodes,
isNodeDefParentCode,
getNodeDefCategoryLevelIndex,

Expand Down
18 changes: 13 additions & 5 deletions src/survey/surveys/nodeDefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,15 +311,23 @@ export const visitNodeDefs = (params: {
}

// Node Def Code
export const getNodeDefParentCode = (params: {
survey: Survey
nodeDef: NodeDef<NodeDefType, NodeDefCodeProps>
}): NodeDef<NodeDefType.code, NodeDefCodeProps> | undefined => {
export const getNodeDefParentCode = (params: { survey: Survey; nodeDef: NodeDefCode }): NodeDefCode | undefined => {
const { survey, nodeDef } = params
const parentCodeDefUuid = nodeDef.props.parentCodeDefUuid
if (!parentCodeDefUuid) return undefined
const parentCodeDef = getNodeDefByUuid({ survey, uuid: parentCodeDefUuid })
return parentCodeDef as NodeDef<NodeDefType.code, NodeDefCodeProps>
return parentCodeDef as NodeDefCode
}

export const getNodeDefAncestorCodes = (params: { survey: Survey; nodeDef: NodeDefCode }): NodeDefCode[] => {
const { survey, nodeDef } = params
const ancestors = []
let currentParentCode = getNodeDefParentCode({ survey, nodeDef })
while (currentParentCode) {
ancestors.unshift(currentParentCode)
currentParentCode = getNodeDefParentCode({ survey, nodeDef: currentParentCode })
}
return ancestors
}

export const isNodeDefParentCode = (params: {
Expand Down

0 comments on commit 586ab46

Please sign in to comment.