diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index 49430796dc..baf0142b0c 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/common/activityLog/activityLog.js b/common/activityLog/activityLog.js index dca0d8d4e1..514326d4fe 100644 --- a/common/activityLog/activityLog.js +++ b/common/activityLog/activityLog.js @@ -113,6 +113,7 @@ export const keysContent = { // Node nodeDefUuid: 'nodeDefUuid', recordUuid: 'recordUuid', + nodeIId: 'nodeIId', // User groupUuid: 'groupUuid', // Analysis diff --git a/common/model/db/tables/dataNodeDef/columnNodeDef.js b/common/model/db/tables/dataNodeDef/columnNodeDef.js index 66e19ed6af..16c5daff07 100644 --- a/common/model/db/tables/dataNodeDef/columnNodeDef.js +++ b/common/model/db/tables/dataNodeDef/columnNodeDef.js @@ -8,6 +8,7 @@ import * as SQL from '../../sql' const { nodeDefType } = NodeDef +const columnSuffixEntityIId = '_i_id' const columnSuffixCodeLabel = '_label' const columnSuffixFileUuid = '_file_uuid' const columnSuffixFileName = '_file_name' @@ -54,7 +55,7 @@ const getColumnNames = (nodeDef) => { return colsSuffix.map((colSuffix) => `${nodeDefName}${colSuffix}`) } if (NodeDef.isEntity(nodeDef)) { - return [`${nodeDefName}_uuid`] + return [`${nodeDefName}${columnSuffixEntityIId}`] } return [nodeDefName] } @@ -71,8 +72,8 @@ const extractColumnName = ({ nodeDef, columnName }) => { /** * A nodeDef data table column. - * * @typedef {object} module:arena.ColumnNodeDef + * @property */ export default class ColumnNodeDef { constructor(table, nodeDef) { diff --git a/common/model/db/tables/dataNodeDef/dataColProps.js b/common/model/db/tables/dataNodeDef/dataColProps.js index c6f3304abd..e2133aa284 100644 --- a/common/model/db/tables/dataNodeDef/dataColProps.js +++ b/common/model/db/tables/dataNodeDef/dataColProps.js @@ -113,7 +113,7 @@ const props = { }, }, [nodeDefType.entity]: { - [colValueProcessor]: () => () => Node.getUuid, + [colValueProcessor]: () => (node) => Node.getIId(node), }, [nodeDefType.file]: { [colValueProcessor]: ({ nodeDefCol }) => { diff --git a/common/model/db/tables/dataNodeDef/table.js b/common/model/db/tables/dataNodeDef/table.js index 872e86d157..2775a8ae22 100644 --- a/common/model/db/tables/dataNodeDef/table.js +++ b/common/model/db/tables/dataNodeDef/table.js @@ -15,9 +15,9 @@ const columnSet = { id: Table.columnSetCommon.id, dateCreated: Table.columnSetCommon.dateCreated, dateModified: Table.columnSetCommon.dateModified, - uuid: Table.columnSetCommon.uuid, - parentUuid: 'parent_uuid', - ancestorUuid: 'ancestor_uuid', + iId: Table.columnSetCommon.iId, + parentInternalId: 'p_i_id', + ancestorIId: 'a_i_id', recordUuid: 'record_uuid', recordCycle: 'record_cycle', recordStep: 'record_step', @@ -32,15 +32,15 @@ const rootDefColumnNames = [ ] const commonColumnNamesAndTypes = [ - `${columnSet.id} bigint NOT NULL GENERATED ALWAYS AS IDENTITY`, - `${columnSet.dateCreated} TIMESTAMP NOT NULL DEFAULT (now() AT TIME ZONE 'UTC')`, - `${columnSet.dateModified} TIMESTAMP NOT NULL DEFAULT (now() AT TIME ZONE 'UTC')`, - `${columnSet.uuid} uuid NOT NULL`, - `${columnSet.parentUuid} uuid NULL`, + `${columnSet.id} bigint NOT NULL GENERATED ALWAYS AS IDENTITY`, + `${columnSet.recordUuid} uuid NOT NULL`, + `${columnSet.iId} int NOT NULL`, + `${columnSet.parentInternalId} int NULL`, + `${columnSet.dateCreated} TIMESTAMP NOT NULL DEFAULT (now() AT TIME ZONE 'UTC')`, + `${columnSet.dateModified} TIMESTAMP NOT NULL DEFAULT (now() AT TIME ZONE 'UTC')`, ] const rootDefColumnNamesAndTypes = [ - `${columnSet.recordUuid} uuid NOT NULL`, `${columnSet.recordCycle} varchar(2) NOT NULL`, `${columnSet.recordStep} varchar(63) NOT NULL`, `${columnSet.recordOwnerUuid} uuid NOT NULL`, @@ -71,12 +71,20 @@ export default class TableDataNodeDef extends TableSurveyRdb { return this.getColumn(columnSet.id) } - get columnUuid() { - return this.getColumn(columnSet.uuid) + get columnIId() { + return this.getColumn(columnSet.iId) } - get columnParentUuid() { - return this.getColumn(columnSet.parentUuid) + get columnIIdName() { + return columnSet.iId + } + + get columnParentInternalId() { + return this.getColumn(columnSet.parentInternalId) + } + + get columnParentInternalIdName() { + return columnSet.parentInternalId } get columnRecordUuid() { @@ -121,7 +129,13 @@ export default class TableDataNodeDef extends TableSurveyRdb { getColumnNames = ({ includeAnalysis = true } = {}) => { const { nodeDef } = this const nodeDefsForColumns = this.getNodeDefsForColumns({ includeAnalysis }) - const names = [columnSet.uuid, columnSet.parentUuid, columnSet.dateCreated, columnSet.dateModified] + const names = [ + columnSet.recordUuid, + columnSet.iId, + columnSet.parentInternalId, + columnSet.dateCreated, + columnSet.dateModified, + ] if (NodeDef.isRoot(nodeDef)) { names.push(...rootDefColumnNames) } @@ -135,24 +149,30 @@ export default class TableDataNodeDef extends TableSurveyRdb { if (NodeDef.isRoot(this.nodeDef)) { columnsAndType.push(...rootDefColumnNamesAndTypes) } - this.columnNodeDefs.forEach((nodeDefColumn) => { - columnsAndType.push(...nodeDefColumn.names.map((name, i) => `${name} ${nodeDefColumn.types[i]}`)) - }) + for (const nodeDefColumn of this.columnNodeDefs) { + for (let i = 0; i < nodeDefColumn.names.length; i++) { + const name = nodeDefColumn.names[i] + const type = nodeDefColumn.types[i] + columnsAndType.push(`${name} ${type}`) + } + } return columnsAndType } - _getConstraintFk(tableReferenced, column) { - return `CONSTRAINT ${this.name}_${tableReferenced.name}_fk - FOREIGN KEY (${column}) - REFERENCES ${tableReferenced.nameQualified} (${columnSet.uuid}) + _getConstraintFk({ referencedTable, referencingColumnNames, referencedColumnNames }) { + return `CONSTRAINT ${this.name}_${referencedTable.name}_fk + FOREIGN KEY (${referencingColumnNames.join(', ')}) + REFERENCES ${referencedTable.nameQualified} (${referencedColumnNames.join(', ')}) ON DELETE CASCADE` } getConstraintFkRecord() { - if (!NodeDef.isRoot(this.nodeDef)) { - return null - } - return this._getConstraintFk(new TableRecord(this.surveyId), columnSet.recordUuid) + const tableRecord = new TableRecord(this.surveyId) + return this._getConstraintFk({ + referencedTable: tableRecord, + referencingColumnNames: [columnSet.recordUuid], + referencedColumnNames: [TableRecord.columnSet.uuid], + }) } getConstraintFkParent() { @@ -160,19 +180,25 @@ export default class TableDataNodeDef extends TableSurveyRdb { return null } const ancestorMultipleEntity = Survey.getNodeDefAncestorMultipleEntity(this.nodeDef)(this.survey) - return this._getConstraintFk(new TableDataNodeDef(this.survey, ancestorMultipleEntity), columnSet.parentUuid) + const ancestorEntityTable = new TableDataNodeDef(this.survey, ancestorMultipleEntity) + return this._getConstraintFk({ + referencedTable: ancestorEntityTable, + referencingColumnNames: [columnSet.recordUuid, columnSet.parentInternalId], + referencedColumnNames: [TableDataNodeDef.columnSet.recordUuid, TableDataNodeDef.columnSet.iId], + }) } - getConstraintUuidUnique() { - return `CONSTRAINT ${NodeDef.getName(this.nodeDef)}_uuid_unique_ix1 UNIQUE (${columnSet.uuid})` + getConstraintIIdUnique() { + return `CONSTRAINT ${NodeDef.getName(this.nodeDef)}_i_id_unique_ix1 UNIQUE (${columnSet.recordUuid}, ${columnSet.iId})` } getRowValuesByColumnName = ({ nodeRow, nodeDefColumns }) => { const { survey, nodeDef } = this const valuesByColumnName = TableDataNodeDefRowUtils.getValuesByColumnName({ survey, nodeRow, nodeDefColumns }) const result = { - [columnSet.uuid]: nodeRow[columnSet.uuid], - [columnSet.parentUuid]: nodeRow[columnSet.ancestorUuid], + [columnSet.iId]: nodeRow[columnSet.iId], + [columnSet.recordUuid]: nodeRow[columnSet.recordUuid], + [columnSet.parentInternalId]: nodeRow[columnSet.ancestorIId], [columnSet.dateCreated]: nodeRow[columnSet.dateCreated], [columnSet.dateModified]: nodeRow[columnSet.dateModified], } diff --git a/common/model/db/tables/node/select.js b/common/model/db/tables/node/select.js index 57cbe950ed..f72b9416c8 100644 --- a/common/model/db/tables/node/select.js +++ b/common/model/db/tables/node/select.js @@ -16,17 +16,16 @@ const _isInsideSingleEntity = ({ survey, nodeDefUuid }) => { /** * Generates the select query for the node table by the given parameters. - * * @param {!object} params - The query parameters. - * @param {string} [params.uuid=null] - The node uuid to filter by. - * @param {string} [params.recordUuid=null] - The record uuid to filter by. - * @param {string} [params.parentUuid=null] - The parent node uuid to filter by. - * @param {string} [params.nodeDefUuid=null] - The node definition uuid to filter by. - * @param {boolean} [params.draft=false] - Whether to fetch draft props or only published ones. + * @param {string} [params.uuid] - The node uuid to filter by. + * @param {string} [params.recordUuid] - The record uuid to filter by. + * @param {string} [params.parentIId] - The parent node internal id to filter by. + * @param {string} [params.nodeDefUuid] - The node definition uuid to filter by. + * @param {boolean} [params.draft] - Whether to fetch draft props or only published ones. * @returns {string} - The select query. */ export function getSelect(params) { - const { uuid = null, recordUuid = null, parentUuid = null, nodeDefUuid = null, draft = false } = params + const { uuid = null, recordUuid = null, parentIId = null, nodeDefUuid = null, draft = false } = params const _getPropsCombined = (table) => draft ? `${table.columnProps} || ${table.columnPropsDraft}` : `${table.columnProps}` @@ -47,20 +46,26 @@ export function getSelect(params) { } } + const _addIdEqualCondition = (column, value) => { + if (value) { + whereConditions.push(`${column} = ${value}`) + } + } + const _addParentNodeCondition = () => { if (nodeDefUuid && _isInsideSingleEntity({ survey: this.survey, nodeDefUuid })) { // node def is inside a single entity - // parentUuid is the uuid of the first ancestor multiple entity + // parentIId is the internal id of the first ancestor multiple entity // use node hierarchy in the where condition whereConditions.push( - `${parentUuid}::text IN (SELECT jsonb_array_elements_text(${this.columnMeta} -> '${Node.metaKeys.hierarchy}'))` + `${parentIId}::integer IN (SELECT jsonb_array_elements(${this.columnMeta} -> '${Node.metaKeys.hierarchy}'))` ) } else { - _addUuidEqualCondition(this.columnParentUuid, parentUuid) + _addIdEqualCondition(this.columnParentIId, parentIId) } } - _addUuidEqualCondition(this.columnUuid, uuid) + _addIdEqualCondition(this.columnIId, uuid) _addUuidEqualCondition(this.columnRecordUuid, recordUuid) _addUuidEqualCondition(this.columnNodeDefUuid, nodeDefUuid) _addParentNodeCondition() diff --git a/common/model/db/tables/node/table.js b/common/model/db/tables/node/table.js index 937f33c689..2103c3bf41 100644 --- a/common/model/db/tables/node/table.js +++ b/common/model/db/tables/node/table.js @@ -9,10 +9,10 @@ import TableSurvey from '../tableSurvey' import { getSelect } from './select' const columnSet = { - uuid: Table.columnSetCommon.uuid, id: Table.columnSetCommon.id, recordUuid: 'record_uuid', - parentUuid: 'parent_uuid', + iId: 'i_id', + parentIId: 'p_i_id', nodeDefUuid: 'node_def_uuid', value: 'value', meta: 'meta', @@ -38,8 +38,12 @@ export default class TableNode extends TableSurvey { return super.getColumn(columnSet.recordUuid) } - get columnParentUuid() { - return super.getColumn(columnSet.parentUuid) + get columnIId() { + return super.getColumn(columnSet.iId) + } + + get columnParentIId() { + return super.getColumn(columnSet.parentIId) } get columnNodeDefUuid() { diff --git a/common/model/db/tables/table.js b/common/model/db/tables/table.js index a1b4a3afbf..4b544f6c0f 100644 --- a/common/model/db/tables/table.js +++ b/common/model/db/tables/table.js @@ -5,6 +5,7 @@ const columnSetCommon = { dateCreated: 'date_created', dateModified: 'date_modified', id: 'id', + iId: 'i_id', props: 'props', propsDraft: 'props_draft', uuid: 'uuid', @@ -12,7 +13,6 @@ const columnSetCommon = { /** * A database table object. - * * @typedef {object} module:arena.Table * @property {string} schema - The schema it belongs to. * @property {string} name - The table name. @@ -22,10 +22,9 @@ const columnSetCommon = { export default class Table { /** * Create an instance of a Table. - * * @param {!string} schema - The schema. * @param {!string} name - The table name. - * @param {{[key: string]: string}} [columnSet={}] - The table column set. + * @param {{[key: string]: string}} [columnSet] - The table column set. */ constructor(schema, name, columnSet = {}) { if (new.target === Table) { diff --git a/common/model/db/views/dataNodeDef/view.js b/common/model/db/views/dataNodeDef/view.js index 0143e9b444..97d618a0fd 100644 --- a/common/model/db/views/dataNodeDef/view.js +++ b/common/model/db/views/dataNodeDef/view.js @@ -39,18 +39,18 @@ export default class ViewDataNodeDef extends TableDataNodeDef { } } - get columnNodeDefUuid() { + get columnNodeDefIId() { // node def can be the entity itself or it's source entity (if virtual) const { nodeDef } = this.tableData return new ColumnNodeDef(this, nodeDef) } - get columnUuid() { - return this.columnNodeDefUuid.nameFull + get columnNodeDefIIdNameFull() { + return this.columnNodeDefIId.nameFull } - get columnUuidName() { - return this.columnNodeDefUuid.name + get columnNodeDefIIdName() { + return this.columnNodeDefIId.name } get columnIdName() { @@ -58,17 +58,17 @@ export default class ViewDataNodeDef extends TableDataNodeDef { return `_${NodeDef.getName(nodeDef)}_${TableDataNodeDef.columnSet.id}` } - get columnNodeDefUuids() { + get columnNodeDefIIds() { return [ - this.columnNodeDefUuid, + this.columnNodeDefIId, ...(this.viewDataParent - ? this.viewDataParent.columnNodeDefUuids.map((columnNodeDef) => new ColumnNodeDef(this, columnNodeDef.nodeDef)) + ? this.viewDataParent.columnNodeDefIIds.map((columnNodeDef) => new ColumnNodeDef(this, columnNodeDef.nodeDef)) : []), ] } - get columnUuids() { - return this.columnNodeDefUuids.flatMap((columnNodeDef) => columnNodeDef.namesFull) + get columnNodeDefIIdsNamesFull() { + return this.columnNodeDefIIds.flatMap((columnNodeDef) => columnNodeDef.namesFull) } get _multipleAttributeColumns() { @@ -123,8 +123,12 @@ export default class ViewDataNodeDef extends TableDataNodeDef { return this.columnNodeDefs.flatMap((columnNodeDef) => new ColumnNodeDef(this, columnNodeDef.nodeDef).namesFull) } - get columnParentUuidName() { - return this.viewDataParent?.columnUuidName + get columnParentInternalId() { + return this.viewDataParent?.columnIId + } + + get columnParentInternalIdName() { + return this.viewDataParent?.columnIIdName } get tableData() { diff --git a/core/objectUtils.js b/core/objectUtils.js index 878bfa8e2e..a0ec4c1d36 100644 --- a/core/objectUtils.js +++ b/core/objectUtils.js @@ -11,9 +11,11 @@ export const keys = { draft: 'draft', extra: 'extra', id: 'id', + iId: 'iId', index: 'index', name: 'name', nodeDefUuid: 'nodeDefUuid', + pIId: 'pIId', parentUuid: 'parentUuid', props: 'props', propsDraft: 'propsDraft', @@ -31,6 +33,7 @@ export const keysProps = { // ====== READ export const getId = R.prop(keys.id) +export const getIId = R.prop(keys.iId) export const getUuid = R.propOr(null, keys.uuid) export const getProps = R.propOr({}, keys.props) @@ -40,6 +43,7 @@ export const isKeyTrue = (key) => (obj) => !!R.propOr(false, key)(obj) export const isPropTrue = (prop) => (obj) => !!getProp(prop)(obj) export const getParentUuid = R.propOr(null, keys.parentUuid) +export const getParentInternalId = R.propOr(null, keys.pIId) export const getLabels = getProp(keysProps.labels, {}) export const getLabel = (lang, defaultTo = null) => R.pipe(getLabels, R.propOr(defaultTo, lang)) @@ -132,6 +136,8 @@ export const toIndexedObj = (array, propNameOrExtractor) => export const toUuidIndexedObj = R.partialRight(toIndexedObj, [keys.uuid]) +export const toIIdIndexedObj = R.partialRight(toIndexedObj, [keys.iId]) + export const groupByProps = (...propNamesOrExtractors) => (items) => diff --git a/core/record/_record/recordKeys.js b/core/record/_record/recordKeys.js index 86765531c8..e5a70fbcde 100644 --- a/core/record/_record/recordKeys.js +++ b/core/record/_record/recordKeys.js @@ -5,6 +5,7 @@ export const keys = { dateCreated: ObjectUtils.keys.dateCreated, dateModified: ObjectUtils.keys.dateModified, info: 'info', + lastNodeInternalId: 'lastNodeInternalId', mergedIntoRecordUuid: 'mergedIntoRecordUuid', nodes: 'nodes', ownerEmail: 'ownerEmail', diff --git a/core/record/_record/recordNodesUpdater.js b/core/record/_record/recordNodesUpdater.js index 1da5b97dc0..d55be317b7 100644 --- a/core/record/_record/recordNodesUpdater.js +++ b/core/record/_record/recordNodesUpdater.js @@ -162,7 +162,7 @@ const _addOrUpdateAttribute = if (!attribute || NodeDef.isMultipleAttribute(attributeDef)) { // create new attribute const updateResult = new RecordUpdateResult({ record }) - const attributeCreated = Node.newNode(attributeDefUuid, record.uuid, entity, value) + const attributeCreated = Node.newNode({ record, nodeDefUuid: attributeDefUuid, parentNode: entity, value }) updateResult.addNode(attributeCreated, { sideEffect }) return updateResult } @@ -453,17 +453,17 @@ const deleteNodesInEntityByNodeDefUuid = async (record) => { const updateResult = new RecordUpdateResult({ record }) - const nodeUuidsToDelete = [] + const nodeIIdsToDelete = [] for (const nodeDefUuid of nodeDefUuids) { const children = RecordReader.getNodeChildrenByDefUuid(entity, nodeDefUuid)(record) - nodeUuidsToDelete.push(...children.map(Node.getUuid)) + nodeIIdsToDelete.push(...children.map(Node.getIId)) } const nodesDeleteUpdateResult = await deleteNodes({ user, survey, record, - nodeUuids: nodeUuidsToDelete, + nodeInternalIds: nodeIIdsToDelete, categoryItemProvider, taxonProvider, sideEffect, diff --git a/core/record/_record/recordReader.js b/core/record/_record/recordReader.js index 696e7bd22f..4580d07a9a 100644 --- a/core/record/_record/recordReader.js +++ b/core/record/_record/recordReader.js @@ -15,7 +15,7 @@ import { keys } from './recordKeys' const { getChildren: getNodeChildren, - getNodeByUuid, + getNodeByInternalId, getNodesByDefUuid, getParent: getParentNode, getRoot: getRootNode, @@ -24,10 +24,12 @@ const { /** * === simple getters. */ +export const getLastNodeInternalId = R.propOr(0, keys.lastNodeInternalId) + export const getNodes = R.propOr({}, keys.nodes) export const getNodesArray = (record) => Object.values(getNodes(record)) -export { getNodeChildren, getNodeByUuid, getNodesByDefUuid, getRootNode, getParentNode } +export { getNodeChildren, getNodeByInternalId, getNodesByDefUuid, getRootNode, getParentNode } export const findNodeChildren = (parentNode, childDefUuid) => (record) => { try { @@ -201,10 +203,10 @@ export const getParentCodeAttribute = (_survey, parentNode, nodeDef) => (record) export const visitAncestorCodeAttributes = ({ survey, parentNode, nodeDef, visitor }) => (record) => { - const visitedNodeUuids = new Set() // avoid cycles + const visitedNodeIIds = new Set() // avoid cycles let currentParentCodeAttribute = Records.getParentCodeAttribute({ parentNode, nodeDef })(record) - while (currentParentCodeAttribute && !visitedNodeUuids.has(Node.getUuid(currentParentCodeAttribute))) { - visitedNodeUuids.add(Node.getUuid(currentParentCodeAttribute)) + while (currentParentCodeAttribute && !visitedNodeIIds.has(Node.getIId(currentParentCodeAttribute))) { + visitedNodeIIds.add(Node.getIId(currentParentCodeAttribute)) visitor(currentParentCodeAttribute) const parentCodeAttributeNodeDef = SurveyNodeDefs.getNodeDefByUuid( Node.getNodeDefUuid(currentParentCodeAttribute) @@ -337,7 +339,7 @@ export const getAttributesUniqueDependent = ({ survey, record, node }) => { } else if (_isNodeDefUnique(nodeDef)) { siblingUniqueAttributes = getAttributesUniqueSibling({ record, attribute: node, attributeDef: nodeDef }) } - return ObjectUtils.toUuidIndexedObj(siblingUniqueAttributes) + return ObjectUtils.toIIdIndexedObj(siblingUniqueAttributes) } export const isNodeFilledByUser = (node) => (record) => Records.isNodeFilledByUser(node)(record) diff --git a/core/record/_record/recordUpdater.js b/core/record/_record/recordUpdater.js index 8745685a6f..757235ed48 100644 --- a/core/record/_record/recordUpdater.js +++ b/core/record/_record/recordUpdater.js @@ -14,9 +14,8 @@ import * as RecordReader from './recordReader' * Updates the record nodes with the ones passed as parameter. * Existing nodes will be replaced by the ones in the specified parameter. * Nodes marked as "deleted" will be removed from the record. - * * @param {object} nodes - The nodes to be added or updated. - * @param {boolean} [removeFlags = false] - True if flags like "deleted" or "created" must be removed from the nodes, false otherwise. + * @param {boolean} [removeFlags] - True if flags like "deleted" or "created" must be removed from the nodes, false otherwise. * @returns {object} - The updated record. */ export const mergeNodes = @@ -27,12 +26,12 @@ export const mergeNodes = recordUpdated[keys.nodes] = {} } - Object.entries(nodes).forEach(([nodeUuid, n]) => { + Object.entries(nodes).forEach(([nodeIId, n]) => { // Remove deleted node if (Node.isDeleted(n)) { recordUpdated = deleteNode(n, { sideEffect })(recordUpdated) } else { - const nodeExisting = RecordReader.getNodeByUuid(nodeUuid)(recordUpdated) + const nodeExisting = RecordReader.getNodeByInternalId(nodeIId)(recordUpdated) // Exclude dirty nodes currently being edited by the user const toBeAdded = !nodeExisting || // NodeExisting does not exist, n is new node @@ -64,11 +63,10 @@ export const assocNode = /** * Adds new nodes to the record. * Nodes shouldn't have been added previously to the record, so in this casa there is no need to check for duplicates. - * * @param {!object} params - The parameters. * @param {!object} [params.nodes] - The nodes to be added. - * @param {boolean} [params.updateNodesIndex = true] - True if the nodes must be added to the index (slower), false otherwise (faster). - * @param {boolean} [params.sideEffect = true] - True if the passed record object can be modified by calling this function (faster), false otherwise. + * @param {boolean} [params.updateNodesIndex] - True if the nodes must be added to the index (slower), false otherwise (faster). + * @param {boolean} [params.sideEffect] - True if the passed record object can be modified by calling this function (faster), false otherwise. * @returns {object} - The updated record. */ export const assocNodes = @@ -93,7 +91,7 @@ export const assocDateModified = (dateModified) => (record) => { export const deleteNode = (node, { sideEffect = false } = {}) => (record) => { - const nodeUuid = Node.getUuid(node) - const { record: recordUpdated } = Records.deleteNode(nodeUuid, { sideEffect })(record) + const nodeIId = Node.getIId(node) + const { record: recordUpdated } = Records.deleteNode(nodeIId, { sideEffect })(record) return recordUpdated } diff --git a/core/record/_record/recordsCombiner.js b/core/record/_record/recordsCombiner.js index e2fc512f28..e2ad8ca9d3 100644 --- a/core/record/_record/recordsCombiner.js +++ b/core/record/_record/recordsCombiner.js @@ -1,4 +1,4 @@ -import { Dates, Objects, Records, RecordUpdateResult, Surveys, UUIDs } from '@openforis/arena-core' +import { Dates, Objects, Records, RecordUpdateResult, Surveys } from '@openforis/arena-core' import * as A from '@core/arena' import * as Survey from '@core/survey/survey' @@ -12,30 +12,30 @@ import * as RecordReader from './recordReader' import { updateAttributeValue } from './recordNodeValueUpdater' import { afterNodesUpdate } from './recordNodesUpdaterCommon' -const findEntityByUuidOrKeys = ({ +const findEntityByIIdOrKeys = ({ survey, record, entityDefUuid, parentEntity, - uuid = null, + iId = null, keyValuesByDefUuid = null, }) => { - const entityWithSameUuid = uuid ? Records.getNodeByUuid(uuid)(record) : null - if (entityWithSameUuid) { - return entityWithSameUuid + const entityWithSameIId = iId ? Records.getNodeByIId(iId)(record) : null + if (entityWithSameIId) { + return entityWithSameIId } return keyValuesByDefUuid ? Records.findEntityByKeyValues({ survey, record, parentEntity, entityDefUuid, keyValuesByDefUuid }) : null } -const _findNodeWithSameUuid = (nodeSearch, nodesArray) => - nodesArray.find((node) => Node.getUuid(node) === Node.getUuid(nodeSearch)) +const _findNodeWithSameIId = (nodeSearch, nodesArray) => + nodesArray.find((node) => Node.getIId(node) === Node.getIId(nodeSearch)) -const _getNodesArrayDifference = (nodes, otherNodes) => nodes.filter((node) => !_findNodeWithSameUuid(node, otherNodes)) +const _getNodesArrayDifference = (nodes, otherNodes) => nodes.filter((node) => !_findNodeWithSameIId(node, otherNodes)) const _getNodesArrayIntersection = (nodes, otherNodes) => - nodes.filter((node) => !!_findNodeWithSameUuid(node, otherNodes)) + nodes.filter((node) => !!_findNodeWithSameIId(node, otherNodes)) const _replaceAttributeValueIfEmptyOrModified = ({ survey, @@ -99,8 +99,8 @@ const _replaceUpdatedNodesInEntities = ({ // delete nodes that are not in source record const childrenTargetToDelete = _getNodesArrayDifference(childrenTarget, childrenSource).map(Node.assocDeleted(true)) if (childrenTargetToDelete.length > 0) { - const childrenTargetToDeleteUuids = childrenTargetToDelete.map(Node.getUuid) - const nodesDeleteUpdateResult = Records.deleteNodes(childrenTargetToDeleteUuids, { sideEffect })( + const childrenTargetToDeleteIIds = childrenTargetToDelete.map(Node.getIId) + const nodesDeleteUpdateResult = Records.deleteNodes(childrenTargetToDeleteIIds, { sideEffect })( updateResult.record ) updateResult.merge(nodesDeleteUpdateResult) @@ -118,7 +118,7 @@ const _replaceUpdatedNodesInEntities = ({ // update existing nodes (nodes in both source and target records) _getNodesArrayIntersection(childrenSource, childrenTarget).forEach((childSource) => { - const childTargetToUpdate = _findNodeWithSameUuid(childSource, childrenTarget) + const childTargetToUpdate = _findNodeWithSameIId(childSource, childrenTarget) if (NodeDef.isAttribute(childDef)) { const attrUpdateResult = _replaceAttributeValueIfEmptyOrModified({ survey, @@ -152,9 +152,9 @@ export const replaceUpdatedNodes = async (recordTarget) => { const rootSource = RecordReader.getRootNode(recordSource) const rootTarget = RecordReader.getRootNode(recordTarget) - if (Node.getUuid(rootTarget) !== Node.getUuid(rootSource)) { + if (Node.getIId(rootTarget) !== Node.getIId(rootSource)) { // it should never happen... - throw new Error('error merging records: root entities have different uuids') + throw new Error('error merging records: root entities have different iIds') } const updateResult = _replaceUpdatedNodesInEntities({ survey, @@ -177,9 +177,9 @@ export const replaceUpdatedNodes = } const _recalculateNodeHierarchy = ({ parentEntity, node }) => { - const parentEntityUuid = Node.getUuid(parentEntity) - node[Node.keys.parentUuid] = parentEntityUuid - const hierarchyUpdated = [...Node.getHierarchy(parentEntity), parentEntityUuid] + const parentEntityIId = Node.getIId(parentEntity) + node[Node.keys.parentIId] = parentEntityIId + const hierarchyUpdated = [...Node.getHierarchy(parentEntity), parentEntityIId] Objects.setInPath({ obj: node, path: [Node.keys.meta, Node.metaKeys.hierarchy], value: hierarchyUpdated }) } @@ -187,17 +187,24 @@ const _addNodeToUpdateResult = ({ updateResult, node, parentEntity: parentEntityParam = undefined, - assignNewUuid = false, + assignNewIds = false, sideEffect = false, }) => { + const { record } = updateResult const newNodeToAdd = sideEffect ? Node.setCreated(node) : Node.assocCreated(true)(node) - if (assignNewUuid) { - newNodeToAdd[Node.keys.uuid] = UUIDs.v4() + if (assignNewIds) { + const lastNodeInternalId = RecordReader.getLastNodeInternalId(record) + const newInternalId = lastNodeInternalId + 1 + newNodeToAdd[Node.keys.iId] = newInternalId + const recordUpdated = { ...record, lastNodeInternalId: newInternalId } + updateResult.merge(new RecordUpdateResult({ record: recordUpdated })) } - delete newNodeToAdd[Node.keys.id] // clear internal id - newNodeToAdd[Node.keys.recordUuid] = updateResult.record.uuid + // clear id, used for storage in DB, but not needed for the updateResult + delete newNodeToAdd[Node.keys.id] - const parentEntity = parentEntityParam ?? RecordReader.getNodeByUuid(Node.getParentUuid(node))(updateResult.record) + newNodeToAdd[Node.keys.recordUuid] = record.uuid + + const parentEntity = parentEntityParam ?? RecordReader.getNodeByInternalId(Node.getParentInternalId(node))(record) _recalculateNodeHierarchy({ node: newNodeToAdd, parentEntity }) updateResult.addNode(newNodeToAdd, { sideEffect }) } @@ -252,12 +259,12 @@ const _areNotSameValues = ({ survey, childDef, updateResult, entityTarget, sourc }) const replaceNodes = ({ childrenSource, childrenTarget, entityTarget, sideEffect, updateResult }) => { - const childrenTargetToDeleteUuids = childrenTarget.map(Node.getUuid) - const nodesDeleteUpdateResult = Records.deleteNodes(childrenTargetToDeleteUuids, { sideEffect })(updateResult.record) + const childrenTargetToDeleteIIds = childrenTarget.map(Node.getIId) + const nodesDeleteUpdateResult = Records.deleteNodes(childrenTargetToDeleteIIds, { sideEffect })(updateResult.record) updateResult.merge(nodesDeleteUpdateResult) - childrenSource.forEach((childSource) => { + for (const childSource of childrenSource) { _addNodeToUpdateResult({ updateResult, node: childSource, parentEntity: entityTarget }) - }) + } } const _mergeMultipleAttributes = ({ @@ -281,7 +288,7 @@ const _mergeMultipleAttributes = ({ } } else { // keep nodes from both records, unless they have the same value - childrenSource.forEach((childSource) => { + for (const childSource of childrenSource) { const sourceValue = Node.getValue(childSource) if ( !targetValues.find((targetValue) => @@ -296,9 +303,9 @@ const _mergeMultipleAttributes = ({ ) ) { // value not in target values => add it to the record - _addNodeToUpdateResult({ updateResult, node: childSource, parentEntity: entityTarget, assignNewUuid: true }) + _addNodeToUpdateResult({ updateResult, node: childSource, parentEntity: entityTarget, assignNewIds: true }) } - }) + } } } } @@ -310,21 +317,22 @@ const _cloneEntityAndDescendants = async ({ parentEntity, sideEffect = false, }) => { - const newNodeUuidByOldUuid = {} + const newNodeIIdByOldIId = {} + let lastNodeInternalId = RecordReader.getLastNodeInternalId(recordSource) RecordReader.visitDescendantsAndSelf(entitySource, (visitedChildSource) => { - const oldUuid = Node.getUuid(visitedChildSource) - const oldParentUuid = Node.getParentUuid(visitedChildSource) - const newUuid = UUIDs.v4() - newNodeUuidByOldUuid[oldUuid] = newUuid - const newParentEntityUuid = + const oldIId = Node.getIId(visitedChildSource) + const oldParentIId = Node.getParentInternalId(visitedChildSource) + const newIId = ++lastNodeInternalId + newNodeIIdByOldIId[oldIId] = newIId + const newParentEntityIId = visitedChildSource === entitySource - ? Node.getUuid(parentEntity) - : (newNodeUuidByOldUuid[oldParentUuid] ?? oldParentUuid) + ? Node.getIId(parentEntity) + : (newNodeIIdByOldIId[oldParentIId] ?? oldParentIId) // if parent node is not in the visited path, keep the same parentIId (it will be updated in hierarchy recalculation) const nodeTarget = ObjectUtils.clone(visitedChildSource) Node.removeFlags({ sideEffect: true })(nodeTarget) nodeTarget[Node.keys.created] = true // consider it as new node, to allow RDB updates - nodeTarget[Node.keys.uuid] = newUuid - nodeTarget[Node.keys.parentUuid] = newParentEntityUuid + nodeTarget[Node.keys.iId] = newIId + nodeTarget[Node.keys.pIId] = newParentEntityIId // node hierarchy will be recalculated in _addNodeToUpdateResult _addNodeToUpdateResult({ updateResult, node: nodeTarget, sideEffect }) })(recordSource) @@ -347,12 +355,12 @@ const _mergeMultipleEntities = ({ record: recordSource, entity: childSource, }) - const childTarget = findEntityByUuidOrKeys({ + const childTarget = findEntityByIIdOrKeys({ survey, record: updateResult.record, entityDefUuid: childDefUuid, parentEntity: entityTarget, - uuid: Node.getUuid(childSource), + iId: Node.getIId(childSource), keyValuesByDefUuid, }) if (childTarget) { diff --git a/core/record/node.js b/core/record/node.js index f30ca30741..9b646ccc0d 100644 --- a/core/record/node.js +++ b/core/record/node.js @@ -4,7 +4,6 @@ import { Objects } from '@openforis/arena-core' import * as ObjectUtils from '@core/objectUtils' import * as StringUtils from '@core/stringUtils' -import { uuidv4 } from '@core/uuid' import * as Validation from '@core/validation/validation' import * as NodeDef from '@core/survey/nodeDef' @@ -43,8 +42,9 @@ const flagKeysIncludingDirty = [...flagKeysArray, dirtyFlag] export const keys = { id: ObjectUtils.keys.id, + iId: ObjectUtils.keys.iId, uuid: ObjectUtils.keys.uuid, - parentUuid: ObjectUtils.keys.parentUuid, + pIId: ObjectUtils.keys.pIId, dateCreated: ObjectUtils.keys.dateCreated, dateModified: ObjectUtils.keys.dateModified, recordUuid: 'recordUuid', @@ -70,9 +70,9 @@ export const isValueProp = ({ nodeDef, prop }) => Boolean(R.path([NodeDef.getTyp // ====== // -export const { getId, getUuid } = ObjectUtils +export const { getId, getIId, getUuid } = ObjectUtils -export const { getParentUuid } = ObjectUtils +export const { getParentInternalId } = ObjectUtils export const getRecordUuid = R.prop(keys.recordUuid) @@ -107,7 +107,7 @@ export const isCreated = R.propEq(keys.created, true) export const isUpdated = R.propEq(keys.updated, true) export const isDeleted = R.propEq(keys.deleted, true) export const isDirty = R.propEq(dirtyFlag, true) -export const isRoot = R.pipe(getParentUuid, R.isNil) +export const isRoot = R.pipe(getParentInternalId, R.isNil) export const { isEqual } = ObjectUtils export const { getValidation } = Validation @@ -126,13 +126,13 @@ export const isDescendantOf = (ancestor) => (node) => R.includes(getUuid(ancesto // ====== // -export const newNode = (nodeDefUuid, recordUuid, parentNode = null, value = null) => { +export const newNode = ({ record, nodeDefUuid, parentNode = null, value = null }) => { const now = new Date() return { - [keys.uuid]: uuidv4(), [keys.nodeDefUuid]: nodeDefUuid, - [keys.recordUuid]: recordUuid, - [keys.parentUuid]: getUuid(parentNode), + [keys.recordUuid]: ObjectUtils.getUuid(record), + [keys.iId]: record.lastInternalId + 1, + [keys.pIId]: getIId(parentNode), [keys.value]: value, [keys.meta]: { [metaKeys.hierarchy]: parentNode ? R.append(getUuid(parentNode), getHierarchy(parentNode)) : [], @@ -144,7 +144,7 @@ export const newNode = (nodeDefUuid, recordUuid, parentNode = null, value = null } export const newNodePlaceholder = (nodeDef, parentNode, value = null) => ({ - ...newNode(NodeDef.getUuid(nodeDef), getRecordUuid(parentNode), parentNode, value), + ...newNode({ record, nodeDefUuid: NodeDef.getUuid(nodeDef), parentNode, value }), [keys.placeholder]: true, }) @@ -153,6 +153,7 @@ export const newNodePlaceholder = (nodeDef, parentNode, value = null) => ({ // UPDATE // ====== // +export const assocIId = R.assoc(keys.iId) export const assocValue = R.assoc(keys.value) export const { assocValidation } = Validation @@ -163,6 +164,14 @@ export const setCreated = (node) => { node[keys.created] = true return node } +export const setUpdated = (node) => { + node[keys.updated] = true + return node +} +export const setDeleted = (node) => { + node[keys.deleted] = true + return node +} export const assocDeleted = R.assoc(keys.deleted) export const assocUpdated = R.assoc(keys.updated) export const assocDirty = R.assoc(dirtyFlag) diff --git a/core/record/nodeKeys.js b/core/record/nodeKeys.js index 18feddea19..68010b3b52 100644 --- a/core/record/nodeKeys.js +++ b/core/record/nodeKeys.js @@ -5,7 +5,7 @@ import * as NodeDef from '@core/survey/nodeDef' export const keys = { nodeId: 'nodeId', - nodeUuid: 'nodeUuid', + nodeIId: 'nodeIId', nodeDefUuid: 'nodeDefUuid', recordUuid: 'recordUuid', keys: 'keys', diff --git a/core/record/record.js b/core/record/record.js index 5c075f3207..e58eda6509 100644 --- a/core/record/record.js +++ b/core/record/record.js @@ -56,8 +56,8 @@ export const getCreatedWithAppId = (record) => { const createdWith = info[infoKeys.createdWith] return AppInfo.getAppId(createdWith) } - -export const { getNodes, getNodesArray, getNodeByUuid, getRootNode, getNodesByDefUuid } = RecordReader +export const { getLastNodeInternalId, getNodes, getNodesArray, getNodeByInternalId, getRootNode, getNodesByDefUuid } = + RecordReader // ==== hierarchy export const { diff --git a/core/record/recordFile.js b/core/record/recordFile.js index ae632e93e4..2460818486 100644 --- a/core/record/recordFile.js +++ b/core/record/recordFile.js @@ -18,7 +18,7 @@ export const propKeys = { name: 'name', size: 'size', recordUuid: 'recordUuid', - nodeUuid: 'nodeUuid', + nodeIId: 'nodeIId', } export const invalidPropKeys = { @@ -26,13 +26,13 @@ export const invalidPropKeys = { fileSize: 'fileSize', } -export const createFile = ({ name, uuid = null, size = null, content = null, recordUuid = null, nodeUuid = null }) => ({ +export const createFile = ({ name, uuid = null, size = null, content = null, recordUuid = null, nodeIId = null }) => ({ [keys.uuid]: uuid ?? uuidv4(), [keys.props]: { [propKeys.name]: name, [propKeys.size]: size, [propKeys.recordUuid]: recordUuid, - [propKeys.nodeUuid]: nodeUuid, + [propKeys.nodeIId]: nodeIId, }, [keys.content]: content, }) @@ -42,7 +42,7 @@ export const createFileFromNode = ({ node, size = null, content = null }) => uuid: Node.getFileUuid(node), name: Node.getFileName(node), recordUuid: Node.getRecordUuid(node), - nodeUuid: Node.getUuid(node), + nodeIId: Node.getIId(node), size, content, }) @@ -62,7 +62,7 @@ export const { getUuid, getProps } = ObjectUtils export const isDeleted = (file) => Boolean(ObjectUtils.getProp(propKeys.deleted, false)(file)) export const getName = ObjectUtils.getProp(propKeys.name) export const getSize = ObjectUtils.getProp(propKeys.size) -export const getNodeUuid = ObjectUtils.getProp(propKeys.nodeUuid) +export const getNodeIId = ObjectUtils.getProp(propKeys.nodeIId) export const getRecordUuid = ObjectUtils.getProp(propKeys.recordUuid) export const getContent = R.prop(keys.content) export const getExtension = R.pipe(getName, FileNames.getExtension) @@ -85,7 +85,7 @@ export const cleanupInvalidProps = (file) => { [propKeys.name]: props[invalidPropKeys.fileName], [propKeys.size]: props[invalidPropKeys.fileSize], [propKeys.recordUuid]: props[propKeys.recordUuid], - [propKeys.nodeUuid]: props[propKeys.nodeUuid], + [propKeys.nodeIId]: props[propKeys.nodeIId], } if (props.deleted) { propsUpdated[propKeys.deleted] = true diff --git a/core/record/recordValidation.js b/core/record/recordValidation.js index db3d2207b9..6f3015d91c 100644 --- a/core/record/recordValidation.js +++ b/core/record/recordValidation.js @@ -1,5 +1,6 @@ import * as R from 'ramda' +import * as Node from '@core/record/node' import * as Validation from '@core/validation/validation' import * as ValidationResult from '@core/validation/validationResult' import { RecordValidations } from '@openforis/arena-core' @@ -12,8 +13,8 @@ export const keys = { export const prefixValidationFieldChildrenCount = 'childrenCount_' // ===== UTILS -export const getValidationChildrenCountKey = (nodeParentUuid, nodeDefChildUuid) => - RecordValidations.getValidationChildrenCountKey({ nodeParentUuid, nodeDefChildUuid }) +export const getValidationChildrenCountKey = (nodeParentIId, nodeDefChildUuid) => + RecordValidations.getValidationChildrenCountKey({ nodeParentInternalId: nodeParentIId, nodeDefChildUuid }) export const isValidationFieldKeyChildrenCount = R.startsWith(prefixValidationFieldChildrenCount) export const isValidationResultErrorCount = (validationResult) => ValidationResult.getKey(validationResult).startsWith('record.nodes.count.') @@ -30,11 +31,11 @@ export const newValidationRecordDuplicate = ({ // ===== READ -export const getValidationChildrenCount = (nodeParentUuid, nodeDefChildUuid) => - RecordValidations.getValidationChildrenCount({ nodeParentUuid, nodeDefChildUuid }) +export const getValidationChildrenCount = (nodeParentIId, nodeDefChildUuid) => + RecordValidations.getValidationChildrenCount({ nodeParentInternalId: nodeParentIId, nodeDefChildUuid }) -export const getNodeValidation = (node) => RecordValidations.getValidationNode({ nodeUuid: node?.uuid }) +export const getNodeValidation = (node) => RecordValidations.getValidationNode({ nodeInternalId: Node.getIId(node) }) // ===== UPDATE -export const setValidationCount = (nodeParentUuid, nodeDefChildUuid, validationCount) => - Validation.setField(getValidationChildrenCountKey(nodeParentUuid, nodeDefChildUuid), validationCount) +export const setValidationCount = (nodeParentIId, nodeDefChildUuid, validationCount) => + Validation.setField(getValidationChildrenCountKey(nodeParentIId, nodeDefChildUuid), validationCount) diff --git a/core/record/recordValidationReportItem.js b/core/record/recordValidationReportItem.js index 9afc744f04..dd34b8d39b 100644 --- a/core/record/recordValidationReportItem.js +++ b/core/record/recordValidationReportItem.js @@ -13,7 +13,7 @@ const keys = { recordOwnerUuid: 'recordOwnerUuid', recordStep: 'recordStep', recordUuid: 'recordUuid', - nodeUuid: 'nodeUuid', + nodeIId: 'nodeIId', nodeDefUuid: 'nodeDefUuid', keysHierarchy: 'keysHierarchy', keysSelf: 'keysSelf', @@ -28,7 +28,7 @@ export const getRecordDateModified = R.prop(keys.recordDateModified) export const getRecordOwnerName = R.prop(keys.recordOwnerName) export const getRecordOwnerUuid = R.prop(keys.recordOwnerUuid) export const getRecordUuid = R.prop(keys.recordUuid) -const getNodeUuid = R.prop(keys.nodeUuid) +const getNodeIId = R.prop(keys.nodeIId) const getNodeDefUuid = R.prop(keys.nodeDefUuid) const getValidationCountChildDefUuid = R.prop(keys.validationCountChildDefUuid) const getKeysSelf = R.propOr({}, keys.keysSelf) @@ -64,14 +64,14 @@ export const getPath = return NodeKeys.getKeysHierarchyPath({ survey, lang, includeRootKeys: true, labelType })(keys) } -export const getNodeContextUuid = R.ifElse( +export const getNodeContextIId = R.ifElse( isValidationCount, - getNodeUuid, // Node has a validation count, the context will be the node itself (an entity) + getNodeIId, // Node has a validation count, the context will be the node itself (an entity) R.pipe( // Node is an attribute, the context node will be its parent entity (it's the last item of the hierarchy) R.prop(keys.keysHierarchy), R.last, - R.prop(NodeKeys.keys.nodeUuid) + R.prop(NodeKeys.keys.nodeIId) ) ) diff --git a/package.json b/package.json index ce10a9a7a4..915f5dafe8 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,8 @@ "@mui/x-data-grid": "^8.26.0", "@mui/x-date-pickers": "^8.26.0", "@mui/x-tree-view": "^8.26.0", - "@openforis/arena-server": "^1.2.19", + "@openforis/arena-core": "../arena-core", + "@openforis/arena-server": "../arena-server", "@reduxjs/toolkit": "^2.11.2", "@sendgrid/mail": "^8.1.6", "@shopify/draggable": "^1.2.1", diff --git a/server/modules/activityLog/repository/activityLogRepository.js b/server/modules/activityLog/repository/activityLogRepository.js index 264a0f14ce..3eebdd3fc7 100644 --- a/server/modules/activityLog/repository/activityLogRepository.js +++ b/server/modules/activityLog/repository/activityLogRepository.js @@ -216,7 +216,7 @@ export const fetch = async ({ LEFT OUTER JOIN ${NodeKeysHierarchyView.getNameWithSchema(surveyId)} n_h ON - l.content_uuid = n_h.${NodeKeysHierarchyView.columns.nodeUuid} + l.content_uuid = n_h.${NodeKeysHierarchyView.columns.nodeIId} ` : '' } diff --git a/server/modules/analysis/service/rChain/PersistResultsJob.js b/server/modules/analysis/service/rChain/PersistResultsJob.js index 60a258adc8..992849d98f 100644 --- a/server/modules/analysis/service/rChain/PersistResultsJob.js +++ b/server/modules/analysis/service/rChain/PersistResultsJob.js @@ -76,9 +76,9 @@ export default class PersistResultsJob extends FlatDataImportJob { await this.setStatusFailed() return } - const { record_uuid: recordUuid, parent_uuid: entityUuid } = row + const { record_uuid: recordUuid, p_i_id: entityIId } = row const record = await this.recordsProvider.getOrFetch(recordUuid) - const entity = Record.getNodeByUuid(entityUuid)(record) + const entity = Record.getNodeByInternalId(entityIId)(record) const { nodes: nodesUpdated, record: recordUpdated } = await Record.updateAttributesInEntityWithValues({ survey, entity, diff --git a/server/modules/analysis/service/rChain/rFile/system/dfResults.js b/server/modules/analysis/service/rChain/rFile/system/dfResults.js index 386ef282e5..2a10b94890 100644 --- a/server/modules/analysis/service/rChain/rFile/system/dfResults.js +++ b/server/modules/analysis/service/rChain/rFile/system/dfResults.js @@ -73,19 +73,19 @@ export default class DfResults { const columnNodeDef = NodeDef.isVirtual(this.entity) ? Survey.getNodeDefParent(this.entity)(this.survey) : this.entity - const setUuids = [ + const setIdentifiers = [ { name: NodeDef.keysPropsAdvanced.chainUuid, value: quote(this.rChain.chainUuid) }, { name: TableNode.columnSet.recordUuid, value: dfVar(this.dfSourceName, ViewDataNodeDef.columnSet.recordUuid), }, { - name: TableNode.columnSet.parentUuid, + name: TableNode.columnSet.parentIId, value: dfVar(this.dfSourceName, ColumnNodeDef.getColumnName(columnNodeDef)), }, ].map((uuidMapping) => setVar(dfVar(this.name, uuidMapping.name), uuidMapping.value)) - this.scripts.push(...setUuids) + this.scripts.push(...setIdentifiers) } initCodeAttributes() { diff --git a/server/modules/collectImport/service/collectImport/dataImportJobs/collectAttributeValueExtractor.js b/server/modules/collectImport/service/collectImport/dataImportJobs/collectAttributeValueExtractor.js index 8b5bd78068..40c67f5c25 100644 --- a/server/modules/collectImport/service/collectImport/dataImportJobs/collectAttributeValueExtractor.js +++ b/server/modules/collectImport/service/collectImport/dataImportJobs/collectAttributeValueExtractor.js @@ -126,7 +126,7 @@ const extractFileValueAndMeta = (survey, node, collectSurveyFileZip, collectNode size: fileSize, content, recordUuid: Node.getRecordUuid(node), - nodeUuid: Node.getUuid(node), + nodeIId: Node.getIId(node), }) await FileManager.insertFile(Survey.getId(survey), file, tx) diff --git a/server/modules/collectImport/service/collectImport/dataImportJobs/recordsImportJob.js b/server/modules/collectImport/service/collectImport/dataImportJobs/recordsImportJob.js index 68c6ff6607..126e93b665 100644 --- a/server/modules/collectImport/service/collectImport/dataImportJobs/recordsImportJob.js +++ b/server/modules/collectImport/service/collectImport/dataImportJobs/recordsImportJob.js @@ -169,7 +169,6 @@ export default class RecordsImportJob extends Job { async traverseCollectRecordAndInsertNodes({ survey, record, collectRecordJson, nodeDefNamesByPath }) { const { collectSurveyFileZip, collectSurvey } = this.context - const recordUuid = Record.getUuid(record) let recordUpdated = { ...record } const collectRootEntityName = CollectRecord.getRootEntityName(collectRecordJson) @@ -202,7 +201,7 @@ export default class RecordsImportJob extends Job { continue } - let nodeToInsert = Node.newNode(nodeDefUuid, recordUuid, nodeParent) + let nodeToInsert = Node.newNode({ record: recordUpdated, nodeDefUuid, parentNode: nodeParent }) const valueAndMeta = NodeDef.isAttribute(nodeDef) ? await CollectAttributeValueExtractor.extractAttributeValueAndMeta({ diff --git a/server/modules/dataImport/service/DataImportJob/DataImportBaseJob.js b/server/modules/dataImport/service/DataImportJob/DataImportBaseJob.js index 79e61919d8..f28b99836d 100644 --- a/server/modules/dataImport/service/DataImportJob/DataImportBaseJob.js +++ b/server/modules/dataImport/service/DataImportJob/DataImportBaseJob.js @@ -73,7 +73,7 @@ export default class DataImportBaseJob extends Job { } } await RecordManager.assocRefDataToNodes({ survey, nodes: nodesArray }, tx) - const { record: recordUpdated, rdbUpdates } = RecordManager.generateRdbUpates({ survey, record, nodesArray }, tx) + const { record: recordUpdated, rdbUpdates } = RecordManager.generateRdbUpdates({ survey, record, nodesArray }, tx) await this.rdbUpdatesBatchPersister.addItem(rdbUpdates) this.currentRecord = recordUpdated @@ -117,7 +117,6 @@ export default class DataImportBaseJob extends Job { /** * Updates the record modified date using the max modified date of the nodes. - * * @param {!object} record - The record object. * @returns {object} - The modified record. */ diff --git a/server/modules/dataImport/service/DataImportJob/EntitiesDeleteJob.js b/server/modules/dataImport/service/DataImportJob/EntitiesDeleteJob.js index ed4c85fa00..ed50e0d7b5 100644 --- a/server/modules/dataImport/service/DataImportJob/EntitiesDeleteJob.js +++ b/server/modules/dataImport/service/DataImportJob/EntitiesDeleteJob.js @@ -34,23 +34,23 @@ export default class EntitiesDeleteJob extends DataImportBaseJob { const recordUuid = Record.getUuid(currentRecord) const sideEffect = !includeFiles const nodes = Record.getNodesByDefUuid(nodeDefUuid)(currentRecord) - const nodeUuidsToDelete = nodes.reduce((acc, node) => { - const nodeUuid = Node.getUuid(node) - if (!entityUuidTouchedByRecordUuid[recordUuid]?.[nodeUuid]) { - acc.push(nodeUuid) + const nodeIIdsToDelete = nodes.reduce((acc, node) => { + const nodeIId = Node.getIId(node) + if (!entityUuidTouchedByRecordUuid[recordUuid]?.[nodeIId]) { + acc.push(nodeIId) } return acc }, []) - if (nodeUuidsToDelete.length === 0) return null + if (nodeIIdsToDelete.length === 0) return null const updateResult = await Record.deleteNodes({ user, survey, record: currentRecord, - nodeUuids: nodeUuidsToDelete, + nodeInternalIds: nodeIIdsToDelete, sideEffect, }) - this.entitiesDeleted += nodeUuidsToDelete.length + this.entitiesDeleted += nodeIIdsToDelete.length const { nodes: nodesUpdated } = updateResult diff --git a/server/modules/dataImport/service/DataImportJob/FlatDataImportJob.js b/server/modules/dataImport/service/DataImportJob/FlatDataImportJob.js index 4a769a1085..f566b25278 100644 --- a/server/modules/dataImport/service/DataImportJob/FlatDataImportJob.js +++ b/server/modules/dataImport/service/DataImportJob/FlatDataImportJob.js @@ -38,11 +38,11 @@ export default class FlatDataImportJob extends DataImportBaseJob { this.dataImportFileReader = null this.flatDataReader = null - this.entitiesWithMultipleAttributesClearedByUuid = {} // used to clear multiple attribute values only once + this.entitiesWithMultipleAttributesClearedByIId = {} // used to clear multiple attribute values only once this.updatedFilesByUuid = {} this.updatedFilesByName = {} this.filesToDeleteByUuid = {} - this.entityUuidTouchedByRecordUuid = {} + this.entityIIdTouchedByRecordUuid = {} this.entitiesCreated = 0 } @@ -100,7 +100,7 @@ export default class FlatDataImportJob extends DataImportBaseJob { dataImportFileReader: this.dataImportFileReader, updatedFilesByUuid: this.updatedFilesByUuid, filesToDeleteByUuid: this.filesToDeleteByUuid, - entityUuidTouchedByRecordUuid: this.entityUuidTouchedByRecordUuid, + entityIIdTouchedByRecordUuid: this.entityIIdTouchedByRecordUuid, }) } } @@ -241,9 +241,9 @@ export default class FlatDataImportJob extends DataImportBaseJob { sideEffect, })(this.currentRecord) - const entityUuid = Node.getUuid(entity) + const entityIId = Node.getIId(entity) - Objects.setInPath({ obj: this.entityUuidTouchedByRecordUuid, path: [recordUuid, entityUuid], value: true }) + Objects.setInPath({ obj: this.entityIIdTouchedByRecordUuid, path: [recordUuid, entityIId], value: true }) if (Node.isCreated(entity)) { this.entitiesCreated += 1 @@ -294,8 +294,8 @@ export default class FlatDataImportJob extends DataImportBaseJob { const multipleAttributeDefsBeingUpdated = Object.keys(valuesByDefUuid) .map((nodeDefUuid) => Survey.getNodeDefByUuid(nodeDefUuid)(survey)) .filter(NodeDef.isMultipleAttribute) - const entityUuid = Node.getUuid(entity) - if (multipleAttributeDefsBeingUpdated.length > 0 && !this.entitiesWithMultipleAttributesClearedByUuid[entityUuid]) { + const entityIId = Node.getIId(entity) + if (multipleAttributeDefsBeingUpdated.length > 0 && !this.entitiesWithMultipleAttributesClearedByIId[entityIId]) { const nodeDefUuidsToClear = multipleAttributeDefsBeingUpdated.map(NodeDef.getUuid) const entityClearUpdateResult = await Record.deleteNodesInEntityByNodeDefUuid({ survey, @@ -305,7 +305,7 @@ export default class FlatDataImportJob extends DataImportBaseJob { })(this.currentRecord) this.currentRecord = entityClearUpdateResult.record - this.entitiesWithMultipleAttributesClearedByUuid[entityUuid] = true + this.entitiesWithMultipleAttributesClearedByIId[entityIId] = true await this.persistUpdatedNodes({ nodesUpdated: entityClearUpdateResult.nodes }) } @@ -318,7 +318,7 @@ export default class FlatDataImportJob extends DataImportBaseJob { const nodeDefUuid = Node.getNodeDefUuid(node) const nodeDef = Survey.getNodeDefByUuid(nodeDefUuid)(survey) if (NodeDef.isFile(nodeDef)) { - const oldNode = Record.getNodeByUuid(Node.getUuid(node))(originalRecord) + const oldNode = Record.getNodeByInternalId(Node.getIId(node))(originalRecord) if (!Node.isValueBlank(oldNode)) { const fileToDeleteUuid = Node.getFileUuid(oldNode) filesToDeleteByUuid[fileToDeleteUuid] = RecordFile.createFileFromNode({ node: oldNode }) diff --git a/server/modules/mobile/service/arenaMobileDataImport/jobs/recordsImportJob.js b/server/modules/mobile/service/arenaMobileDataImport/jobs/recordsImportJob.js index 803b4a18f3..a82d82810f 100644 --- a/server/modules/mobile/service/arenaMobileDataImport/jobs/recordsImportJob.js +++ b/server/modules/mobile/service/arenaMobileDataImport/jobs/recordsImportJob.js @@ -29,9 +29,9 @@ const checkNodeIsValid = ({ nodes, node, nodeDef }) => { if (!nodeDef) { return { valid: false, error: 'refers a missing node definition' } } - const parentUuid = Node.getParentUuid(node) - if ((!parentUuid && !NodeDef.isRoot(nodeDef)) || (parentUuid && !nodes[parentUuid])) { - return { valid: false, error: `has missing or invalid parent_uuid` } + const parentIId = Node.getParentInternalId(node) + if ((!parentIId && !NodeDef.isRoot(nodeDef)) || (parentIId && !nodes[parentIId])) { + return { valid: false, error: `has missing or invalid parent internal id` } } if (NodeDef.isMultipleAttribute(nodeDef) && Node.isValueBlank(node)) { return { valid: false, error: `is multiple and has an empty value` } @@ -39,7 +39,7 @@ const checkNodeIsValid = ({ nodes, node, nodeDef }) => { const nodeHierarchy = Node.getHierarchy(node) if ( nodeHierarchy.length !== NodeDef.getMetaHierarchy(nodeDef)?.length || - nodeHierarchy.some((ancestorUuid) => !nodes[ancestorUuid]) + nodeHierarchy.some((ancestorIId) => !nodes[ancestorIId]) ) { return { valid: false, error: `has an invalid meta hierarchy` } } @@ -139,7 +139,7 @@ export default class RecordsImportJob extends DataImportBaseJob { delete record['_nodesIndex'] const nodes = Record.getNodes(record) - for (const [nodeUuid, node] of Object.entries(nodes)) { + for (const [nodeIId, node] of Object.entries(nodes)) { const nodeDefUuid = Node.getNodeDefUuid(node) const nodeDef = Survey.getNodeDefByUuid(nodeDefUuid)(survey) const { valid, error } = checkNodeIsValid({ nodes, node, nodeDef }) @@ -148,10 +148,10 @@ export default class RecordsImportJob extends DataImportBaseJob { node[Node.keys.recordUuid] = recordUuid Node.removeFlags({ sideEffect: true })(node) } else { - const messagePrefix = `record ${Record.getUuid(record)}: node with uuid ${Node.getUuid(node)} and node def ${NodeDef.getName(nodeDef)} (uuid ${nodeDefUuid})` + const messagePrefix = `record ${Record.getUuid(record)}: node with internal id ${nodeIId} and node def ${NodeDef.getName(nodeDef)} (uuid ${nodeDefUuid})` const messageSuffix = `: skipping it` this.logWarn(`${messagePrefix} ${error} ${messageSuffix}`) - delete nodes[nodeUuid] + delete nodes[nodeIId] } } // assoc nodes and build index from scratch @@ -275,19 +275,19 @@ export default class RecordsImportJob extends DataImportBaseJob { const nodesIndexedByUuid = Record.getNodesArray(record) .sort((nodeA, nodeB) => Node.getHierarchy(nodeA).length - Node.getHierarchy(nodeB).length) .reduce((acc, node) => { - const nodeUuid = Node.getUuid(node) + const nodeIId = Node.getIId(node) const nodeDefUuid = Node.getNodeDefUuid(node) // check that the node definition associated to the node has not been deleted from the survey const nodeDef = Survey.getNodeDefByUuid(nodeDefUuid)(survey) if (nodeDef) { node[Node.keys.created] = true // do side effect to avoid creating new objects - acc[nodeUuid] = node + acc[nodeIId] = node if (NodeDef.isFile(nodeDef)) { this.trackFileUuid({ node }) } } else { this.logDebug( - `Record ${recordUuid}: missing node def with uuid ${nodeDefUuid} in node ${nodeUuid}; skipping it` + `Record ${recordUuid}: missing node def with uuid ${nodeDefUuid} in node ${nodeIId}; skipping it` ) } return acc diff --git a/server/modules/record/api/recordApi.js b/server/modules/record/api/recordApi.js index a5dbec10e2..664eeb1c79 100644 --- a/server/modules/record/api/recordApi.js +++ b/server/modules/record/api/recordApi.js @@ -37,11 +37,11 @@ import { requireRecordsExportPermission, } from '../../auth/authApiMiddleware' -const fetchRecordNodeFileAsStream = async ({ surveyId, nodeUuid }) => { - const node = await RecordService.fetchNodeByUuid(surveyId, nodeUuid) +const fetchRecordNodeFileAsStream = async ({ surveyId, recordUuid, nodeIId }) => { + const node = await RecordService.fetchNodeByIId(surveyId, recordUuid, nodeIId) const fileUuid = Node.getFileUuid(node) const file = await FileService.fetchFileSummaryByUuid(surveyId, fileUuid) - const fileName = await RecordService.generateNodeFileNameForDownload({ surveyId, nodeUuid, file }) + const fileName = await RecordService.generateNodeFileNameForDownload({ surveyId, nodeIId, file }) const contentStream = await FileService.fetchFileContentAsStream({ surveyId, fileUuid }) return { fileName, file, contentStream } } @@ -280,13 +280,13 @@ export const init = (app) => { }) app.get( - '/survey/:surveyId/record/:recordUuid/nodes/:nodeUuid/file', + '/survey/:surveyId/record/:recordUuid/nodes/:nodeIId/file', requireRecordViewPermission, async (req, res, next) => { try { - const { surveyId, nodeUuid } = Request.getParams(req) + const { surveyId, recordUuid, nodeIId } = Request.getParams(req) - const { fileName, file, contentStream } = await fetchRecordNodeFileAsStream({ surveyId, nodeUuid }) + const { fileName, file, contentStream } = await fetchRecordNodeFileAsStream({ surveyId, recordUuid, nodeIId }) setContentTypeFile({ res, fileName, fileSize: RecordFile.getSize(file) }) contentStream.pipe(res) } catch (error) { @@ -296,13 +296,13 @@ export const init = (app) => { ) app.get( - '/survey/:surveyId/record/:recordUuid/nodes/:nodeUuid/file-exif', + '/survey/:surveyId/record/:recordUuid/nodes/:nodeIId/file-exif', requireRecordViewPermission, async (req, res, next) => { let tempFilePath try { - const { surveyId, nodeUuid } = Request.getParams(req) - const { contentStream } = await fetchRecordNodeFileAsStream({ surveyId, nodeUuid }) + const { surveyId, recordUuid, nodeIId } = Request.getParams(req) + const { contentStream } = await fetchRecordNodeFileAsStream({ surveyId, recordUuid, nodeIId }) ;({ tempFilePath } = await FileUtils.writeStreamToTempFile(contentStream)) const info = await exifr.parse(tempFilePath) res.json(info) @@ -498,12 +498,12 @@ export const init = (app) => { } }) - app.delete('/survey/:surveyId/record/:recordUuid/node/:nodeUuid', requireRecordEditPermission, (req, res) => { - const { surveyId, cycle, draft, recordUuid, nodeUuid, timezoneOffset } = Request.getParams(req) + app.delete('/survey/:surveyId/record/:recordUuid/node/:nodeIId', requireRecordEditPermission, (req, res) => { + const { surveyId, cycle, draft, recordUuid, nodeIId, timezoneOffset } = Request.getParams(req) const user = Request.getUser(req) const socketId = Request.getSocketId(req) - RecordService.deleteNode({ socketId, user, surveyId, cycle, draft, recordUuid, nodeUuid, timezoneOffset }) + RecordService.deleteNode({ socketId, user, surveyId, cycle, draft, recordUuid, nodeIId, timezoneOffset }) sendOk(res) }) } diff --git a/server/modules/record/manager/NodesDeleteBatchPersister.js b/server/modules/record/manager/NodesDeleteBatchPersister.js index 18ca453ae4..b01ecfa8b6 100644 --- a/server/modules/record/manager/NodesDeleteBatchPersister.js +++ b/server/modules/record/manager/NodesDeleteBatchPersister.js @@ -9,11 +9,12 @@ export class NodesDeleteBatchPersister extends BatchPersister { constructor({ user, surveyId, tx }) { super( async (nodes) => - RecordManager.deleteNodesByUuids( + RecordManager.deleteNodesByInternalIds( { user: this.user, surveyId: this.surveyId, - nodeUuids: nodes.map(Node.getUuid), + recordUuid: Node.getRecordUuid(nodes[0]), // all nodes belong to the same record + nodeInternalIds: nodes.map(Node.getIId), systemActivity: true, }, this.tx diff --git a/server/modules/record/manager/_recordManager/nodeCreationManager.js b/server/modules/record/manager/_recordManager/nodeCreationManager.js index 4811f808e9..138b117bf5 100644 --- a/server/modules/record/manager/_recordManager/nodeCreationManager.js +++ b/server/modules/record/manager/_recordManager/nodeCreationManager.js @@ -22,9 +22,9 @@ const _createUpdateResult = ({ record, node = null, nodes = {}, sideEffect = fal return { record: recordUpdated, nodes: { - [Node.getUuid(node)]: node, + [Node.getIId(node)]: node, // Always assoc parentNode, used in surveyRdbManager.updateTableNodes - ...(parentNode ? { [Node.getUuid(parentNode)]: parentNode } : {}), + ...(parentNode ? { [Node.getIId(parentNode)]: parentNode } : {}), ...nodes, }, } @@ -32,16 +32,15 @@ const _createUpdateResult = ({ record, node = null, nodes = {}, sideEffect = fal export const insertNodesInBulk = async ({ user, surveyId, nodesArray, systemActivity = false }, tx) => { const nodeValues = nodesArray.map((node) => [ - Node.getUuid(node), Node.getDateCreated(node), Node.getDateModified(node), Node.getRecordUuid(node), - Node.getParentUuid(node), + Node.getIId(node), + Node.getParentInternalId(node), Node.getNodeDefUuid(node), JSON.stringify(Node.getValue(node, null)), Node.getMeta(node), ]) - await NodeRepository.insertNodesFromValues(surveyId, nodeValues, tx) const activities = nodesArray.map((node) => @@ -86,30 +85,36 @@ export const insertNode = async ( } } - const nodesCreated = { - [Node.getUuid(node)]: node, + const nodeIId = Node.getIId(node) + const nodesCreatedByIId = { + [nodeIId]: node, } let recordUpdated = record + recordUpdated = Record.assocNode(node)(recordUpdated) + + recordUpdated.lastInternalId = recordUpdated.lastInternalId + ? Math.max(recordUpdated.lastInternalId, nodeIId) + : nodeIId if (NodeDef.isEntity(nodeDef)) { const descendantsCreateResult = await RecordNodesUpdater.createDescendants({ user, survey, - record, + record: recordUpdated, parentNode: node, nodeDef, timezoneOffset, createMultipleEntities, }) - Object.assign(nodesCreated, descendantsCreateResult.nodes) + Object.assign(nodesCreatedByIId, descendantsCreateResult.nodes) recordUpdated = descendantsCreateResult.record } const nodesInserted = persistNodes - ? ObjectUtils.toUuidIndexedObj( - await insertNodesInBatch({ user, surveyId, nodes: Object.values(nodesCreated), systemActivity: system }, t) + ? ObjectUtils.toIIdIndexedObj( + await insertNodesInBatch({ user, surveyId, nodes: Object.values(nodesCreatedByIId), systemActivity: system }, t) ) - : nodesCreated + : nodesCreatedByIId return _createUpdateResult({ record: recordUpdated, node, nodes: nodesInserted, sideEffect }) } diff --git a/server/modules/record/manager/_recordManager/nodeRDBManager.js b/server/modules/record/manager/_recordManager/nodeRDBManager.js index 205656bafc..98a0691258 100644 --- a/server/modules/record/manager/_recordManager/nodeRDBManager.js +++ b/server/modules/record/manager/_recordManager/nodeRDBManager.js @@ -5,7 +5,7 @@ import * as DataTableUpdateRepository from '@server/modules/surveyRdb/repository const { updateTablesFromUpdates } = DataTableUpdateRepository -const generateRdbUpates = ({ survey, record, nodesArray }) => { +const generateRdbUpdates = ({ survey, record, nodesArray }) => { // include ancestor nodes (used to find the correct rdb table to update) const nodesAndDependentsAndAncestors = nodesArray.reduce((nodesAcc, node) => { Record.visitAncestorsAndSelf({ node, visitor: (n) => (nodesAcc[n.uuid] = n) })(record) @@ -17,7 +17,7 @@ const generateRdbUpates = ({ survey, record, nodesArray }) => { nodes: nodesAndDependentsAndAncestors, }) // Merge updated nodes with existing ones (remove created/updated flags nodes) - const nodes = ObjectUtils.toUuidIndexedObj(nodesArray) + const nodes = ObjectUtils.toIIdIndexedObj(nodesArray) const recordUpdated = Record.mergeNodes(nodes, { removeFlags: true, sideEffect: true })(record) return { record: recordUpdated, @@ -26,7 +26,7 @@ const generateRdbUpates = ({ survey, record, nodesArray }) => { } const persistNodesToRDB = async ({ survey, record, nodesArray }, t) => { - const { rdbUpdates, record: recordUpdated } = generateRdbUpates({ survey, record, nodesArray }) + const { rdbUpdates, record: recordUpdated } = generateRdbUpdates({ survey, record, nodesArray }) await updateTablesFromUpdates({ rdbUpdates }, t) @@ -34,7 +34,7 @@ const persistNodesToRDB = async ({ survey, record, nodesArray }, t) => { } export const NodeRdbManager = { - generateRdbUpates, + generateRdbUpdates, persistNodesToRDB, updateTablesFromUpdates, } diff --git a/server/modules/record/manager/_recordManager/nodeUpdateManager.js b/server/modules/record/manager/_recordManager/nodeUpdateManager.js index af87a423b9..bef90b075e 100644 --- a/server/modules/record/manager/_recordManager/nodeUpdateManager.js +++ b/server/modules/record/manager/_recordManager/nodeUpdateManager.js @@ -33,9 +33,9 @@ const _createUpdateResult = (record, node = null, nodes = {}) => { return { record: recordUpdated, nodes: { - [Node.getUuid(node)]: node, + [Node.getIId(node)]: node, // Always assoc parentNode, used in surveyRdbManager.updateTableNodes - ...(parentNode ? { [Node.getUuid(parentNode)]: parentNode } : {}), + ...(parentNode ? { [Node.getIId(parentNode)]: parentNode } : {}), ...nodes, }, } @@ -44,6 +44,7 @@ const _createUpdateResult = (record, node = null, nodes = {}) => { const _onNodeUpdate = async (survey, record, node, nodeDependents, t) => { // TODO check if it should be removed const surveyId = Survey.getId(survey) + const recordUuid = Record.getUuid(record) let updatedNodes = nodeDependents || {} @@ -58,11 +59,12 @@ const _onNodeUpdate = async (survey, record, node, nodeDependents, t) => { const nodeDefDependent = Survey.getNodeDefByUuid(Node.getNodeDefUuid(nodeDependent))(survey) return NodeDef.isMultiple(nodeDefDependent) - ? NodeRepository.deleteNode(surveyId, Node.getUuid(nodeDependent), t) + ? NodeRepository.deleteNode({ surveyId, recordUuid, nodeIId: Node.getIId(nodeDependent) }, t) : NodeRepository.updateNode( { surveyId, - nodeUuid: Node.getUuid(nodeDependent), + recordUuid, + nodeIId: Node.getIId(nodeDependent), meta: Node.getMeta(nodeDependent), draft: Record.isPreview(record), }, @@ -72,7 +74,7 @@ const _onNodeUpdate = async (survey, record, node, nodeDependents, t) => { ) updatedNodes = { ...updatedNodes, - ...ObjectUtils.toUuidIndexedObj(nodesClearedArray), + ...ObjectUtils.toIIdIndexedObj(nodesClearedArray), } } } @@ -92,9 +94,8 @@ export const updateNode = async ({ user, survey, record, node, system = false, u meta[Node.metaKeys.defaultValue] = false } if (!Record.isPreview(record)) { - // Keep only node uuid, recordUuid, meta and value const logContent = R.pipe( - R.pick([Node.keys.uuid, Node.keys.recordUuid, Node.keys.nodeDefUuid, Node.keys.value]), + R.pick([Node.keys.iId, Node.keys.recordUuid, Node.keys.nodeDefUuid, Node.keys.value]), R.assoc(Node.keys.meta, meta) )(node) await ActivityLogRepository.insert(user, surveyId, ActivityLog.type.nodeValueUpdate, logContent, system, t) @@ -103,7 +104,7 @@ export const updateNode = async ({ user, survey, record, node, system = false, u const value = Node.getValue(node) if (NodeDef.isFile(nodeDef)) { // mark old file as deleted if changed - const nodePrev = await NodeRepository.fetchNodeByUuid(surveyId, Node.getUuid(node), t) + const nodePrev = await NodeRepository.fetchNodeByIId(surveyId, Node.getRecordUuid(node), Node.getIId(node), t) const fileUuidPrev = Node.getFileUuid(nodePrev) if (fileUuidPrev !== null && fileUuidPrev !== Node.getFileUuid(node)) { await FileRepository.markFileAsDeleted(surveyId, fileUuidPrev, t) @@ -113,7 +114,8 @@ export const updateNode = async ({ user, survey, record, node, system = false, u const nodeUpdated = await NodeRepository.updateNode( { surveyId, - nodeUuid: Node.getUuid(node), + recordUuid: Node.getRecordUuid(node), + nodeIId: Node.getIId(node), value, meta, draft: Record.isPreview(record), @@ -131,21 +133,23 @@ export const updateNode = async ({ user, survey, record, node, system = false, u } const _reloadNodes = async ({ surveyId, record, nodes }, tx) => { + const recordUuid = Record.getUuid(record) const nodesReloadedArray = ( - await NodeRepository.fetchNodesWithRefDataByUuids( - { surveyId, nodeUuids: Object.keys(nodes), draft: Record.isPreview(record) }, + await NodeRepository.fetchNodesWithRefDataByIIds( + { surveyId, recordUuid, nodeIIds: Object.keys(nodes), draft: Record.isPreview(record) }, tx ) ).map((nodeReloaded) => { // preserve status flags (used in rdb updates) - const oldNode = nodes[Node.getUuid(nodeReloaded)] + // side effect on nodes is possible: update node in place because nodes have been fetched from DB + const oldNode = nodes[Node.getIId(nodeReloaded)] return R.pipe( - Node.assocCreated(Node.isCreated(oldNode)), - Node.assocDeleted(Node.isDeleted(oldNode)), - Node.assocUpdated(Node.isUpdated(oldNode)) + Node.setCreated(Node.isCreated(oldNode)), + Node.setDeleted(Node.isDeleted(oldNode)), + Node.setUpdated(Node.isUpdated(oldNode)) )(nodeReloaded) }) - return ObjectUtils.toUuidIndexedObj(nodesReloadedArray) + return ObjectUtils.toIIdIndexedObj(nodesReloadedArray) } const _groupNodesByFlags = (nodesArray) => @@ -163,7 +167,7 @@ const _groupNodesByFlags = (nodesArray) => { nodesInserted: [], nodesUpdated: [], nodesDeleted: [] } ) -const _persistNodes = async ({ surveyId, nodesArray }, tx) => { +const _persistNodes = async ({ surveyId, recordUuid, nodesArray }, tx) => { const { nodesInserted, nodesUpdated, nodesDeleted } = _groupNodesByFlags(nodesArray) if (nodesInserted.length) { @@ -173,7 +177,10 @@ const _persistNodes = async ({ surveyId, nodesArray }, tx) => { await NodeRepository.updateNodes({ surveyId, nodes: nodesUpdated }, tx) } if (nodesDeleted.length) { - await NodeRepository.deleteNodesByUuids(surveyId, nodesDeleted.map(Node.getUuid), tx) + await NodeRepository.deleteNodesByInternalIds( + { surveyId, recordUuid, nodeInternalIds: nodesDeleted.map(Node.getIId) }, + tx + ) } } @@ -199,8 +206,9 @@ export const updateNodesDependents = async ( if (persistNodes && !R.isEmpty(allNodesUpdated)) { const nodesArray = Object.values(allNodesUpdated) const surveyId = Survey.getId(survey) + const recordUuid = Record.getUuid(record) - await _persistNodes({ surveyId, nodesArray }, tx) + await _persistNodes({ surveyId, recordUuid, nodesArray }, tx) // reload nodes to get nodes ref data const nodesReloaded = await _reloadNodes({ surveyId, record: recordUpdated, nodes: allNodesUpdated }, tx) @@ -218,7 +226,7 @@ export const updateNodesDependents = async ( // ==== DELETE const _getNodeDependentKeyAttributes = (survey, record, node) => { - const nodeDependentKeyAttributes = {} + const nodeDependentKeyAttributesByIId = {} const nodeDef = Survey.getNodeDefByUuid(Node.getNodeDefUuid(node))(survey) if (NodeDef.isMultipleEntity(nodeDef)) { // Find sibling entities with same key values @@ -238,25 +246,26 @@ const _getNodeDependentKeyAttributes = (survey, record, node) => { if (R.equals(nodeKeyValues, nodeDeletedKeyValues)) { nodeKeys.forEach((nodeKey) => { - nodeDependentKeyAttributes[Node.getUuid(nodeKey)] = nodeKey + nodeDependentKeyAttributesByIId[Node.getIId(nodeKey)] = nodeKey }) } }) } } - return nodeDependentKeyAttributes + return nodeDependentKeyAttributesByIId } -export const deleteNode = async (user, survey, record, nodeUuid, t) => { +export const deleteNode = async (user, survey, record, nodeIId, t) => { const surveyId = Survey.getId(survey) + const recordUuid = Record.getUuid(record) - const node = await NodeRepository.deleteNode(surveyId, nodeUuid, t) + const node = await NodeRepository.deleteNode({ surveyId, recordUuid, nodeIId }, t) if (!Record.isPreview(record)) { const logContent = { - [ActivityLog.keysContent.uuid]: nodeUuid, [ActivityLog.keysContent.recordUuid]: Node.getRecordUuid(node), + [ActivityLog.keysContent.nodeIId]: nodeIId, [ActivityLog.keysContent.nodeDefUuid]: Node.getNodeDefUuid(node), [Node.keys.meta]: { [Node.metaKeys.hierarchy]: Node.getHierarchy(node), @@ -275,11 +284,11 @@ export const deleteNode = async (user, survey, record, nodeUuid, t) => { // mark deleted dependent attributes nodeDependentUniqueAttributes = Object.values(nodeDependentUniqueAttributes).reduce((nodesAcc, nodeDependent) => { - const nodeDependentUuid = Node.getUuid(nodeDependent) - const deleted = !Record.getNodeByUuid(nodeDependentUuid)(recordUpdated) + const nodeDependentIId = Node.getIId(nodeDependent) + const deleted = !Record.getNodeByInternalId(nodeDependentIId)(recordUpdated) const nodeDependentUpdated = Node.isDeleted(nodeDependent) !== deleted ? Node.assocDeleted(deleted)(nodeDependent) : nodeDependent - return { ...nodesAcc, [nodeDependentUuid]: nodeDependentUpdated } + return { ...nodesAcc, [nodeDependentIId]: nodeDependentUpdated } }, {}) return _onNodeUpdate( @@ -295,18 +304,21 @@ export const deleteNodesByNodeDefUuids = async (user, surveyId, nodeDefUuids, re client.tx(async (t) => { const nodesDeleted = await NodeRepository.deleteNodesByNodeDefUuids(surveyId, nodeDefUuids, t) const activities = nodesDeleted.map((node) => - ActivityLog.newActivity(ActivityLog.type.nodeDelete, { uuid: Node.getUuid(node) }, true) + ActivityLog.newActivity(ActivityLog.type.nodeDelete, { iId: Node.getIId(node) }, true) ) await ActivityLogRepository.insertMany(user, surveyId, activities, t) - const nodesDeletedByUuid = ObjectUtils.toUuidIndexedObj(nodesDeleted) - const recordUpdated = Record.mergeNodes(nodesDeletedByUuid, { sideEffect: true })(record) + const nodesDeletedByIId = ObjectUtils.toIIdIndexedObj(nodesDeleted) + const recordUpdated = Record.mergeNodes(nodesDeletedByIId, { sideEffect: true })(record) return { record: recordUpdated, nodesDeleted } }) -export const deleteNodesByUuids = async ({ user, surveyId, nodeUuids, systemActivity = false }, tx) => { - const nodesDeleted = await NodeRepository.deleteNodesByUuids(surveyId, nodeUuids, tx) - const activities = nodeUuids.map((uuid) => - ActivityLog.newActivity(ActivityLog.type.nodeDelete, { uuid }, systemActivity) +export const deleteNodesByInternalIds = async ( + { user, surveyId, recordUuid, nodeInternalIds, systemActivity = false }, + tx +) => { + const nodesDeleted = await NodeRepository.deleteNodesByInternalIds({ surveyId, recordUuid, nodeInternalIds }, tx) + const activities = nodeInternalIds.map((iId) => + ActivityLog.newActivity(ActivityLog.type.nodeDelete, { iId }, systemActivity) ) await ActivityLogRepository.insertMany(user, surveyId, activities, tx) return nodesDeleted diff --git a/server/modules/record/manager/_recordManager/recordUniquenessValidator.js b/server/modules/record/manager/_recordManager/recordUniquenessValidator.js index 5dbeb01c95..ed5660a370 100644 --- a/server/modules/record/manager/_recordManager/recordUniquenessValidator.js +++ b/server/modules/record/manager/_recordManager/recordUniquenessValidator.js @@ -12,7 +12,7 @@ import * as DataViewRepository from '@server/modules/surveyRdb/repository/dataVi const createNodesRecordUniqueValidation = ({ nodes, unique, errorKey }) => { const validationAcc = {} for (const node of nodes) { - validationAcc[Node.getUuid(node)] = RecordValidation.newValidationRecordDuplicate({ unique, errorKey }) + validationAcc[Node.getIId(node)] = RecordValidation.newValidationRecordDuplicate({ unique, errorKey }) } return validationAcc } diff --git a/server/modules/record/manager/_recordManager/recordUpdateManager.js b/server/modules/record/manager/_recordManager/recordUpdateManager.js index e6fedce362..acc09c941b 100644 --- a/server/modules/record/manager/_recordManager/recordUpdateManager.js +++ b/server/modules/record/manager/_recordManager/recordUpdateManager.js @@ -48,7 +48,7 @@ export const initNewRecord = async ( ) => { const rootNodeDef = Survey.getNodeDefRoot(survey) - const rootNode = Node.newNode(NodeDef.getUuid(rootNodeDef), Record.getUuid(record)) + const rootNode = Node.newNode({ record, nodeDefUuid: NodeDef.getUuid(rootNodeDef) }) return persistNode( { @@ -189,9 +189,9 @@ export const persistNode = async ( node, timezoneOffset, nodesUpdateFn: async (user, survey, record, node, t) => { - const nodeUuid = Node.getUuid(node) + const nodeIId = Node.getIId(node) - const existingNode = Record.getNodeByUuid(nodeUuid)(record) + const existingNode = Record.getNodeByInternalId(nodeIId)(record) if (existingNode) { return NodeUpdateManager.updateNode({ user, survey, record, node, system }, t) @@ -213,7 +213,7 @@ export const deleteNode = async ( user, survey, record, - nodeUuid, + nodeIId, timezoneOffset, nodesUpdateListener = null, nodesValidationListener = null, @@ -224,17 +224,17 @@ export const deleteNode = async ( user, survey, record, - node: Record.getNodeByUuid(nodeUuid)(record), + node: Record.getNodeByInternalId(nodeIId)(record), timezoneOffset, nodesUpdateFn: (user, survey, record, node, t) => - NodeUpdateManager.deleteNode(user, survey, record, Node.getUuid(node), t), + NodeUpdateManager.deleteNode(user, survey, record, Node.getIId(node), t), nodesUpdateListener, nodesValidationListener, }, t ) -export const { deleteNodesByUuids } = NodeUpdateManager +export const { deleteNodesByInternalIds } = NodeUpdateManager export const deleteNodesByNodeDefUuids = async ({ user, surveyId, nodeDefUuids, record }, client = db) => { const { record: recordUpdated } = await NodeUpdateManager.deleteNodesByNodeDefUuids( @@ -344,7 +344,7 @@ const _getDependentNodesToValidate = ({ survey, record, nodes }) => { record, nodePointers: dependentNodePointersToValidate, }) - return { ...nodes, ...ObjectUtils.toUuidIndexedObj(dependentNodesToValidate) } + return { ...nodes, ...ObjectUtils.toIIdIndexedObj(dependentNodesToValidate) } } const _onNodesUpdate = async ( @@ -428,7 +428,7 @@ const _afterNodesUpdate = async ({ survey, record, nodes, nodesValidationListene const validateNodesAndPersistToRDB = async ({ user, survey, record, nodes, nodesValidationListener = null }, t) => { const nodesArray = Object.values(nodes) const nodesToValidate = nodesArray.reduce( - (nodesAcc, node) => (Node.isDeleted(node) ? nodesAcc : { ...nodesAcc, [Node.getUuid(node)]: node }), + (nodesAcc, node) => (Node.isDeleted(node) ? nodesAcc : { ...nodesAcc, [Node.getIId(node)]: node }), {} ) const { nodesValidation: validations } = await RecordValidationManager.validateNodesAndPersistValidation( diff --git a/server/modules/record/manager/_recordManager/recordValidationManager.js b/server/modules/record/manager/_recordManager/recordValidationManager.js index b8d80235ec..dfef3315ee 100644 --- a/server/modules/record/manager/_recordManager/recordValidationManager.js +++ b/server/modules/record/manager/_recordManager/recordValidationManager.js @@ -66,42 +66,42 @@ export const validateSortedNodesAndPersistValidation = async ( record, nodesArray, }) - const nodesValueValidationsByUuid = Validation.getFieldValidations(nodesValueValidation) + const nodesValueValidationsByIId = Validation.getFieldValidations(nodesValueValidation) // 1.a. workaround: always define value field validation even when validation is valid to allow cleaning up errors later - for (const [nodeUuid, nodeValueValidation] of Object.entries(nodesValueValidationsByUuid)) { + for (const [nodeIId, nodeValueValidation] of Object.entries(nodesValueValidationsByIId)) { if (Validation.isValid(nodeValueValidation)) { const nodeValueValidationUpdated = Validation.setField('value', Validation.newInstance())(nodeValueValidation) - nodesValueValidationsByUuid[nodeUuid] = nodeValueValidationUpdated + nodesValueValidationsByIId[nodeIId] = nodeValueValidationUpdated } } // 2. validate record unique nodes - const uniqueNodesValidationByNodeUuid = + const uniqueNodesValidationByNodeIId = validateRecordUniqueness && !Record.isPreview(record) && isRootUniqueNodesUpdated({ survey, nodesArray }) ? await RecordUniquenessValidator.validateRecordUniqueNodes({ survey, record }, tx) : {} // 3. get previous validation of unique nodes - const uniqueNodesUuids = Object.keys(uniqueNodesValidationByNodeUuid) - const oldUniqueNodesValidationByNodeUuid = Validation.getFieldValidationsByFields(uniqueNodesUuids)( + const uniqueNodesIIds = Object.keys(uniqueNodesValidationByNodeIId) + const oldUniqueNodesValidationByNodeIIds = Validation.getFieldValidationsByFields(uniqueNodesIIds)( Record.getValidation(record) ) // 4. merge unique nodes previous validation with new one const uniqueNodesValidationMergedByUuid = Validation.mergeFieldValidations( - uniqueNodesValidationByNodeUuid, - oldUniqueNodesValidationByNodeUuid + uniqueNodesValidationByNodeIId, + oldUniqueNodesValidationByNodeIIds ) // 5. merge unique nodes validation with nodes values validation const uniqueNodesValidationWithValueValidationByUuid = Validation.mergeFieldValidations( - nodesValueValidationsByUuid, + nodesValueValidationsByIId, uniqueNodesValidationMergedByUuid ) // 6. generate full validation object const fullNodesValidationByUuid = { - ...nodesValueValidationsByUuid, + ...nodesValueValidationsByIId, ...uniqueNodesValidationWithValueValidationByUuid, } const nodesValidation = Validation.recalculateValidity(Validation.newInstance(true, fullNodesValidationByUuid)) diff --git a/server/modules/record/manager/recordManager.js b/server/modules/record/manager/recordManager.js index 53eaa29408..0644e94333 100644 --- a/server/modules/record/manager/recordManager.js +++ b/server/modules/record/manager/recordManager.js @@ -36,7 +36,7 @@ export { insertRecord, createRecordFromSamplingPointDataItem } from './_recordMa export { insertNodesInBatch, insertNodesInBulk } from './_recordManager/nodeCreationManager' export const { insertNode } = RecordUpdateManager -export const { generateRdbUpates, persistNodesToRDB } = NodeRdbManager +export const { generateRdbUpdates, persistNodesToRDB } = NodeRdbManager // ==== READ @@ -210,11 +210,11 @@ export const fetchRecordAndNodesByUuid = async ( }, client ) - const indexedNodes = ObjectUtils.toUuidIndexedObj(nodes) + const indexedNodes = ObjectUtils.toIIdIndexedObj(nodes) return Record.assocNodes({ nodes: indexedNodes, updateNodesIndex: fetchForUpdate, sideEffect: true })(record) } -export { fetchNodeByUuid, fetchChildNodesByNodeDefUuids } from '../repository/nodeRepository' +export { fetchNodeByIId, fetchChildNodesByNodeDefUuids } from '../repository/nodeRepository' const fetchNodeRefData = async ({ survey, node, isCode }, client) => { const surveyId = Survey.getId(survey) @@ -302,9 +302,9 @@ export const updateNodes = async ({ user, surveyId, nodes }, client = db) => client.tx(async (t) => { const activities = nodes.map((node) => { const logContent = R.pick([ - Node.keys.uuid, Node.keys.recordUuid, - Node.keys.parentUuid, + Node.keys.iId, + Node.keys.pIId, Node.keys.nodeDefUuid, Node.keys.meta, Node.keys.value, @@ -333,7 +333,7 @@ export { deleteRecordsByCycles, deleteNode, deleteNodesByNodeDefUuids, - deleteNodesByUuids, + deleteNodesByInternalIds, } from './_recordManager/recordUpdateManager' // ==== VALIDATION diff --git a/server/modules/record/repository/node/index.js b/server/modules/record/repository/node/index.js deleted file mode 100644 index fb16cb84d9..0000000000 --- a/server/modules/record/repository/node/index.js +++ /dev/null @@ -1 +0,0 @@ -export { fetchNodes } from './read' diff --git a/server/modules/record/repository/node/read.js b/server/modules/record/repository/node/read.js deleted file mode 100644 index 10adbb2c09..0000000000 --- a/server/modules/record/repository/node/read.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as pgPromise from 'pg-promise' -import { TableNode } from '../../../../../common/model/db' - -import { db } from '../../../../db/db' - -/** - * Fetches nodes by the given survey id and the optional parameters. - * - * @param {!object} params - The query parameters. - * @param {!object} params.survey - The survey object. - * @param {string} [params.uuid=null] - The node uuid to filter by. - * @param {string} [params.recordUuid=null] - The record uuid to filter by. - * @param {string} [params.parentUuid=null] - The parent node uuid to filter by. - * @param {string} [params.nodeDefUuid=null] - The node definition uuid to filter by. - * @param {boolean} [params.draft=false] - Whether to fetch draft props or only published ones. - * @param {pgPromise.IDatabase} [client=db] - The database client. - * - * @returns {Promise} - The result promise. - */ -export const fetchNodes = async (params, client = db) => { - const { survey, uuid, recordUuid, parentUuid, nodeDefUuid, draft } = params - const table = new TableNode(survey) - return client.map( - table.getSelect({ uuid, recordUuid, parentUuid, nodeDefUuid, draft }), - [], - TableNode.dbTransformCallback - ) -} diff --git a/server/modules/record/repository/nodeKeysRepository.js b/server/modules/record/repository/nodeKeysRepository.js index bb9203626f..4e9fdca984 100644 --- a/server/modules/record/repository/nodeKeysRepository.js +++ b/server/modules/record/repository/nodeKeysRepository.js @@ -4,15 +4,15 @@ import * as SchemaRdb from '@common/surveyRdb/schemaRdb' import { db } from '@server/db/db' -export const fetchNodesHierarchyKeys = async ({ surveyId, nodeUuids }, client = db) => +export const fetchNodesHierarchyKeys = async ({ surveyId, nodeIIds }, client = db) => client.map( ` SELECT * FROM ${SchemaRdb.getName(surveyId)}._node_keys_hierarchy h WHERE - h.node_uuid IN ($1:csv) + h.node_i_id IN ($1:csv) `, - [nodeUuids], + [nodeIIds], A.camelizePartial({ limitToLevel: 1 }) ) diff --git a/server/modules/record/repository/nodeRepository.js b/server/modules/record/repository/nodeRepository.js index 9eafaf05d6..4e5bc8855d 100644 --- a/server/modules/record/repository/nodeRepository.js +++ b/server/modules/record/repository/nodeRepository.js @@ -21,11 +21,11 @@ const { keys: categoryItemKeys } = CategoryItem const { keys: taxonKeys } = Taxon export const tableColumnsInsert = [ - 'uuid', 'date_created', 'date_modified', 'record_uuid', - 'parent_uuid', + 'i_id', + 'p_i_id', 'node_def_uuid', 'value', 'meta', @@ -55,11 +55,11 @@ const dbTransformCallback = (node) => { const _toValueQueryParam = (value) => (value === null || A.isEmpty(value) ? null : JSON.stringify(value)) -const _getAncestorUuidSelectField = (ancestorDef) => { +const _getAncestorIIdSelectField = (ancestorDef) => { const nodeAncestorEntityHierarchyIndex = ancestorDef ? NodeDef.getMetaHierarchy(ancestorDef).length : null return nodeAncestorEntityHierarchyIndex === null ? 'null' - : `(n.meta -> '${Node.metaKeys.hierarchy}' ->> ${nodeAncestorEntityHierarchyIndex})::uuid` + : `(n.meta -> '${Node.metaKeys.hierarchy}' -> ${nodeAncestorEntityHierarchyIndex})::integer` } /** @@ -100,7 +100,7 @@ export const getNodeSelectQuery = ({ 'r.cycle AS record_cycle', 'r.step AS record_step', 'r.owner_uuid AS record_owner_uuid', - `${_getAncestorUuidSelectField(ancestorDef)} AS ancestor_uuid` + `${_getAncestorIIdSelectField(ancestorDef)} AS ancestor_i_id` ) fromParts.push(`JOIN ${schema}.record r ON r.uuid = n.record_uuid @@ -176,16 +176,19 @@ export const insertNode = async (surveyId, node, draft, client = db) => { [Node.metaKeys.childApplicability]: {}, } + const nodeIId = Node.getIId(node) + const recordUuid = Node.getRecordUuid(node) + await client.query( ` INSERT INTO ${getSurveyDBSchema(surveyId)}.node - (uuid, record_uuid, parent_uuid, node_def_uuid, value, meta) + (i_id, record_uuid, p_i_id, node_def_uuid, value, meta) VALUES ($1, $2, $3, $4, $5::jsonb, $6::jsonb) `, [ - Node.getUuid(node), - Node.getRecordUuid(node), - Node.getParentUuid(node), + nodeIId, + recordUuid, + Node.getParentInternalId(node), Node.getNodeDefUuid(node), _toValueQueryParam(Node.getValue(node, null)), meta, @@ -193,7 +196,7 @@ export const insertNode = async (surveyId, node, draft, client = db) => { ) // reload node to get node ref data - const nodeAdded = await fetchNodeWithRefDataByUuid({ surveyId, nodeUuid: Node.getUuid(node), draft }, client) + const nodeAdded = await fetchNodeWithRefDataByIId({ surveyId, recordUuid, nodeIId, draft }, client) return { ...nodeAdded, [Node.keys.created]: true } } @@ -213,7 +216,8 @@ export const insertNodesInBatch = async ({ surveyId, nodes = [] }, client = db) date_created: Dates.formatForStorage(Node.getDateCreated(node)), date_modified: Dates.formatForStorage(Node.getDateModified(node)), record_uuid: Node.getRecordUuid(node), - parent_uuid: Node.getParentUuid(node), + i_id: Node.getIId(node), + p_i_id: Node.getParentInternalId(node), node_def_uuid: Node.getNodeDefUuid(node), value: _toValueQueryParam(Node.getValue(node)), meta: Node.getMeta(node), @@ -238,58 +242,58 @@ export const fetchNodesByRecordUuid = async ( dbTransformCallback ) -export const fetchNodeByUuid = async (surveyId, uuid, client = db) => +export const fetchNodeByIId = async (surveyId, recordUuid, iId, client = db) => client.one( ` SELECT * FROM ${getSurveyDBSchema(surveyId)}.node - WHERE uuid = $1`, - [uuid], + WHERE record_uuid = $/recordUuid/ AND i_id = $/iId/`, + { recordUuid, iId }, dbTransformCallback ) -export const fetchNodesWithRefDataByUuids = async ({ surveyId, nodeUuids, draft }, client = db) => +export const fetchNodesWithRefDataByIIds = async ({ surveyId, recordUuid, nodeIIds, draft }, client = db) => client.map( ` ${getNodeSelectQuery({ surveyId, draft })} - WHERE n.uuid IN ($/nodeUuids:list/) + WHERE n.record_uuid = $/recordUuid/ AND n.i_id IN ($/nodeIIds:list/) `, - { surveyId, nodeUuids }, + { surveyId, recordUuid, nodeIIds }, dbTransformCallback ) -export const fetchNodeWithRefDataByUuid = async ({ surveyId, nodeUuid, draft }, client = db) => - (await fetchNodesWithRefDataByUuids({ surveyId, nodeUuids: [nodeUuid], draft }, client))[0] +export const fetchNodeWithRefDataByIId = async ({ surveyId, recordUuid, nodeIId, draft }, client = db) => + (await fetchNodesWithRefDataByIIds({ surveyId, recordUuid, nodeIIds: [nodeIId], draft }, client))[0] -export const fetchChildNodesByNodeDefUuids = async (surveyId, recordUuid, nodeUuid, childDefUuids, client = db) => +export const fetchChildNodesByNodeDefUuids = async (surveyId, recordUuid, nodeIId, childDefUuids, client = db) => client.map( ` ${getNodeSelectQuery({ surveyId, draft: false })} WHERE n.record_uuid = $/recordUuid/ - AND n.parent_uuid ${nodeUuid ? '= $/nodeUuid/' : 'is null'} + AND n.p_i_id ${nodeIId ? '= $/nodeIId/' : 'is null'} AND n.node_def_uuid IN ($/childDefUuids:csv/)`, - { surveyId, recordUuid, nodeUuid, childDefUuids }, + { surveyId, recordUuid, nodeIId, childDefUuids }, dbTransformCallback ) // ============== UPDATE export const updateNode = async ( - { surveyId, nodeUuid, value = null, meta = {}, draft, reloadNode = true }, + { surveyId, recordUuid, nodeIId, value = null, meta = {}, draft, reloadNode = true }, client = db ) => { await client.query( ` UPDATE ${getSurveyDBSchema(surveyId)}.node - SET value = $1::jsonb, - meta = meta || $2::jsonb, + SET value = $/value/::jsonb, + meta = meta || $/meta/::jsonb, date_modified = ${DbUtils.now} - WHERE uuid = $3 + WHERE record_uuid = $/recordUuid/ AND i_id = $/nodeIId/ `, - [_toValueQueryParam(value), meta || {}, nodeUuid] + { value: _toValueQueryParam(value), meta: meta || {}, recordUuid, nodeIId } ) if (!reloadNode) return null // fetch node with ref data - const node = await fetchNodeWithRefDataByUuid({ surveyId, nodeUuid, draft }, client) + const node = await fetchNodeWithRefDataByIId({ surveyId, recordUuid, nodeIId, draft }, client) node[Node.keys.updated] = true return node } @@ -317,14 +321,14 @@ export const updateNodes = async ({ surveyId, nodes }, client = db) => { } // ============== DELETE -export const deleteNode = async (surveyId, nodeUuid, client = db) => +export const deleteNode = async ({ surveyId, recordUuid, nodeIId }, client = db) => client.one( ` DELETE FROM ${getSurveyDBSchema(surveyId)}.node - WHERE uuid = $1 + WHERE record_uuid = $/recordUuid/ AND i_id = $/nodeIId/ RETURNING *, true as ${Node.keys.deleted} `, - [nodeUuid], + { recordUuid, nodeIId }, dbTransformCallback ) @@ -339,11 +343,11 @@ export const deleteNodesByNodeDefUuids = async (surveyId, nodeDefUuids, client = dbTransformCallback ) -export const deleteNodesByUuids = async (surveyId, nodeUuids, client = db) => +export const deleteNodesByInternalIds = async ({ surveyId, recordUuid, nodeInternalIds }, client = db) => client.manyOrNone( `DELETE FROM ${getSurveyDBSchema(surveyId)}.node - WHERE uuid IN ($1:csv) + WHERE record_uuid = $/recordUuid/ AND i_id IN ($/nodeInternalIds:csv/) RETURNING *, true as ${Node.keys.deleted}`, - [nodeUuids], + { recordUuid, nodeInternalIds }, dbTransformCallback ) diff --git a/server/modules/record/service/recordService.js b/server/modules/record/service/recordService.js index c60a3e9903..70e9032d2b 100644 --- a/server/modules/record/service/recordService.js +++ b/server/modules/record/service/recordService.js @@ -276,7 +276,7 @@ const _sendNodeUpdateMessage = ({ socketId, user, recordUuid, msg }) => { thread.postMessage(msg, user) } -export const { fetchNodeByUuid } = RecordManager +export const { fetchNodeByIId } = RecordManager export const persistNode = async ({ socketId, @@ -302,7 +302,7 @@ export const persistNode = async ({ size: file.size, content: fs.readFileSync(file.tempFilePath), recordUuid, - nodeUuid: Node.getUuid(node), + nodeIId: Node.getIId(node), }) await FileService.insertFile(surveyId, fileObj) } @@ -315,7 +315,7 @@ export const persistNode = async ({ }) } -export const deleteNode = ({ socketId, user, surveyId, cycle, draft, recordUuid, nodeUuid, timezoneOffset }) => +export const deleteNode = ({ socketId, user, surveyId, cycle, draft, recordUuid, nodeIId, timezoneOffset }) => _sendNodeUpdateMessage({ socketId, user, @@ -326,15 +326,15 @@ export const deleteNode = ({ socketId, user, surveyId, cycle, draft, recordUuid, cycle, draft, recordUuid, - nodeUuid, + nodeIId, user, timezoneOffset, }, }) // generates the record file name in this format: file_SURVEYNAME_KEYVALUES_ATTRIBUTENAME_POSITION.EXTENSION -export const generateNodeFileNameForDownload = async ({ surveyId, nodeUuid, file }) => { - const node = await fetchNodeByUuid(surveyId, nodeUuid) +export const generateNodeFileNameForDownload = async ({ surveyId, nodeIId, file }) => { + const node = await fetchNodeByIId(surveyId, nodeIId) const record = await fetchRecordAndNodesByUuid({ surveyId, recordUuid: Node.getRecordUuid(node), diff --git a/server/modules/record/service/recordsCloneJob.js b/server/modules/record/service/recordsCloneJob.js index 053958ca40..9c0cb92334 100644 --- a/server/modules/record/service/recordsCloneJob.js +++ b/server/modules/record/service/recordsCloneJob.js @@ -85,11 +85,7 @@ export default class RecordsCloneJob extends Job { const record = await RecordManager.fetchRecordAndNodesByUuid({ surveyId, recordUuid }) // assign new UUIDs with side effect on record and nodes, faster when record is big - const { - record: recordCloned, - newNodeUuidsByOldUuid, - newFileUuidsByOldUuid, - } = RecordCloner.cloneRecord({ survey, record, cycleTo }) + const { record: recordCloned, newFileUuidsByOldUuid } = RecordCloner.cloneRecord({ survey, record, cycleTo }) const newRecordUuid = Record.getUuid(recordCloned) const nodes = Record.getNodes(recordCloned) @@ -110,12 +106,12 @@ export default class RecordsCloneJob extends Job { // update RDB await DataTableUpdateRepository.updateTables({ survey, record: recordCloned, nodes }, tx) - await this.cloneFiles({ newFileUuidsByOldUuid, newNodeUuidsByOldUuid, newRecordUuid }) + await this.cloneFiles({ newFileUuidsByOldUuid, newRecordUuid }) this.incrementProcessedItems() } - async cloneFiles({ newFileUuidsByOldUuid, newNodeUuidsByOldUuid, newRecordUuid }) { + async cloneFiles({ newFileUuidsByOldUuid, newRecordUuid }) { const { context, tx } = this const { surveyId } = context @@ -123,12 +119,10 @@ export default class RecordsCloneJob extends Job { const fileSummary = await FileService.fetchFileSummaryByUuid(surveyId, fileUuid, tx) if (fileSummary) { const content = await FileService.fetchFileContentAsBuffer({ surveyId, fileUuid }, tx) - const oldNodeUuid = RecordFile.getNodeUuid(fileSummary) - const newNodeUuid = oldNodeUuid ? newNodeUuidsByOldUuid[oldNodeUuid] : null const newFile = RecordFile.createFile({ content, name: RecordFile.getName(fileSummary), - nodeUuid: newNodeUuid, + nodeIId: RecordFile.getNodeIId(fileSummary), recordUuid: newRecordUuid, size: RecordFile.getSize(fileSummary), uuid: newFileUuid, diff --git a/server/modules/record/service/update/thread/recordsUpdateThread.js b/server/modules/record/service/update/thread/recordsUpdateThread.js index 534878fff5..70e7e13ac2 100644 --- a/server/modules/record/service/update/thread/recordsUpdateThread.js +++ b/server/modules/record/service/update/thread/recordsUpdateThread.js @@ -18,6 +18,12 @@ import { RecordsUpdateThreadMessageTypes } from './recordsThreadMessageTypes' const Logger = Log.getLogger('RecordsUpdateThread') +const prepareNodeForStorage = ({ record, node }) => { + const lastIId = Record.getLastNodeInternalId(record) + const nodeIId = Node.getIId(node) ?? lastIId + 1 + return Node.assocIId(nodeIId)(node) +} + class RecordsUpdateThread extends Thread { constructor(paramsObj) { super(paramsObj) @@ -199,11 +205,13 @@ class RecordsUpdateThread extends Thread { const recordUuid = Node.getRecordUuid(node) let record = await this.getOrFetchRecord({ msg, recordUuid }) + const nodePreparedForStorage = prepareNodeForStorage({ record, node }) + record = await RecordManager.persistNode({ user, survey, record, - node, + node: nodePreparedForStorage, timezoneOffset, nodesUpdateListener: (updatedNodes) => this.handleNodesUpdated({ record, updatedNodes }), nodesValidationListener: (validations) => this.handleNodesValidationUpdated({ record, validations }), @@ -212,7 +220,7 @@ class RecordsUpdateThread extends Thread { } async processRecordNodeDeleteMsg(msg) { - const { nodeUuid, recordUuid, user, timezoneOffset } = msg + const { nodeIId, recordUuid, user, timezoneOffset } = msg const { survey, recordsCache } = await this.getOrFetchSurveyData(msg) @@ -221,7 +229,7 @@ class RecordsUpdateThread extends Thread { user, survey, record, - nodeUuid, + nodeIId, timezoneOffset, (updatedNodes) => this.handleNodesUpdated({ record, updatedNodes }), (validations) => this.handleNodesValidationUpdated({ record, validations }) @@ -243,7 +251,7 @@ class RecordsUpdateThread extends Thread { async processSurveyClearMsg(msg) { const { surveyId, cycle } = msg - let keysToDelete = [] + const keysToDelete = [] if (!Objects.isNil(cycle)) { const key = this.getSurveyDataKey(msg) diff --git a/server/modules/survey/service/recordCheckJob.js b/server/modules/survey/service/recordCheckJob.js index 8b3c606b74..6f03a15cb5 100644 --- a/server/modules/survey/service/recordCheckJob.js +++ b/server/modules/survey/service/recordCheckJob.js @@ -193,9 +193,9 @@ export default class RecordCheckJob extends Job { record = _clearRecordKeysValidation(record) // 6. validate nodes - const newNodes = nodeDefAddedUuids.reduce((nodesByUuid, nodeDefUuid) => { + const newNodes = nodeDefAddedUuids.reduce((nodesByIId, nodeDefUuid) => { const nodes = Record.getNodesByDefUuid(nodeDefUuid)(record) - return Object.assign(nodesByUuid, ObjectUtils.toUuidIndexedObj(nodes)) + return Object.assign(nodesByIId, ObjectUtils.toIIdIndexedObj(nodes)) }, {}) Object.assign(allUpdatedNodesByUuid, newNodes) @@ -271,7 +271,7 @@ const _insertMissingSingleNode = async ({ survey, childDef, record, parentNode, return {} } // insert missing single node - const childNode = Node.newNode(NodeDef.getUuid(childDef), Record.getUuid(record), parentNode) + const childNode = Node.newNode({ record, nodeDefUuid: NodeDef.getUuid(childDef), parentNode }) return RecordManager.insertNode( { user, survey, record, node: childNode, system: true, persistNodes: false, sideEffect }, tx @@ -285,7 +285,7 @@ const _applyDefaultValuesAndApplicability = async (survey, nodeDefUpdatedUuids, for (const nodeDefUpdatedUuid of nodeDefUpdatedUuids) { const nodesToUpdatePartial = Record.getNodesByDefUuid(nodeDefUpdatedUuid)(record) for (const nodeUpdated of nodesToUpdatePartial) { - nodesToUpdate[Node.getUuid(nodeUpdated)] = nodeUpdated + nodesToUpdate[Node.getIId(nodeUpdated)] = nodeUpdated } } @@ -317,7 +317,7 @@ const _validateNodes = async ({ user, survey, nodeDefUuids, record, nodes }, tx) const def = Survey.getNodeDefByUuid(nodeDefUuid)(survey) const parentNodes = Record.getNodesByDefUuid(NodeDef.getParentUuid(def))(record) for (const parentNode of parentNodes) { - nodesToValidate[Node.getUuid(parentNode)] = parentNode + nodesToValidate[Node.getIId(parentNode)] = parentNode } } // Record keys uniqueness must be validated after RDB generation diff --git a/server/modules/surveyRdb/manager/surveyRdbCsvExport.js b/server/modules/surveyRdb/manager/surveyRdbCsvExport.js index e997f2f123..5b6ae86723 100644 --- a/server/modules/surveyRdb/manager/surveyRdbCsvExport.js +++ b/server/modules/surveyRdb/manager/surveyRdbCsvExport.js @@ -71,7 +71,7 @@ const getCsvExportFields = ({ const fields = [] if (includeInternalUuids) { - ArrayUtils.addIfNotEmpty(viewDataNodeDef.columnParentUuidName)(fields) + ArrayUtils.addIfNotEmpty(viewDataNodeDef.columnParentInternalId)(fields) fields.push(viewDataNodeDef.columnUuidName) } if (addCycle) { diff --git a/server/modules/surveyRdb/repository/RdbUpdates.js b/server/modules/surveyRdb/repository/RdbUpdates.js index c5c81e891a..8e8a26b2c4 100644 --- a/server/modules/surveyRdb/repository/RdbUpdates.js +++ b/server/modules/surveyRdb/repository/RdbUpdates.js @@ -39,21 +39,18 @@ export class RdbUpdates { } getAll() { - return ( - Object.entries(this.updatesBySchemaTableAndType) - .sort(([keyA, tableUpdatesA], [keyB, tableUpdatesB]) => { - // execute updates in order, according to the type (1. delete, 2. insert, 3. update) and hierarchy level - const { type: typeA } = RdbUpdates.expandKey(keyA) - const { nodeDefHierarchyLevel: nodeDefHierarchyLevelA } = tableUpdatesA - const { type: typeB } = RdbUpdates.expandKey(keyB) - const { nodeDefHierarchyLevel: nodeDefHierarchyLevelB } = tableUpdatesB - return ( - executionOrderByType[typeA] - executionOrderByType[typeB] || nodeDefHierarchyLevelA - nodeDefHierarchyLevelB - ) - }) - // eslint-disable-next-line no-unused-vars - .flatMap(([_key, updates]) => updates.getAll()) - ) + return Object.entries(this.updatesBySchemaTableAndType) + .sort(([keyA, tableUpdatesA], [keyB, tableUpdatesB]) => { + // execute updates in order, according to the type (1. delete, 2. insert, 3. update) and hierarchy level + const { type: typeA } = RdbUpdates.expandKey(keyA) + const { nodeDefHierarchyLevel: nodeDefHierarchyLevelA } = tableUpdatesA + const { type: typeB } = RdbUpdates.expandKey(keyB) + const { nodeDefHierarchyLevel: nodeDefHierarchyLevelB } = tableUpdatesB + return ( + executionOrderByType[typeA] - executionOrderByType[typeB] || nodeDefHierarchyLevelA - nodeDefHierarchyLevelB + ) + }) + .flatMap(([_key, updates]) => updates.getAll()) } merge(updates) { diff --git a/server/modules/surveyRdb/repository/dataTable/create.js b/server/modules/surveyRdb/repository/dataTable/create.js index c352e09c3c..a17ab315e2 100644 --- a/server/modules/surveyRdb/repository/dataTable/create.js +++ b/server/modules/surveyRdb/repository/dataTable/create.js @@ -5,12 +5,10 @@ import { TableDataNodeDef } from '../../../../../common/model/db' /** * Create a nodeDef data table. - * * @param {object} params - The query parameters. * @param {Survey} params.survey - The survey. * @param {NodeDef} params.nodeDef - The nodeDef to create the data table for. * @param {pgPromise.IDatabase} client - The data base client. - * * @returns {Promise} - The result promise. */ export const createDataTable = async ({ survey, nodeDef }, client) => { @@ -27,7 +25,7 @@ export const createDataTable = async ({ survey, nodeDef }, client) => { ${tableDataNodeDef.nameQualified} ( ${tableDataNodeDef.getColumnsWithType().join(', ')}, - ${tableDataNodeDef.getConstraintUuidUnique()}, + ${tableDataNodeDef.getConstraintIIdUnique()}, ${constraintFkParent ? `${constraintFkParent}, ` : ''} ${constraintFkRecord ? `${constraintFkRecord}, ` : ''} PRIMARY KEY (${TableDataNodeDef.columnSet.id}) diff --git a/server/modules/surveyRdb/repository/dataTableInsertRepository.js b/server/modules/surveyRdb/repository/dataTableInsertRepository.js index c55dda4d53..5bc2a4540e 100644 --- a/server/modules/surveyRdb/repository/dataTableInsertRepository.js +++ b/server/modules/surveyRdb/repository/dataTableInsertRepository.js @@ -10,7 +10,13 @@ import * as NodeRepository from '@server/modules/record/repository/nodeRepositor const getSelectQuery = ({ surveyId, nodeDef, nodeDefContext, nodeDefAncestorMultipleEntity, nodeDefColumnsUuids }) => { const getNodeSelectQuery = (ancestorDef) => - NodeRepository.getNodeSelectQuery({ surveyId, includeRefData: true, includeRecordInfo: true, ancestorDef }) + NodeRepository.getNodeSelectQuery({ + surveyId, + includeRefData: true, + includeRecordUuid: true, + includeRecordInfo: true, + ancestorDef, + }) const nodesSelect = `${getNodeSelectQuery(nodeDefAncestorMultipleEntity)} WHERE n.node_def_uuid = $/nodeDefUuid/ @@ -35,9 +41,10 @@ const getSelectQuery = ({ surveyId, nodeDef, nodeDefContext, nodeDefAncestorMult LEFT OUTER JOIN ( SELECT - c.ancestor_uuid, + c.record_uuid, + c.ancestor_i_id, json_object_agg(c.node_def_uuid::text, json_build_object( - '${Node.keys.uuid}', c.uuid, + '${Node.keys.iId}', c.i_id, '${Node.keys.nodeDefUuid}', c.node_def_uuid, '${Node.keys.value}', c.value, '${NodeRefData.keys.refData}', c.ref_data @@ -47,10 +54,11 @@ const getSelectQuery = ({ surveyId, nodeDef, nodeDefContext, nodeDefAncestorMult c.value IS NOT NULL ${nodeDefColumnsUuids.length > 0 ? 'AND c.node_def_uuid IN ($/nodeDefColumnsUuids:csv/)' : ''} GROUP BY - c.ancestor_uuid + c.record_uuid, c.ancestor_i_id ) c ON - c.ancestor_uuid = n.uuid` + c.record_uuid = n.record_uuid + AND c.ancestor_i_id = n.i_id` } export const populateTable = async ({ survey, nodeDef, stopIfFunction = null }, client) => { diff --git a/server/modules/surveyRdb/repository/dataTableReadRepository.js b/server/modules/surveyRdb/repository/dataTableReadRepository.js index ae44848997..3716e80616 100644 --- a/server/modules/surveyRdb/repository/dataTableReadRepository.js +++ b/server/modules/surveyRdb/repository/dataTableReadRepository.js @@ -15,7 +15,7 @@ import { db } from '@server/db/db' * { * uuid, //record uuid, * validation: {}, //record validation object, - node_duplicate_uuids: [nodeUuid1, nodeUuid2, ...] //array of duplicate entity uuids + node_duplicate_i_ids: [nodeIId1, nodeIId2, ...] //array of duplicate entity iids * } * ```. * @param {!object} survey - The survey object. @@ -45,13 +45,16 @@ export const fetchRecordsWithDuplicateEntities = async (survey, cycle, nodeDefEn )(nodeDefKeys) const recordAndParentEqualCondition = NodeDef.isRoot(nodeDefEntity) - ? '' + ? `AND ${getColEqualCondition(TableDataNodeDef.columnSet.recordUuid)}` : `AND ${getColEqualCondition(TableDataNodeDef.columnSet.recordUuid)} - AND ${getColEqualCondition(TableDataNodeDef.columnSet.parentUuid)}` + AND ${getColEqualCondition(TableDataNodeDef.columnSet.parentInternalId)}` return await client.any( ` - SELECT r.uuid, r.validation, json_agg(${aliasA}.uuid) as node_duplicate_uuids + SELECT + r.uuid, + r.validation, + json_agg(${aliasA}.i_id) as node_duplicate_i_ids FROM ${Schemata.getSchemaSurvey(surveyId)}.record r JOIN ${tableName} ${aliasA} ON r.uuid = ${aliasA}.${TableDataNodeDef.columnSet.recordUuid} @@ -59,20 +62,20 @@ export const fetchRecordsWithDuplicateEntities = async (survey, cycle, nodeDefEn r.cycle = $1 AND EXISTS ( --exists a node entity with the same key node values in the same record (if not root entity) and in the same parent node entity - SELECT ${aliasB}.${TableDataNodeDef.columnSet.uuid} + SELECT ${aliasB}.${TableDataNodeDef.columnSet.iId} FROM ${tableName} ${aliasB} WHERE --same cycle - ${aliasB}.${TableDataNodeDef.columnSet.recordCycle} = $1 - --different node uuid - AND ${aliasA}.${TableDataNodeDef.columnSet.uuid} != ${aliasB}.${TableDataNodeDef.columnSet.uuid} + ${aliasB}.${TableDataNodeDef.columnSet.recordCycle} = $/cycle/ + --different node internal ID + AND ${aliasA}.${TableDataNodeDef.columnSet.iId} != ${aliasB}.${TableDataNodeDef.columnSet.iId} ${recordAndParentEqualCondition} --same key node(s) values AND (${equalKeysCondition}) ) GROUP BY r.uuid, r.validation `, - [cycle] + { cycle } ) } @@ -80,14 +83,14 @@ export const fetchEntityKeysByRecordAndNodeDefUuid = async ( survey, entityDefUuid, recordUuid, - nodeUuid = null, + nodeIId = null, client = db ) => { const entityDef = Survey.getNodeDefByUuid(entityDefUuid)(survey) const tableDef = new TableDataNodeDef(survey, entityDef) const entityDefKeys = Survey.getNodeDefKeys(entityDef)(survey) const keyColumns = R.pipe(R.map(NodeDefTable.getColumnName), R.join(', '))(entityDefKeys) - const nodeUuidWhereCondition = NodeDef.isRoot(entityDef) ? '' : `AND ${tableDef.columnUuid} = $2` + const nodeIIdWhereCondition = NodeDef.isRoot(entityDef) ? '' : `AND ${tableDef.columnIId} = $/nodeIId/` return await client.oneOrNone( ` @@ -96,9 +99,9 @@ export const fetchEntityKeysByRecordAndNodeDefUuid = async ( FROM ${tableDef.nameAliased} WHERE - ${tableDef.columnRecordUuid} = $1 - ${nodeUuidWhereCondition}`, - [recordUuid, nodeUuid], + ${tableDef.columnRecordUuid} = $/recordUuid/ + ${nodeIIdWhereCondition}`, + { recordUuid, nodeIId }, (row) => (row ? Object.values(row) : []) ) } diff --git a/server/modules/surveyRdb/repository/dataTableUpdateRepository.js b/server/modules/surveyRdb/repository/dataTableUpdateRepository.js index 580aacae21..9b0cfb565e 100644 --- a/server/modules/surveyRdb/repository/dataTableUpdateRepository.js +++ b/server/modules/surveyRdb/repository/dataTableUpdateRepository.js @@ -45,14 +45,14 @@ const _getValuesByColumnName = ({ survey, record, nodeDef, node, ancestorMultipl } else { const { columnSet } = TableDataNodeDef const result = { - [columnSet.uuid]: Node.getUuid(node), - [columnSet.parentUuid]: Node.getUuid(ancestorMultipleEntity), + [columnSet.recordUuid]: Node.getRecordUuid(node), + [columnSet.iId]: Node.getIId(node), + [columnSet.parentInternalId]: Node.getIId(ancestorMultipleEntity), [columnSet.dateCreated]: Node.getDateCreated(node), [columnSet.dateModified]: Node.getDateModified(node), } if (NodeDef.isRoot(nodeDef)) { Object.assign(result, { - [columnSet.recordUuid]: Node.getRecordUuid(node), [columnSet.recordCycle]: Record.getCycle(record), [columnSet.recordStep]: Record.getStep(record), [columnSet.recordOwnerUuid]: Record.getOwnerUuid(record), @@ -66,59 +66,62 @@ const _getValuesByColumnName = ({ survey, record, nodeDef, node, ancestorMultipl } const _findAncestor = ({ ancestorDefUuid, node, nodes }) => { - let currentParent = nodes[Node.getParentUuid(node)] + let currentParent = nodes[Node.getParentInternalId(node)] while (currentParent && !Node.isRoot(currentParent) && Node.getNodeDefUuid(currentParent) !== ancestorDefUuid) { - currentParent = nodes[Node.getParentUuid(currentParent)] + currentParent = nodes[Node.getParentInternalId(currentParent)] } return currentParent } -const _getRowUuid = ({ nodeDef, ancestorMultipleEntity, node }) => - _hasTable(nodeDef) ? Node.getUuid(node) : Node.getUuid(ancestorMultipleEntity) +const _getUpdateKey = ({ nodeDef, ancestorMultipleEntity, node }) => + _hasTable(nodeDef) ? Node.getIId(node) : Node.getIId(ancestorMultipleEntity) export const generateRdbUpdates = ({ survey, record, nodes }) => { // visit nodes with BFS algorithm to avoid FK constraints violations (sort nodes by hierarchy depth) const nodesArray = Object.values(nodes).sort( (nodeA, nodeB) => Node.getHierarchy(nodeA).length - Node.getHierarchy(nodeB).length ) - return nodesArray.reduce((updatesAcc, node) => { + const updatesAcc = new RdbUpdates() + for (const node of nodesArray) { const nodeDef = Survey.getNodeDefByUuid(Node.getNodeDefUuid(node))(survey) // skip single entities if (!NodeDef.isRoot(nodeDef) && NodeDef.isSingleEntity(nodeDef)) { - return updatesAcc + continue } const type = _getType(nodeDef, node) - if (type) { - const ancestorDef = Survey.getNodeDefAncestorMultipleEntity(nodeDef)(survey) - const ancestorDefUuid = NodeDef.getUuid(ancestorDef) - const ancestorMultipleEntity = _findAncestor({ ancestorDefUuid, node, nodes }) - const update = { - type, - schema: Schemata.getSchemaSurveyRdb(Survey.getId(survey)), - table: NodeDefTable.getTableName(nodeDef, ancestorDef), - nodeDefUuid: NodeDef.getUuid(nodeDef), - nodeDefHierarchyLevel: NodeDef.getMetaHierarchy(nodeDef).length, - valuesByColumnName: _getValuesByColumnName({ survey, record, nodeDef, node, ancestorMultipleEntity, type }), - rowUuid: _getRowUuid({ nodeDef, ancestorMultipleEntity, node }), - } - updatesAcc.addUpdate(update) + if (!type) { + continue } - return updatesAcc - }, new RdbUpdates()) + const ancestorDef = Survey.getNodeDefAncestorMultipleEntity(nodeDef)(survey) + const ancestorDefUuid = NodeDef.getUuid(ancestorDef) + const ancestorMultipleEntity = _findAncestor({ ancestorDefUuid, node, nodes }) + const update = { + type, + schema: Schemata.getSchemaSurveyRdb(Survey.getId(survey)), + table: NodeDefTable.getTableName(nodeDef, ancestorDef), + recordUuid: Record.getUuid(record), + nodeIId: _getUpdateKey({ nodeDef, ancestorMultipleEntity, node }), + nodeDefUuid: NodeDef.getUuid(nodeDef), + nodeDefHierarchyLevel: NodeDef.getMetaHierarchy(nodeDef).length, + valuesByColumnName: _getValuesByColumnName({ survey, record, nodeDef, node, ancestorMultipleEntity, type }), + } + updatesAcc.addUpdate(update) + } + return updatesAcc } // ==== execution const _update = (update, client) => { - const { schema, table, valuesByColumnName, rowUuid } = update + const { schema, table, valuesByColumnName, recordUuid, nodeIId } = update const columnNames = Object.keys(valuesByColumnName) const values = Object.values(valuesByColumnName) return client.one( `UPDATE ${schema}.${table} - SET ${columnNames.map((col, i) => `${col} = $${i + 2}`).join(',')} - WHERE uuid = $1 - RETURNING uuid`, - [rowUuid, ...values] + SET ${columnNames.map((col, i) => `${col} = $${i + 3}`).join(',')} + WHERE record_uuid = $1 AND i_id = $2 + RETURNING record_uuid, i_id`, + [recordUuid, nodeIId, ...values] ) } @@ -131,18 +134,18 @@ const _insert = (update, client) => { (${columnNames.join(',')}) VALUES (${columnNames.map((_col, i) => `$${i + 1}`).join(',')}) - RETURNING uuid`, + RETURNING record_uuid, i_id`, values ) } const _delete = (update, client) => { - const { schema, table, rowUuid } = update + const { schema, table, recordUuid, nodeIId } = update return client.oneOrNone( `DELETE FROM ${schema}.${table} - WHERE uuid = $1 - RETURNING uuid`, - rowUuid + WHERE record_uuid = $1 AND i_id = $2 + RETURNING record_uuid, i_id`, + [recordUuid, nodeIId] ) } diff --git a/server/modules/surveyRdb/repository/dataView/create.js b/server/modules/surveyRdb/repository/dataView/create.js index ac91672b2b..4fb91e6451 100644 --- a/server/modules/surveyRdb/repository/dataView/create.js +++ b/server/modules/surveyRdb/repository/dataView/create.js @@ -19,7 +19,7 @@ const _getSelectFieldNodeDefs = (viewDataNodeDef) => const isMultipleAttribute = NodeDef.isMultipleAttribute(nodeDef) if (NodeDef.isEqual(nodeDef)(viewNodeDef)) { if (!isMultipleAttribute) { - return [`${tableData.columnUuid} AS ${colName}`] + return [`${tableData.columnIId} AS ${colName}`] } } else if (isMultipleAttribute && NodeDef.isDescendantOf(viewNodeDef)(nodeDef)) { if (canJoinWithMultipleAttributeTable({ nodeDef, viewNodeDef })) { @@ -50,12 +50,13 @@ const _getJoinWithMultipleAttributeTable = ({ viewDataNodeDef, multAttrColumnNod return `LEFT JOIN ( SELECT - ${multAttrDataTable.columnParentUuid}, + ${multAttrDataTable.columnParentInternalId}, ${columnNames.map((colName) => `json_agg(${multAttrDataTable.alias}.${colName}) AS ${colName}`).join(', ')} FROM ${multAttrDataTable.nameAliased} - GROUP BY ${multAttrDataTable.columnParentUuid} + GROUP BY ${multAttrDataTable.columnParentInternalId} ) AS ${multAttrDataTable.alias} - ON ${multAttrDataTable.columnParentUuid} = ${tableData.columnUuid}` + ON ${multAttrDataTable.columnRecordUuid} = ${tableData.columnRecordUuid} + AND ${multAttrDataTable.columnParentInternalId} = ${tableData.columnIId}` } const _getJoinsWithMultipleAttributeDataTables = (viewDataNodeDef) => { @@ -72,12 +73,10 @@ const _getJoinsWithMultipleAttributeDataTables = (viewDataNodeDef) => { /** * Create a nodeDef data view. - * * @param {object} params - The query parameters. * @param {Survey} params.survey - The survey. * @param {NodeDef} params.nodeDef - The nodeDef to create the data view for. * @param {pgPromise.IDatabase} client - The data base client. - * * @returns {Promise} - The result promise. */ export const createDataView = async ({ survey, nodeDef }, client) => { @@ -110,7 +109,8 @@ export const createDataView = async ({ survey, nodeDef }, client) => { const joinWithParentView = viewDataParent ? `LEFT JOIN ${viewDataParent.nameAliased} - ON ${viewDataParent.columnUuid} = ${tableData.columnParentUuid}` + ON ${viewDataParent.columnRecordUuid} = ${tableData.columnRecordUuid} + AND ${viewDataParent.columnIId} = ${tableData.columnParentIId}` : '' const query = ` diff --git a/server/modules/surveyRdb/repository/dataView/read.js b/server/modules/surveyRdb/repository/dataView/read.js index 62bd0099c5..7c8ef787be 100644 --- a/server/modules/surveyRdb/repository/dataView/read.js +++ b/server/modules/surveyRdb/repository/dataView/read.js @@ -103,8 +103,8 @@ const _prepareSelectFields = ({ viewDataNodeDef.columnUuid, // selected node def columns ...nodeDefCols.flatMap(_selectFieldsByNodeDefType({ viewDataNodeDef, streamMode })), - // Add ancestor uuid columns - ...viewDataNodeDef.columnUuids + // Add ancestor internal IDs columns + ...viewDataNodeDef.columnIIdsNamesFull ) if (includeDateCreated) { queryBuilder.select(viewDataNodeDef.columnDateCreated) diff --git a/server/modules/surveyRdb/repository/nodeHierarchyDisaggregatedViewRepository.js b/server/modules/surveyRdb/repository/nodeHierarchyDisaggregatedViewRepository.js index 337fe684cf..3e6fac8b12 100644 --- a/server/modules/surveyRdb/repository/nodeHierarchyDisaggregatedViewRepository.js +++ b/server/modules/surveyRdb/repository/nodeHierarchyDisaggregatedViewRepository.js @@ -27,28 +27,28 @@ export const createNodeHierarchyDisaggregatedView = async (survey, client = db) ( SELECT n.id AS ${columns.nodeId}, - n.uuid AS ${columns.nodeUuid}, + n.i_id AS ${columns.nodeIId}, n.node_def_uuid AS ${columns.nodeDefUuid}, - jsonb_array_elements_text(n.meta->'h')::uuid AS ${columns.nodeAncestorUuid} + jsonb_array_elements_text(n.meta->'h')::integer AS ${columns.nodeAncestorIId} FROM ${surveySchema}.node n ) h ON - n.uuid = h.${columns.nodeAncestorUuid} + n.i_id = h.${columns.nodeAncestorIId} -- Union with root nodes UNION ALL SELECT n.record_uuid AS ${columns.recordUuid}, n.id AS ${columns.nodeId}, - n.uuid AS ${columns.nodeUuid}, + n.i_id AS ${columns.nodeIId}, n.node_def_uuid AS ${columns.nodeDefUuid}, - NULL AS ${columns.nodeAncestorUuid}, + NULL AS ${columns.nodeAncestorIId}, NULL AS ${columns.nodeAncestorId}, NULL AS ${columns.nodeDefAncestorUuid} FROM ${surveySchema}.node n WHERE - n.parent_uuid IS NULL + n.p_i_id IS NULL ORDER BY ${columns.nodeAncestorId}, ${columns.nodeId} diff --git a/server/modules/surveyRdb/repository/nodeKeysHierarchyViewRepository.js b/server/modules/surveyRdb/repository/nodeKeysHierarchyViewRepository.js index 41b4daef28..b9300d54d7 100644 --- a/server/modules/surveyRdb/repository/nodeKeysHierarchyViewRepository.js +++ b/server/modules/surveyRdb/repository/nodeKeysHierarchyViewRepository.js @@ -16,14 +16,14 @@ export const createNodeKeysHierarchyView = async (survey, client = db) => { CREATE VIEW ${NodeKeysHierarchyView.getNameWithSchema(surveyId)} AS ( SELECT h.${NodeHierarchyDisaggregatedView.columns.nodeId} AS ${NodeKeysHierarchyView.columns.nodeId}, - h.${NodeHierarchyDisaggregatedView.columns.nodeUuid} AS ${NodeKeysHierarchyView.columns.nodeUuid}, + h.${NodeHierarchyDisaggregatedView.columns.nodeIId} AS ${NodeKeysHierarchyView.columns.nodeIId}, h.${NodeHierarchyDisaggregatedView.columns.nodeDefUuid} AS ${NodeKeysHierarchyView.columns.nodeDefUuid}, h.${NodeHierarchyDisaggregatedView.columns.recordUuid} AS ${NodeKeysHierarchyView.columns.recordUuid}, k_s.${NodeKeysView.columns.keys} AS ${NodeKeysHierarchyView.columns.keysSelf}, jsonb_agg( jsonb_build_object( '${NodeKeys.keys.nodeDefUuid}', h.${NodeHierarchyDisaggregatedView.columns.nodeDefAncestorUuid}, - '${NodeKeys.keys.nodeUuid}', h.${NodeHierarchyDisaggregatedView.columns.nodeAncestorUuid}, + '${NodeKeys.keys.nodeIId}', h.${NodeHierarchyDisaggregatedView.columns.nodeAncestorIId}, '${NodeKeys.keys.nodeId}', h.${NodeHierarchyDisaggregatedView.columns.nodeAncestorId}, '${NodeKeys.keys.recordUuid}', h.${NodeHierarchyDisaggregatedView.columns.recordUuid}, '${NodeKeys.keys.keys}', k_h.${NodeKeysView.columns.keys} @@ -36,12 +36,12 @@ export const createNodeKeysHierarchyView = async (survey, client = db) => { LEFT OUTER JOIN ${NodeKeysView.getNameWithSchema(surveyId)} k_h ON - k_h.${NodeKeysView.columns.nodeUuid} = h.${NodeHierarchyDisaggregatedView.columns.nodeAncestorUuid} + k_h.${NodeKeysView.columns.nodeIId} = h.${NodeHierarchyDisaggregatedView.columns.nodeAncestorIId} -- Join to get keys for itself if it's an entity LEFT OUTER JOIN ${NodeKeysView.getNameWithSchema(surveyId)} k_s ON - k_s.${NodeKeysView.columns.nodeUuid} = h.${NodeHierarchyDisaggregatedView.columns.nodeUuid} + k_s.${NodeKeysView.columns.nodeIId} = h.${NodeHierarchyDisaggregatedView.columns.nodeIId} GROUP BY 1,2,3,4,5 )`) diff --git a/server/modules/surveyRdb/repository/nodeKeysViewRepository.js b/server/modules/surveyRdb/repository/nodeKeysViewRepository.js index 35e946c4a9..92bb70bf69 100644 --- a/server/modules/surveyRdb/repository/nodeKeysViewRepository.js +++ b/server/modules/surveyRdb/repository/nodeKeysViewRepository.js @@ -21,9 +21,8 @@ export const createNodeKeysView = async (survey, client = db) => { selectViews.push(` SELECT ${viewDataNodeDef.columnIdName} AS ${NodeKeysView.columns.nodeId}, - ${viewDataNodeDef.columnNodeDefUuid.name} AS ${NodeKeysView.columns.nodeUuid}, - '${NodeDef.getUuid(nodeDef)}' AS ${NodeKeysView.columns.nodeDefUuid}, ${ViewDataNodeDef.columnSet.recordUuid} AS ${NodeKeysView.columns.recordUuid}, + ${viewDataNodeDef.columnNodeDefIIdName} AS ${NodeKeysView.columns.nodeIId}, ${RDBDataView.columns.keys} AS ${NodeKeysView.columns.keys} FROM ${RDBDataView.getNameWithSchema(surveyId)(nodeDef)} diff --git a/server/modules/surveyRdb/repository/resultNode/massiveUpdateData.js b/server/modules/surveyRdb/repository/resultNode/massiveUpdateData.js index 00e72c64a0..6b1aa78a33 100644 --- a/server/modules/surveyRdb/repository/resultNode/massiveUpdateData.js +++ b/server/modules/surveyRdb/repository/resultNode/massiveUpdateData.js @@ -70,8 +70,8 @@ export default class MassiveUpdateData extends MassiveUpdate { }, { [TableDataNodeDef.columnSet.recordUuid]: rowResult[TableDataNodeDef.columnSet.recordUuid], - // in this table the coloumn uuid is the parent_uuid of the result - [TableDataNodeDef.columnSet.uuid]: rowResult[TableDataNodeDef.columnSet.parentUuid], + // in this table the coloumn iId is the parentInternalId of the result + [TableDataNodeDef.columnSet.iId]: rowResult[TableDataNodeDef.columnSet.parentInternalId], } ) diff --git a/server/modules/surveyRdb/repository/resultNode/massiveUpdateNodes.js b/server/modules/surveyRdb/repository/resultNode/massiveUpdateNodes.js index 097ce4b93f..14880ccde8 100644 --- a/server/modules/surveyRdb/repository/resultNode/massiveUpdateNodes.js +++ b/server/modules/surveyRdb/repository/resultNode/massiveUpdateNodes.js @@ -34,13 +34,14 @@ export default class MassiveUpdateNodes extends MassiveUpdate { includeExtendedCols: true, }) + const colSet = TableNode.columnSet + // Adding '?' in front of a column name means it is only for a WHERE condition in this case the record_uuid const cols = [ - `?${TableNode.columnSet.recordUuid}`, - `?${TableNode.columnSet.nodeDefUuid}`, - `?${TableNode.columnSet.parentUuid}`, - - new Column({ name: TableNode.columnSet.value, cast: 'jsonb' }), + `?${colSet.recordUuid}`, + `?${colSet.nodeDefUuid}`, + `?${colSet.parentIId}`, + new Column({ name: colSet.value, cast: 'jsonb' }), ] const tableNode = new TableNode(survey) @@ -51,9 +52,9 @@ export default class MassiveUpdateNodes extends MassiveUpdate { table: tableNode.name, cols, where: ` WHERE - t.${TableNode.columnSet.recordUuid}::uuid = v.${TableNode.columnSet.recordUuid}::uuid - AND t.${TableNode.columnSet.nodeDefUuid}::uuid = v.${TableNode.columnSet.nodeDefUuid}::uuid - AND t.${TableNode.columnSet.parentUuid}::uuid = v.${TableNode.columnSet.parentUuid}::uuid `, + t.${colSet.recordUuid}::uuid = v.${colSet.recordUuid}::uuid + AND t.${colSet.nodeDefUuid}::uuid = v.${colSet.nodeDefUuid}::uuid + AND t.${colSet.parentIId} = v.${colSet.parentIId} `, }, tx ) @@ -68,10 +69,10 @@ export default class MassiveUpdateNodes extends MassiveUpdate { const value = extractValueFromRowResult({ rowResult, nodeDef, columnName }) const values = { - [TableNode.columnSet.parentUuid]: rowResult[TableNode.columnSet.parentUuid], - [TableNode.columnSet.recordUuid]: rowResult[TableNode.columnSet.recordUuid], - [TableNode.columnSet.nodeDefUuid]: NodeDef.getUuid(nodeDef), - [TableNode.columnSet.value]: value, + [colSet.parentIId]: rowResult[colSet.parentIId], + [colSet.recordUuid]: rowResult[colSet.recordUuid], + [colSet.nodeDefUuid]: NodeDef.getUuid(nodeDef), + [colSet.value]: value, } super.push(values) diff --git a/server/modules/surveyRdb/schemaRdb/nodeHierarchyDisaggregatedView.js b/server/modules/surveyRdb/schemaRdb/nodeHierarchyDisaggregatedView.js index 465daa2269..952fb861b9 100644 --- a/server/modules/surveyRdb/schemaRdb/nodeHierarchyDisaggregatedView.js +++ b/server/modules/surveyRdb/schemaRdb/nodeHierarchyDisaggregatedView.js @@ -2,10 +2,10 @@ import * as SchemaRdb from '@common/surveyRdb/schemaRdb' export const columns = { nodeId: 'node_id', - nodeUuid: 'node_uuid', + nodeIId: 'node_i_id', nodeDefUuid: 'node_def_uuid', nodeAncestorId: 'node_ancestor_id', - nodeAncestorUuid: 'node_ancestor_uuid', + nodeAncestorIId: 'node_ancestor_i_id', nodeDefAncestorUuid: 'node_def_ancestor_uuid', recordUuid: 'record_uuid', } diff --git a/server/modules/surveyRdb/schemaRdb/nodeKeysHierarchyView.js b/server/modules/surveyRdb/schemaRdb/nodeKeysHierarchyView.js index 809afc5ec0..3c27c80519 100644 --- a/server/modules/surveyRdb/schemaRdb/nodeKeysHierarchyView.js +++ b/server/modules/surveyRdb/schemaRdb/nodeKeysHierarchyView.js @@ -1,8 +1,8 @@ import * as SchemaRdb from '@common/surveyRdb/schemaRdb' export const columns = { - nodeUuid: 'node_uuid', nodeId: 'node_id', + nodeIId: 'node_i_id', nodeDefUuid: 'node_def_uuid', keysHierarchy: 'keys_hierarchy', keysSelf: 'keys_self', diff --git a/server/modules/surveyRdb/schemaRdb/nodeKeysView.js b/server/modules/surveyRdb/schemaRdb/nodeKeysView.js index 6001b56efa..3866181e0e 100644 --- a/server/modules/surveyRdb/schemaRdb/nodeKeysView.js +++ b/server/modules/surveyRdb/schemaRdb/nodeKeysView.js @@ -2,7 +2,7 @@ import * as SchemaRdb from '@common/surveyRdb/schemaRdb' export const columns = { nodeId: 'node_id', - nodeUuid: 'node_uuid', + nodeIId: 'node_i_id', nodeDefUuid: 'node_def_uuid', recordUuid: 'record_uuid', keys: 'keys', diff --git a/server/modules/surveyRdb/service/surveyRdbCreationJob/recordsUniquenessValidationJob.js b/server/modules/surveyRdb/service/surveyRdbCreationJob/recordsUniquenessValidationJob.js index 92ef221bb1..5edeb5872e 100644 --- a/server/modules/surveyRdb/service/surveyRdbCreationJob/recordsUniquenessValidationJob.js +++ b/server/modules/surveyRdb/service/surveyRdbCreationJob/recordsUniquenessValidationJob.js @@ -14,14 +14,14 @@ import * as SurveyRdbManager from '@server/modules/surveyRdb/manager/surveyRdbMa const recordValidationUpdateBatchSize = 1000 -const _updateNodeValidation = (validationRecord, nodeUuid, validationNode) => { - const validationNodeOld = Validation.getFieldValidation(nodeUuid)(validationRecord) +const _updateNodeValidation = (validationRecord, nodeIId, validationNode) => { + const validationNodeOld = Validation.getFieldValidation(nodeIId)(validationRecord) // Merge new validation with node validation const nodeValidationUpdated = R.mergeDeepRight(validationNodeOld, validationNode) // Replace node validation in record validation - return R.pipe(Validation.setValid(false), Validation.setField(nodeUuid, nodeValidationUpdated))(validationRecord) + return R.pipe(Validation.setValid(false), Validation.setField(nodeIId, nodeValidationUpdated))(validationRecord) } export default class RecordsUniquenessValidationJob extends Job { @@ -74,12 +74,12 @@ export default class RecordsUniquenessValidationJob extends Job { } // 2. for each duplicate node entity, update record validation - const { uuid: recordUuid, validation, node_duplicate_uuids: nodeDuplicateUuids } = rowRecordDuplicate - const nodeRootUuid = nodeDuplicateUuids[0] + const { uuid: recordUuid, validation, node_duplicate_iids: nodeDuplicateIIds } = rowRecordDuplicate + const nodeRootIId = nodeDuplicateIIds[0] const nodesKeyDuplicate = await RecordManager.fetchChildNodesByNodeDefUuids( this.surveyId, recordUuid, - nodeRootUuid, + nodeRootIId, nodeDefKeys.map(NodeDef.getUuid), this.tx ) @@ -88,7 +88,7 @@ export default class RecordsUniquenessValidationJob extends Job { const validationRecordUpdated = R.pipe( R.reduce( (validationRecordAccumulator, nodeKeyDuplicate) => - _updateNodeValidation(validationRecordAccumulator, Node.getUuid(nodeKeyDuplicate), validationDuplicate), + _updateNodeValidation(validationRecordAccumulator, Node.getIId(nodeKeyDuplicate), validationDuplicate), validationRecord ), Validation.updateCounts diff --git a/test/e2e/tests/_surveyExport/records.js b/test/e2e/tests/_surveyExport/records.js index 2d544d51e3..1f95b8b818 100644 --- a/test/e2e/tests/_surveyExport/records.js +++ b/test/e2e/tests/_surveyExport/records.js @@ -1,4 +1,4 @@ -import * as PromiseUtils from '../../../../core/promiseUtils' +import * as Node from '../../../../core/record/node' import { ExportFile } from '../../../../server/modules/survey/service/surveyExport/exportFile' import { getSurveyEntry } from '../../paths' import { records } from '../../mock/records' @@ -6,7 +6,6 @@ import { cluster, tree } from '../../mock/nodeDefs' import { formatTime } from '../_record' import { getNodeDefByName } from './_surveyUtils' -// eslint-disable-next-line camelcase const { tree_id } = tree.children const getNodesByDefUuid = (nodeDefUuid) => (record) => @@ -72,31 +71,30 @@ const verifyRecord = async (survey, surveyExport, recordsMock, recordUuid) => { await expect(recordExport.step).toBe('1') await expect(recordExport.cycle).toBe('0') - await PromiseUtils.each(Object.entries(record), async ([name, value]) => { + for (const [name, value] of Object.entries(record)) { if (name === 'trees') { // const treeDefExport = getNodeDefByName(tree.name)(surveyExport) const treeIdDefExport = getNodeDefByName(tree_id.name)(surveyExport) const treeIdsExport = getNodesByDefUuid(treeIdDefExport.uuid)(recordExport) - await PromiseUtils.each(value, async (treeNode) => { + for (const treeNode of value) { const treeId = treeNode[tree_id.name] const treeIdExport = treeIdsExport.find((_nodeExport) => _nodeExport.value === treeId) await verifyNode(treeIdDefExport, treeIdExport, treeId) - // const treeExport = recordExport.nodes[treeIdExport.parentUuid] - await PromiseUtils.each(Object.entries(treeNode), async ([treeChildName, treeChildValue]) => { + for (const [treeChildName, treeChildValue] of Object.entries(treeNode)) { const nodeDefExport = getNodeDefByName(treeChildName)(surveyExport) const nodeExport = getNodesByDefUuid(nodeDefExport.uuid)(recordExport).find( - (_node) => _node.parentUuid === treeIdExport.parentUuid + (_node) => Node.getParentInternalId(_node) === Node.getParentInternalId(treeIdExport) ) await verifyNode(nodeDefExport, nodeExport, treeChildValue) - }) - }) + } + } } else { const nodeDefExport = getNodeDefByName(name)(surveyExport) const nodeExport = getNodeByDefUuid(nodeDefExport.uuid)(recordExport) await verifyNode(nodeDefExport, nodeExport, value) } - }) + } } export const verifyRecords = (survey, recordsMock = records) => @@ -106,7 +104,7 @@ export const verifyRecords = (survey, recordsMock = records) => await expect(recordsExport.length).toBe(recordsMock.length) - await PromiseUtils.each(recordsExport, async (recordExport) => + for (const recordExport of recordsExport) { verifyRecord(survey, surveyExport, recordsMock, recordExport.uuid) - ) + } }) diff --git a/test/integration/tests/004applicableUpdatetest.js b/test/integration/tests/004applicableUpdatetest.js index 278e901299..210f6ee64a 100644 --- a/test/integration/tests/004applicableUpdatetest.js +++ b/test/integration/tests/004applicableUpdatetest.js @@ -56,7 +56,7 @@ describe('Applicable Test', () => { const nodeSource = RecordUtils.findNodeByPath('cluster/num')(survey, record) const nodeDependent = RecordUtils.findNodeByPath('cluster/dependent_node')(survey, record) const nodeDependentParent = Record.getParentNode(nodeDependent)(record) - const nodeDependentParentUuid = Node.getUuid(nodeDependentParent) + const nodeDependentParentIId = Node.getIId(nodeDependentParent) const nodeDependentDefUuid = Node.getNodeDefUuid(nodeDependent) // Test values, couples of expected values by input @@ -73,7 +73,7 @@ describe('Applicable Test', () => { // Update source node value const nodesUpdated = { - [Node.getUuid(nodeSource)]: Node.assocValue(sourceValue)(nodeSource), + [Node.getIId(nodeSource)]: Node.assocValue(sourceValue)(nodeSource), } global.applicableRecord = Record.mergeNodes(nodesUpdated)(record) @@ -86,7 +86,7 @@ describe('Applicable Test', () => { }) global.applicableRecord = recordUpdate - const nodeDependentParentUpdated = Record.getNodeByUuid(nodeDependentParentUuid)(record) + const nodeDependentParentUpdated = Record.getNodeByInternalId(nodeDependentParentIId)(record) const applicable = Node.isChildApplicable(nodeDependentDefUuid)(nodeDependentParentUpdated) diff --git a/test/integration/tests/005calculatedValueUpdatetest.js b/test/integration/tests/005calculatedValueUpdatetest.js index bf5203db40..a7c9480b45 100644 --- a/test/integration/tests/005calculatedValueUpdatetest.js +++ b/test/integration/tests/005calculatedValueUpdatetest.js @@ -26,7 +26,7 @@ const updateNodeAndExpectDependentNodeValueToBe = async ( const nodeSource = RecordUtils.findNodeByPath(sourcePath)(survey, record) const nodesUpdated = { - [Node.getUuid(nodeSource)]: Node.assocValue(sourceValue)(nodeSource), + [Node.getIId(nodeSource)]: Node.assocValue(sourceValue)(nodeSource), } global.applicableRecord = Record.mergeNodes(nodesUpdated)(record) @@ -34,10 +34,10 @@ const updateNodeAndExpectDependentNodeValueToBe = async ( const { record: recordUpdate } = await RecordManager.updateNodesDependents({ survey, record, nodes: nodesUpdated }) global.applicableRecord = recordUpdate - const nodeDependent = RecordUtils.findNodeByPath(dependentPath)(survey, record) + const nodeDependent = RecordUtils.findNodeByPath(dependentPath)(survey, recordUpdate) expect(Node.getValue(nodeDependent)).toBe(dependentExpectedValue) - return record + return recordUpdate } describe('Calculated value test', () => { diff --git a/test/integration/tests/008recordValidationtest.js b/test/integration/tests/008recordValidationtest.js index ed1540a8a2..95ccb5bbfa 100644 --- a/test/integration/tests/008recordValidationtest.js +++ b/test/integration/tests/008recordValidationtest.js @@ -31,7 +31,7 @@ const _deleteNode = async (parentNode, childNodeName, childNodePosition) => { const childDef = Survey.getNodeDefByName(childNodeName)(survey) const children = Record.getNodeChildrenByDefUuid(parentNode, NodeDef.getUuid(childDef))(record) const node = children[childNodePosition - 1] - global.applicableRecord = await RecordManager.deleteNode(getContextUser(), survey, record, Node.getUuid(node)) + global.applicableRecord = await RecordManager.deleteNode(getContextUser(), survey, record, Node.getIId(node)) } const _updateNodeAndExpectValidationToBe = async (nodePath, value, validationExpected) => { @@ -40,7 +40,7 @@ const _updateNodeAndExpectValidationToBe = async (nodePath, value, validationExp await _persistNode(Node.assocValue(value)(node)) - const nodeValidation = Validation.getFieldValidation(Node.getUuid(node))(Record.getValidation(record)) + const nodeValidation = Validation.getFieldValidation(Node.getIId(node))(Record.getValidation(record)) expect(Validation.isValid(nodeValidation)).toBe(validationExpected) } @@ -62,7 +62,7 @@ const _addNodeAndExpectCountToBe = async (parentNodePath, childNodeName, expecte const parentNode = RecordUtils.findNodeByPath(parentNodePath)(survey, record) const childDef = Survey.getNodeDefByName(childNodeName)(survey) - const node = Node.newNode(NodeDef.getUuid(childDef), Record.getUuid(record), parentNode) + const node = Node.newNode({ record, nodeDefUuid: NodeDef.getUuid(childDef), parentNode }) await _persistNode(node) @@ -76,7 +76,7 @@ const _addNodeWithDuplicateKeyAndExpect2ValidationErrors = async () => { // Add a new plot const nodeRoot = Record.getRootNode(record) const nodeDefPlot = Survey.getNodeDefByName('plot')(survey) - const nodePlot = Node.newNode(NodeDef.getUuid(nodeDefPlot), Record.getUuid(record), nodeRoot) + const nodePlot = Node.newNode({ record, nodeDefUuid: NodeDef.getUuid(nodeDefPlot), parentNode: nodeRoot }) await _persistNode(nodePlot) // Update new plot num with a duplicate value @@ -84,15 +84,15 @@ const _addNodeWithDuplicateKeyAndExpect2ValidationErrors = async () => { const value = 2 // Duplicate value await _persistNode(Node.assocValue(value)(nodePlotNum)) + const recordValidation = Record.getValidation(record) // Expect validation to be invalid - const nodePlotNumValidation = Validation.getFieldValidation(Node.getUuid(nodePlotNum))(Record.getValidation(record)) + const nodePlotNumValidation = Validation.getFieldValidation(Node.getIId(nodePlotNum))(recordValidation) expect(Validation.isValid(nodePlotNumValidation)).toBe(false) // Expect duplicate node validation to be invalid const nodePlotNumDuplicate = RecordUtils.findNodeByPath('cluster/plot[2]/plot_num')(survey, record) - const nodePlotNumDuplicateValidation = Validation.getFieldValidation(Node.getUuid(nodePlotNumDuplicate))( - Record.getValidation(record) - ) + const nodePlotNumDuplicateIId = Node.getIId(nodePlotNumDuplicate) + const nodePlotNumDuplicateValidation = Validation.getFieldValidation(nodePlotNumDuplicateIId)(recordValidation) expect(Validation.isValid(nodePlotNumDuplicateValidation)).toBe(false) } @@ -102,10 +102,10 @@ const _removeNodeWithDuplicateKeyAndExpectDuplicateNodeKeyToBeValid = async () = await _deleteNode(Record.getRootNode(record), 'plot', 4) + const recordValidation = Record.getValidation(record) const nodePlotNumDuplicate = RecordUtils.findNodeByPath('cluster/plot[2]/plot_num')(survey, record) - const nodePlotNumDuplicateValidation = Validation.getFieldValidation(Node.getUuid(nodePlotNumDuplicate))( - Record.getValidation(record) - ) + const nodePlotNumDuplicateIId = Node.getIId(nodePlotNumDuplicate) + const nodePlotNumDuplicateValidation = Validation.getFieldValidation(nodePlotNumDuplicateIId)(recordValidation) expect(Validation.isValid(nodePlotNumDuplicateValidation)).toBe(true) } @@ -255,7 +255,7 @@ describe('Record Validation Test', () => { const nodeDefChild = Survey.getNodeDefByName('tree')(survey) const validationCount = R.pipe( Record.getValidation, - RecordValidation.getValidationChildrenCount(Node.getUuid(nodeParent), NodeDef.getUuid(nodeDefChild)) + RecordValidation.getValidationChildrenCount(Node.getIId(nodeParent), NodeDef.getUuid(nodeDefChild)) )(record) expect(Validation.isValid(validationCount)).toBe(false) // Min count = 1 @@ -265,12 +265,12 @@ describe('Record Validation Test', () => { [ Validation.keys.validation, Validation.keys.fields, - RecordValidation.getValidationChildrenCountKey(Node.getUuid(nodeParent), NodeDef.getUuid(nodeDefChild)), + RecordValidation.getValidationChildrenCountKey(Node.getIId(nodeParent), NodeDef.getUuid(nodeDefChild)), ], record ) // Children count validation should be deleted - /* eslint-disable no-unused-expressions */ + expect(validationCountUpdated).toBeUndefined() }) diff --git a/test/utils/recordBuilder.js b/test/utils/recordBuilder.js index 8a4be0e7f8..506028930c 100644 --- a/test/utils/recordBuilder.js +++ b/test/utils/recordBuilder.js @@ -23,17 +23,18 @@ class EntityBuilder extends NodeBuilder { this.childBuilders = childBuilders } - build(survey, parentNodeDef, recordUuid, parentNode) { + build(survey, parentNodeDef, record, parentNode) { const nodeDef = parentNodeDef ? Survey.getNodeDefChildByName(parentNodeDef, this.nodeDefName)(survey) : Survey.getNodeDefRoot(survey) - const entity = Node.newNode(NodeDef.getUuid(nodeDef), recordUuid, parentNode) + const entity = Node.newNode({ record, nodeDefUuid: NodeDef.getUuid(nodeDef), parentNode }) + record.lastNodeInternalId = Node.getIId(entity) return R.pipe( - R.map((childBuilder) => childBuilder.build(survey, nodeDef, recordUuid, entity)), + R.map((childBuilder) => childBuilder.build(survey, nodeDef, record, entity)), R.mergeAll, - R.assoc(Node.getUuid(entity), entity) + R.assoc(Node.getIId(entity), entity) )(this.childBuilders) } @@ -46,7 +47,8 @@ class EntityBuilder extends NodeBuilder { } else if (NodeDef.isSingle(nodeDef)) { node = R.head(Record.getNodeChildrenByDefUuid(parentNode, NodeDef.getUuid(nodeDef))(record)) } else { - node = Node.newNode(NodeDef.getUuid(nodeDef), Record.getUuid(record), parentNode) + node = Node.newNode({ record, nodeDefUuid: NodeDef.getUuid(nodeDef), parentNode }) + record.lastNodeInternalId = Node.getIId(node) record = await RecordManager.persistNode({ user, survey, record, node, system: true }, t) } @@ -64,12 +66,12 @@ class AttributeBuilder extends NodeBuilder { this.value = value } - build(survey, parentNodeDef, recordUuid, parentNode) { + build(survey, _parentNodeDef, record, parentNode) { const nodeDef = Survey.getNodeDefByName(this.nodeDefName)(survey) - const attribute = Node.newNode(NodeDef.getUuid(nodeDef), recordUuid, parentNode, this.value) - + const attribute = Node.newNode({ record, nodeDefUuid: NodeDef.getUuid(nodeDef), parentNode, value: this.value }) + record.lastNodeInternalId = Node.getIId(attribute) return { - [Node.getUuid(attribute)]: attribute, + [Node.getIId(attribute)]: attribute, } } @@ -84,10 +86,13 @@ class AttributeBuilder extends NodeBuilder { ? R.head(Record.getNodeChildrenByDefUuid(parentNode, NodeDef.getUuid(nodeDef))(record)) : null - const nodeToPersist = nodeInRecord - ? Node.assocValue(this.value)(nodeInRecord) - : Node.newNode(NodeDef.getUuid(nodeDef), Record.getUuid(record), parentNode, this.value) - + let nodeToPersist + if (nodeInRecord) { + nodeToPersist = Node.assocValue(this.value)(nodeInRecord) + } else { + nodeToPersist = Node.newNode({ record, nodeDefUuid: NodeDef.getUuid(nodeDef), parentNode, value: this.value }) + record.lastNodeInternalId = Node.getIId(nodeToPersist) + } return await RecordManager.persistNode({ user, survey, record, node: nodeToPersist, system: true }, t) } } @@ -101,7 +106,7 @@ class RecordBuilder { build() { const record = RecordUtils.newRecord(this.user) - const nodes = this.rootEntityBuilder.build(this.survey, null, Record.getUuid(record), null) + const nodes = this.rootEntityBuilder.build(this.survey, null, record, null) return Record.assocNodes({ nodes })(record) } diff --git a/test/utils/recordUtils.js b/test/utils/recordUtils.js index 1f6003f4cd..ff3e38d3c8 100644 --- a/test/utils/recordUtils.js +++ b/test/utils/recordUtils.js @@ -29,7 +29,7 @@ export const getNodePath = (node) => (survey, record) => { if (NodeDef.isMultiple(nodeDef)) { const siblings = Record.getNodeChildrenByDefUuid(parentNode, nodeDefUuid)(record) - const index = R.findIndex((n) => Node.getUuid(n) === Node.getUuid(node), siblings) + const index = R.findIndex((n) => Node.getIId(n) === Node.getIId(node), siblings) return `${parentNodePath}/${NodeDef.getName(nodeDef)}[${index}]` } @@ -86,5 +86,5 @@ export const findNodeValueByPath = (path) => (survey, record) => Node.getValue(f export const getValidationChildrenCount = (parentNode, childDef) => R.pipe( Validation.getValidation, - RecordValidation.getValidationChildrenCount(Node.getUuid(parentNode), NodeDef.getUuid(childDef)) + RecordValidation.getValidationChildrenCount(Node.getIId(parentNode), NodeDef.getUuid(childDef)) ) diff --git a/webapp/components/DataQuery/DataQuery.js b/webapp/components/DataQuery/DataQuery.js index e35860f69e..fcfce52529 100644 --- a/webapp/components/DataQuery/DataQuery.js +++ b/webapp/components/DataQuery/DataQuery.js @@ -97,7 +97,7 @@ const DataQuery = () => { dispatch(DataExplorerActions.closeRecordEditModal())} recordUuid={recordEditModalProps.recordUuid} - parentNodeUuid={recordEditModalProps.parentNodeUuid} + parentNodeIId={recordEditModalProps.parentNodeIId} /> )} diff --git a/webapp/components/DataQuery/Visualizer/DataQueryChart/DataQueryScatterChart/DataQueryScatterChart.js b/webapp/components/DataQuery/Visualizer/DataQueryChart/DataQueryScatterChart/DataQueryScatterChart.js index 65a9df83c5..158c3a142d 100644 --- a/webapp/components/DataQuery/Visualizer/DataQueryChart/DataQueryScatterChart/DataQueryScatterChart.js +++ b/webapp/components/DataQuery/Visualizer/DataQueryChart/DataQueryScatterChart/DataQueryScatterChart.js @@ -67,9 +67,9 @@ export const DataQueryScatterChart = (props) => { const { payload } = e const { record_uuid: recordUuid } = payload const queryParentEntityField = `${queryEntityDefName}_uuid` - const parentNodeUuid = payload[queryParentEntityField] - if (recordUuid && parentNodeUuid) { - dispatch(DataExplorerActions.openRecordEditModal({ recordUuid, parentNodeUuid })) + const parentNodeIId = payload[queryParentEntityField] + if (recordUuid && parentNodeIId) { + dispatch(DataExplorerActions.openRecordEditModal({ recordUuid, parentNodeIId })) } }, [dispatch, queryEntityDefName] diff --git a/webapp/components/DataQuery/Visualizer/DataQueryTable/Row/Column/ColumnDataEdit.js b/webapp/components/DataQuery/Visualizer/DataQueryTable/Row/Column/ColumnDataEdit.js index bc3b33a3ae..c5c63a30b2 100644 --- a/webapp/components/DataQuery/Visualizer/DataQueryTable/Row/Column/ColumnDataEdit.js +++ b/webapp/components/DataQuery/Visualizer/DataQueryTable/Row/Column/ColumnDataEdit.js @@ -30,7 +30,7 @@ const ColumnDataEdit = (props) => { const { widthOuter } = useColumn({ nodeDef, query, colWidth }) const cellNode = cell?.node - const nodeUpdated = useRecordNode({ nodeUuid: cellNode?.uuid }) + const nodeUpdated = useRecordNode({ nodeIId: Node.getIId(cellNode) }) const node = nodeUpdated || cellNode const recordValidation = Record.getValidation(record) const validation = RecordValidation.getNodeValidation(node)(recordValidation) @@ -48,7 +48,7 @@ const ColumnDataEdit = (props) => { nodeDef={nodeDef} parentNode={{ [Node.keys.recordUuid]: Record.getUuid(record), - [Node.keys.parentUuid]: cell.parentUuid, + [Node.keys.pIId]: Node.getParentInternalId(cellNode), }} nodes={[node]} entry diff --git a/webapp/components/DataQuery/Visualizer/DataQueryTable/Row/LinkRecord/LinkRecord.js b/webapp/components/DataQuery/Visualizer/DataQueryTable/Row/LinkRecord/LinkRecord.js index 07fb188899..11be3d997e 100644 --- a/webapp/components/DataQuery/Visualizer/DataQueryTable/Row/LinkRecord/LinkRecord.js +++ b/webapp/components/DataQuery/Visualizer/DataQueryTable/Row/LinkRecord/LinkRecord.js @@ -11,10 +11,10 @@ const LinkRecord = (props) => { const i18n = useI18n() - const { parentUuid, record } = row + const { parentIId, record } = row const recordUuid = Record.getUuid(record) - const recordEditUrl = `${appModuleUri(dataModules.record)}${recordUuid}?pageNodeUuid=${parentUuid}` + const recordEditUrl = `${appModuleUri(dataModules.record)}${recordUuid}?pageNodeIId=${parentIId}` return ( diff --git a/webapp/components/survey/NodeDefsSelector/NodeDefTreeSelect/useBuildEntityTreeData.js b/webapp/components/survey/NodeDefsSelector/NodeDefTreeSelect/useBuildEntityTreeData.js index 6c77f92ca3..305907ef6c 100644 --- a/webapp/components/survey/NodeDefsSelector/NodeDefTreeSelect/useBuildEntityTreeData.js +++ b/webapp/components/survey/NodeDefsSelector/NodeDefTreeSelect/useBuildEntityTreeData.js @@ -11,8 +11,8 @@ import { useNodeDefLabelType, usePagesUuidMap } from '@webapp/store/ui/surveyFor import { TestId } from '@webapp/utils/testId' const getPageNode = ({ record, pagesUuidMap, nodeDefUuid }) => { - const nodeUuid = pagesUuidMap[nodeDefUuid] - return record && nodeUuid ? Record.getNodeByUuid(nodeUuid)(record) : null + const nodeIId = pagesUuidMap[nodeDefUuid] + return record && nodeIId ? Record.getNodeByInternalId(nodeIId)(record) : null } const isPageVisible = ({ cycle, record, pageNodeDef, parentNode }) => diff --git a/webapp/components/survey/Record/Record.js b/webapp/components/survey/Record/Record.js index 463e6dc442..96e295912b 100644 --- a/webapp/components/survey/Record/Record.js +++ b/webapp/components/survey/Record/Record.js @@ -13,7 +13,7 @@ const Record = (props) => { editable: editableProp = true, locked = false, noHeader = false, - pageNodeUuid, + pageNodeIId, record: recordProp = null, recordUuid, } = props @@ -21,7 +21,7 @@ const Record = (props) => { editableProp, locked, noHeader, - pageNodeUuid, + pageNodeIId, recordProp, recordUuid, }) @@ -55,7 +55,7 @@ Record.propTypes = { editable: PropTypes.bool, locked: PropTypes.bool, noHeader: PropTypes.bool, - pageNodeUuid: PropTypes.string, + pageNodeIId: PropTypes.number, record: PropTypes.object, recordUuid: PropTypes.string, } diff --git a/webapp/components/survey/Record/RecordNoHeader.js b/webapp/components/survey/Record/RecordNoHeader.js index 5f79585fff..4c8e48f8d6 100644 --- a/webapp/components/survey/Record/RecordNoHeader.js +++ b/webapp/components/survey/Record/RecordNoHeader.js @@ -1,14 +1,20 @@ import React from 'react' +import PropTypes from 'prop-types' import Record from './Record' import SurveyDefsLoader from '../SurveyDefsLoader/SurveyDefsLoader' export const RecordNoHeader = (props) => { - const { recordUuid, pageNodeUuid } = props + const { recordUuid, pageNodeIId } = props return ( - + ) } + +RecordNoHeader.propTypes = { + recordUuid: PropTypes.string.isRequired, + pageNodeIId: PropTypes.number.isRequired, +} diff --git a/webapp/components/survey/Record/store/useLocalState.js b/webapp/components/survey/Record/store/useLocalState.js index 03dc70c566..a6bb221fc0 100644 --- a/webapp/components/survey/Record/store/useLocalState.js +++ b/webapp/components/survey/Record/store/useLocalState.js @@ -18,7 +18,7 @@ export const useLocalState = (props) => { editableProp, recordProp, recordUuid: recordUuidProp, - pageNodeUuid: pageNodeUuidProp, + pageNodeIId: pageNodeIIdProp, pageNodeDefUuid: pageNodeDefUuidProp, noHeader: noHeaderProp = false, locked: lockedProp = false, @@ -30,7 +30,7 @@ export const useLocalState = (props) => { const { recordUuid: recordUuidUrlParam } = useParams() const { - pageNodeUuid: pageNodeUuidUrlParam, + pageNodeIId: pageNodeIIdUrlParam, pageNodeDefUuid: pageNodeDefUuidUrlParam, noHeader: noHeaderUrlParam, locked: lockedUrlParam, @@ -40,7 +40,7 @@ export const useLocalState = (props) => { const preview = Boolean(recordUuidPreview) const recordUuid = recordUuidProp || recordUuidUrlParam || recordUuidPreview - const pageNodeUuid = pageNodeUuidProp || pageNodeUuidUrlParam + const pageNodeIId = pageNodeIIdProp || pageNodeIIdUrlParam const pageNodeDefUuid = pageNodeDefUuidProp || pageNodeDefUuidUrlParam const noHeader = noHeaderProp || noHeaderUrlParam @@ -55,23 +55,23 @@ export const useLocalState = (props) => { // Add websocket event listeners useOnWebSocketEvent({ eventName: WebSocketEvents.nodesUpdate, - eventHandler: useCallback((content) => dispatch(RecordActions.recordNodesUpdate(content.updatedNodes)), []), + eventHandler: useCallback((content) => dispatch(RecordActions.recordNodesUpdate(content.updatedNodes)), [dispatch]), }) useOnWebSocketEvent({ eventName: WebSocketEvents.nodeValidationsUpdate, - eventHandler: useCallback((content) => dispatch(RecordActions.nodeValidationsUpdate(content)), []), + eventHandler: useCallback((content) => dispatch(RecordActions.nodeValidationsUpdate(content)), [dispatch]), }) useOnWebSocketEvent({ eventName: WebSocketEvents.nodesUpdateCompleted, - eventHandler: useCallback((content) => dispatch(RecordActions.nodesUpdateCompleted(content)), []), + eventHandler: useCallback((content) => dispatch(RecordActions.nodesUpdateCompleted(content)), [dispatch]), }) useOnWebSocketEvent({ eventName: WebSocketEvents.recordDelete, - eventHandler: useCallback(() => dispatch(RecordActions.recordDeleted(navigate)), []), + eventHandler: useCallback(() => dispatch(RecordActions.recordDeleted(navigate)), [dispatch, navigate]), }) useOnWebSocketEvent({ eventName: WebSocketEvents.recordSessionExpired, - eventHandler: useCallback(() => dispatch(RecordActions.sessionExpired(navigate)), []), + eventHandler: useCallback(() => dispatch(RecordActions.sessionExpired(navigate)), [dispatch, navigate]), }) useOnWebSocketEvent({ eventName: WebSocketEvents.applicationError, @@ -100,7 +100,7 @@ export const useLocalState = (props) => { RecordActions.checkInRecord({ recordUuid, draft, - pageNodeUuid, + pageNodeIId, pageNodeDefUuid, noHeader, locked: lockedUrlParam || lockedProp, diff --git a/webapp/components/survey/SurveyForm/FormHeader/usePath.js b/webapp/components/survey/SurveyForm/FormHeader/usePath.js index f714266c97..f12741d092 100644 --- a/webapp/components/survey/SurveyForm/FormHeader/usePath.js +++ b/webapp/components/survey/SurveyForm/FormHeader/usePath.js @@ -37,12 +37,12 @@ export const usePath = (entry) => { if (entry && record && (NodeDef.isRoot(nodeDefCurrent) || NodeDef.isMultipleEntity(nodeDefCurrent))) { // get page node const nodeDefUuidCurrent = NodeDef.getUuid(nodeDefCurrent) - const nodeUuidCurrent = pagesUuidMap[nodeDefUuidCurrent] + const nodeIIdCurrent = pagesUuidMap[nodeDefUuidCurrent] // if entry mode add node key values const nodeCurrent = NodeDef.isSingle(nodeDefCurrent) ? Record.getNodesByDefUuid(nodeDefUuidCurrent)(record)[0] - : Record.getNodeByUuid(nodeUuidCurrent)(record) + : Record.getNodeByInternalId(nodeIIdCurrent)(record) if (nodeCurrent) { const nodeDefKeys = Survey.getNodeDefKeysSorted({ nodeDef: nodeDefCurrent, cycle })(survey) diff --git a/webapp/components/survey/SurveyForm/nodeDefs/components/types/NodeDefCode/NodeDefCode.js b/webapp/components/survey/SurveyForm/nodeDefs/components/types/NodeDefCode/NodeDefCode.js index 9c7c3c682f..cdf23d5988 100644 --- a/webapp/components/survey/SurveyForm/nodeDefs/components/types/NodeDefCode/NodeDefCode.js +++ b/webapp/components/survey/SurveyForm/nodeDefs/components/types/NodeDefCode/NodeDefCode.js @@ -68,7 +68,13 @@ const NodeDefCode = (props) => { const onItemAdd = useCallback( (item) => { const existingNode = singleNode ? nodes[0] : null - const node = existingNode ?? Node.newNode(NodeDef.getUuid(nodeDef), Node.getRecordUuid(parentNode), parentNode) + const node = + existingNode ?? + Node.newNode({ + record: { uuid: Node.getRecordUuid(parentNode) }, + nodeDefUuid: NodeDef.getUuid(nodeDef), + parentNode, + }) const value = Node.newNodeValueCode({ itemUuid: CategoryItem.getUuid(item) }) const meta = { [Node.metaKeys.hierarchyCode]: codeAttributesUuidsHierarchy } diff --git a/webapp/components/survey/SurveyForm/nodeDefs/components/types/nodeDefEntityForm.js b/webapp/components/survey/SurveyForm/nodeDefs/components/types/nodeDefEntityForm.js index 6391b75af7..10f6596ce1 100644 --- a/webapp/components/survey/SurveyForm/nodeDefs/components/types/nodeDefEntityForm.js +++ b/webapp/components/survey/SurveyForm/nodeDefs/components/types/nodeDefEntityForm.js @@ -33,12 +33,12 @@ const NodeDefEntityForm = (props) => { // when there is no entity selected, select the first one useEffect(() => { if (entry && !selectedNode && !R.isEmpty(nodes)) { - const nodeUuid = R.pipe(R.head, Node.getUuid)(nodes) - if (nodeUuid) { - dispatch(SurveyFormActions.setFormPageNode(nodeDef, nodeUuid)) + const nodeIId = R.pipe(R.head, Node.getIId)(nodes) + if (nodeIId) { + dispatch(SurveyFormActions.setFormPageNode(nodeDef, nodeIId)) } } - }, [entry, selectedNode, nodes, NodeDef.getUuid(nodeDef)]) + }, [entry, selectedNode, nodes, dispatch, nodeDef]) return ( <> @@ -48,7 +48,7 @@ const NodeDefEntityForm = (props) => { dispatch(SurveyFormActions.setFormPageNode(nodeDef, selectedNodeUuid))} + onChange={(selectedNodeIId) => dispatch(SurveyFormActions.setFormPageNode(nodeDef, selectedNodeIId))} /> )} @@ -65,9 +65,9 @@ const mapStateToProps = (state, props) => { const getEntryProps = () => { const entryMultiple = NodeDef.isMultiple(nodeDef) - const selectedNodeUuid = SurveyFormState.getFormPageNodeUuid(nodeDef)(state) + const selectedNodeIId = SurveyFormState.getFormPageNodeIId(nodeDef)(state) - const selectedNode = selectedNodeUuid ? Record.getNodeByUuid(selectedNodeUuid)(record) : null + const selectedNode = selectedNodeIId ? Record.getNodeByInternalId(selectedNodeIId)(record) : null return { entryMultiple, diff --git a/webapp/components/survey/SurveyForm/nodeDefs/components/types/nodeDefEntityFormGrid.js b/webapp/components/survey/SurveyForm/nodeDefs/components/types/nodeDefEntityFormGrid.js index b1189c655f..993f35e5e0 100644 --- a/webapp/components/survey/SurveyForm/nodeDefs/components/types/nodeDefEntityFormGrid.js +++ b/webapp/components/survey/SurveyForm/nodeDefs/components/types/nodeDefEntityFormGrid.js @@ -45,7 +45,7 @@ const NodeDefEntityFormGrid = (props) => { const mountedRef = useIsMountedRef({ delay: 200 }) const surveyInfo = Survey.getSurveyInfo(survey) - const nodeUuid = Node.getUuid(node) + const nodeIId = Node.getIId(node) const nodeDefUuid = NodeDef.getUuid(nodeDef) // on node def or node change, scroll inner container to top @@ -54,7 +54,7 @@ const NodeDefEntityFormGrid = (props) => { if (innerContainer) { innerContainer.scrollTop = 0 } - }, [nodeDefUuid, nodeUuid]) + }, [nodeDefUuid, nodeIId]) const onChangeLayout = useCallback( (layout) => { diff --git a/webapp/components/survey/SurveyForm/nodeDefs/components/types/nodeDefEntityFormNodeSelect.js b/webapp/components/survey/SurveyForm/nodeDefs/components/types/nodeDefEntityFormNodeSelect.js index bd8b8f792f..5711d338b2 100644 --- a/webapp/components/survey/SurveyForm/nodeDefs/components/types/nodeDefEntityFormNodeSelect.js +++ b/webapp/components/survey/SurveyForm/nodeDefs/components/types/nodeDefEntityFormNodeSelect.js @@ -34,9 +34,13 @@ const NodeDefEntityFormNodeSelect = (props) => { testId={TestId.entities.form.addNewNode} size="small" onClick={() => { - const entity = Node.newNode(NodeDef.getUuid(nodeDef), Node.getRecordUuid(parentNode), parentNode) + const entity = Node.newNode({ + record: { uuid: Node.getRecordUuid(parentNode) }, + nodeDefUuid: NodeDef.getUuid(nodeDef), + parentNode, + }) updateNode(nodeDef, entity) - onChange(Node.getUuid(entity)) + onChange(Node.getIId(entity)) }} disabled={!canAddNode} iconClassName="icon-plus icon-10px icon-left" @@ -54,7 +58,7 @@ const NodeDefEntityFormNodeSelect = (props) => {