diff --git a/src/imports/data/data.js b/src/imports/data/data.js index 3db9d08..c92d78c 100644 --- a/src/imports/data/data.js +++ b/src/imports/data/data.js @@ -1116,7 +1116,7 @@ export async function update({ authTokenId, document, data, contextUser, tracing value: data.data[key], actionType: 'update', objectOriginalValues: record, - objectNewValues: data.data, + objectNewValues: bodyData, idsToUpdate: query._id.$in, }); if (lookupValidateResult.success === false) { @@ -1173,7 +1173,7 @@ export async function update({ authTokenId, document, data, contextUser, tracing value: data.data[fieldName], actionType: 'update', objectOriginalValues: record, - objectNewValues: data.data, + objectNewValues: bodyData, idsToUpdate: query._id.$in, }); if (result.success === false) { diff --git a/src/imports/konsistent/updateReferences/lookupReference.js b/src/imports/konsistent/updateReferences/lookupReference.js index 7a5694f..44b679b 100644 --- a/src/imports/konsistent/updateReferences/lookupReference.js +++ b/src/imports/konsistent/updateReferences/lookupReference.js @@ -1,183 +1,66 @@ -import compact from 'lodash/compact'; -import get from 'lodash/get'; -import has from 'lodash/has'; +import groupBy from 'lodash/groupBy'; import isArray from 'lodash/isArray'; import merge from 'lodash/merge'; import pick from 'lodash/pick'; -import uniq from 'lodash/uniq'; import { MetaObject } from '@imports/model/MetaObject'; +import { convertStringOfFieldsSeparatedByCommaIntoObjectToFind } from '@imports/utils/convertStringOfFieldsSeparatedByCommaIntoObjectToFind'; import { logger } from '@imports/utils/logger'; +import { getFieldNamesOfPaths } from '../utils'; -export default async function updateLookupReference(metaName, fieldName, field, record, relatedMetaName) { - // Try to get related meta - const meta = MetaObject.Meta[metaName]; - if (!meta) { - return logger.error(`MetaObject.Meta ${metaName} does not exists`); - } +async function getDescriptionAndInheritedFieldsToUpdate({ record, metaField, meta }) { + const fieldsToUpdate = {} - // Try to get related model - const collection = MetaObject.Collections[metaName]; - if (collection == null) { - return logger.error(`Model ${metaName} does not exists`); + if (isArray(metaField.descriptionFields) && metaField.descriptionFields.length > 0) { + const updateKey = metaField.isList ? `${metaField.name}.$` : `${metaField.name}`; + const descriptionFieldsValue = pick(record, Array.from(new Set(['_id'].concat(metaField.descriptionFields)))); + + fieldsToUpdate[updateKey] = descriptionFieldsValue; } - // Define field to query and field to update - const fieldToQuery = `${fieldName}._id`; - let fieldToUpdate = fieldName; + if (isArray(metaField.inheritedFields) && metaField.inheritedFields.length > 0) { + const inheritedFields = metaField.inheritedFields.filter(inheritedField => ['always', 'hierarchy_always'].includes(inheritedField.inherit)); + const fieldsToInherit = inheritedFields.map(inheritedField => meta.fields[inheritedField.fieldName]).filter(Boolean); - // If field is isList then use .$ into field to update - // to find in arrays and update only one item from array - if (field.isList === true) { - fieldToUpdate = `${fieldName}.$`; - } + const { true: lookupFields = [], false: nonLookupFields = [] } = groupBy(fieldsToInherit, field => field.type === 'lookup'); - // Define query with record id - const query = {}; - query[fieldToQuery] = record._id; + for (const field of nonLookupFields) { + fieldsToUpdate[field.name] = record[field.name]; + } - // Init object of data to set - const updateData = { $set: {} }; + for await (const lookupField of lookupFields) { + const keysToFind = [].concat(lookupField.descriptionFields || [], lookupField.inheritedFields || []).map(getFieldNamesOfPaths).join(); + const projection = convertStringOfFieldsSeparatedByCommaIntoObjectToFind(keysToFind); - // Add dynamic field name to update into object to update - updateData.$set[fieldToUpdate] = {}; + const Collection = MetaObject.Collections[lookupField.document]; + const lookupRecord = await Collection.findOne({ _id: record[lookupField.name]._id }, { projection }); - // If there are description fields - if (isArray(field.descriptionFields) && field.descriptionFields.length > 0) { - // Execute method to copy fields and values using an array of paths + const result = await getDescriptionAndInheritedFieldsToUpdate({ record: lookupRecord, metaField: lookupField, meta }); + merge(fieldsToUpdate, result); + } + } + + return fieldsToUpdate; +} - const descriptionFieldsValue = pick(record, Array.from(new Set(['_id'].concat(field.descriptionFields)))); - merge(updateData.$set[fieldToUpdate], descriptionFieldsValue); +export default async function updateLookupReference(metaName, fieldName, field, record, relatedMetaName) { + const meta = MetaObject.Meta[metaName]; + if (!meta) { + return logger.error(`MetaObject.Meta ${metaName} does not exists`); } - // If there are inherit fields - if (isArray(field.inheritedFields) && field.inheritedFields.length > 0) { - // For each inherited field - for (var inheritedField of field.inheritedFields) { - if (['always', 'hierarchy_always'].includes(inheritedField.inherit)) { - // Get field meta - var inheritedMetaField = meta.fields[inheritedField.fieldName]; - - if (inheritedField.inherit === 'hierarchy_always') { - if (get(inheritedMetaField, 'type') !== 'lookup' || inheritedMetaField.isList !== true) { - logger.error(`Not lookup or not isList field ${inheritedField.fieldName} in ${metaName}`); - continue; - } - if (!record[inheritedField.fieldName]) { - record[inheritedField.fieldName] = []; - } - record[inheritedField.fieldName].push({ - _id: record._id, - }); - } - - // If field is lookup - if (get(inheritedMetaField, 'type') === 'lookup') { - // Get model to find record - const lookupCollection = MetaObject.Collections[inheritedMetaField.document]; - - if (!lookupCollection) { - logger.error(`Document ${inheritedMetaField.document} not found`); - continue; - } - - if (has(record, `${inheritedField.fieldName}._id`) || (inheritedMetaField.isList === true && get(record, `${inheritedField.fieldName}.length`) > 0)) { - var lookupRecord, subQuery; - if (inheritedMetaField.isList !== true) { - subQuery = { _id: record[inheritedField.fieldName]._id.valueOf() }; - - // Find records - lookupRecord = await lookupCollection.findOne(subQuery); - - // If no record found log error - if (!lookupRecord) { - logger.error( - `Record not found for field ${inheritedField.fieldName} with _id [${subQuery._id}] on document [${inheritedMetaField.document}] not found`, - ); - continue; - } - - // Else copy description fields - if (isArray(inheritedMetaField.descriptionFields)) { - if (!updateData.$set[inheritedField.fieldName]) { - updateData.$set[inheritedField.fieldName] = {}; - } - - const descriptionFieldsValue = pick(lookupRecord, Array.from(new Set(['_id'].concat(inheritedMetaField.descriptionFields)))); - merge(updateData.$set[inheritedField.fieldName], descriptionFieldsValue); - } - - // End copy inherited values - if (isArray(inheritedMetaField.inheritedFields)) { - for (let inheritedMetaFieldItem of inheritedMetaField.inheritedFields) { - if (inheritedMetaFieldItem.inherit === 'always') { - updateData.$set[inheritedMetaFieldItem.fieldName] = lookupRecord[inheritedMetaFieldItem.fieldName]; - } - } - } - } else if (get(record, `${inheritedField.fieldName}.length`, 0) > 0) { - let ids = record[inheritedField.fieldName].map(item => item._id); - ids = compact(uniq(ids)); - subQuery = { - _id: { - $in: ids, - }, - }; - - const subOptions = {}; - if (isArray(inheritedMetaField.descriptionFields)) { - subOptions.projection = inheritedMetaField.descriptionFields.reduce((obj, item) => { - const key = item.split('.')[0]; - if (obj[key] == null) { - obj[key] = 1; - } - return obj; - }, {}); - } - - // Find records - const lookupRecords = await lookupCollection.find(subQuery, subOptions).toArray(); - const lookupRecordsById = lookupRecords.reduce((obj, item) => { - obj[item._id] = item; - return obj; - }, {}); - - record[inheritedField.fieldName].forEach(function (item) { - lookupRecord = lookupRecordsById[item._id]; - - // If no record found log error - if (!lookupRecord) { - logger.error( - `Record not found for field ${inheritedField.fieldName} with _id [${item._id}] on document [${inheritedMetaField.document}] not found`, - ); - return; - } - - // Else copy description fields - if (isArray(inheritedMetaField.descriptionFields)) { - const tempValue = pick(lookupRecord, Array.from(new Set(['_id'].concat(inheritedMetaField.descriptionFields)))); - if (updateData.$set[inheritedField.fieldName] == null) { - updateData.$set[inheritedField.fieldName] = []; - } - return updateData.$set[inheritedField.fieldName].push(tempValue); - } - }); - } - } - } else { - // Copy data into object to update if inherit method is 'always' - updateData.$set[inheritedField.fieldName] = record[inheritedField.fieldName]; - } - } - } + const collection = MetaObject.Collections[metaName]; + if (collection == null) { + return logger.error(`Model ${metaName} does not exists`); } try { - // Execute update and get affected records + const updateData = await getDescriptionAndInheritedFieldsToUpdate({ record, metaField: field, meta }); + + const query = { [`${fieldName}._id`]: record._id }; const updateResult = await collection.updateMany(query, updateData); - // If there are affected records then log if (updateResult.modifiedCount > 0) { logger.debug(`🔗 ${relatedMetaName} > ${metaName}.${fieldName} (${updateResult.modifiedCount})`); } diff --git a/src/imports/konsistent/updateReferences/lookupReferences.js b/src/imports/konsistent/updateReferences/lookupReferences.js index a35d5d5..7595454 100644 --- a/src/imports/konsistent/updateReferences/lookupReferences.js +++ b/src/imports/konsistent/updateReferences/lookupReferences.js @@ -9,50 +9,39 @@ import uniq from 'lodash/uniq'; import updateLookupReference from '@imports/konsistent/updateReferences/lookupReference'; import { MetaObject } from '@imports/model/MetaObject'; import { logger } from '@imports/utils/logger'; - +import { getFieldNamesOfPaths } from '../utils'; + +/** + * When some document changes, verify if it's a lookup in some other document. + * If it is, update description & inherited fields in all related documents. + * @param {string} metaName + * @param {string} id + * @param {object} data + * @returns {Promise} + */ export default async function updateLookupReferences(metaName, id, data) { - // Get references from meta - let field, fieldName, fields; const references = MetaObject.References[metaName]; - // Verify if exists reverse relations if (!isObject(references) || size(keys(references.from)) === 0) { return; } - // Get model const collection = MetaObject.Collections[metaName]; if (collection == null) { throw new Error(`Collection ${metaName} not found`); } - // Define object to receive only references that have reference fields in changed data const referencesToUpdate = {}; - - // Get all keys that was updated const updatedKeys = Object.keys(data); // Iterate over all relations to verify if each relation have fields in changed keys for (var referenceDocumentName in references.from) { - fields = references.from[referenceDocumentName]; - for (fieldName in fields) { - var key; - field = fields[fieldName]; - let keysToUpdate = []; - // Split each key to get only first key of array of paths - if (size(field.descriptionFields) > 0) { - for (key of field.descriptionFields) { - keysToUpdate.push(key.split('.')[0]); - } - } - - if (size(field.inheritedFields) > 0) { - for (key of field.inheritedFields) { - keysToUpdate.push(key.fieldName.split('.')[0]); - } - } + const fields = references.from[referenceDocumentName]; + for (const fieldName in fields) { + const field = fields[fieldName]; + let keysToUpdate = [].concat(field.descriptionFields || [], field.inheritedFields || []).map(getFieldNamesOfPaths); - // Remove duplicated fields, can exists because we splited paths to get only first part + // Remove duplicated fields keysToUpdate = uniq(keysToUpdate); // Get only keys that exists in references and list of updated keys keysToUpdate = intersection(keysToUpdate, updatedKeys); @@ -67,27 +56,23 @@ export default async function updateLookupReferences(metaName, id, data) { } } - // If there are 0 relations to process then abort if (Object.keys(referencesToUpdate).length === 0) { return; } - // Find record with all information, not only udpated data, to can copy all related fields const record = await collection.findOne({ _id: id }); - // If no record was found log error and abort if (!record) { return logger.error(`Can't find record ${id} from ${metaName}`); } logger.debug(`Updating references for ${metaName} - ${Object.keys(referencesToUpdate).join(", ")}`); - // Iterate over relations to process and iterate over each related field to execute a method to update relations await BluebirdPromise.mapSeries(Object.keys(referencesToUpdate), async referenceDocumentName => { - fields = referencesToUpdate[referenceDocumentName]; + const fields = referencesToUpdate[referenceDocumentName]; await BluebirdPromise.mapSeries(Object.keys(fields), async fieldName => { - field = fields[fieldName]; + const field = fields[fieldName]; return updateLookupReference(referenceDocumentName, fieldName, field, record, metaName); }); }); -} \ No newline at end of file +} diff --git a/src/imports/konsistent/utils.js b/src/imports/konsistent/utils.js index 37b5120..7a626cc 100644 --- a/src/imports/konsistent/utils.js +++ b/src/imports/konsistent/utils.js @@ -1,8 +1,8 @@ -import isArray from 'lodash/isArray'; -import isObject from 'lodash/isObject'; import each from 'lodash/each'; import get from 'lodash/get'; import has from 'lodash/has'; +import isArray from 'lodash/isArray'; +import isObject from 'lodash/isObject'; import { MetaObject } from '@imports/model/MetaObject'; @@ -41,6 +41,8 @@ export function getFirstPartOfArrayOfPaths(paths) { return paths.map(i => i.split('.')[0]); } +export const getFieldNamesOfPaths = (fieldConf) => (fieldConf.fieldName ?? fieldConf).split('.')[0]; + export function formatValue(value, field, ignoreIsList) { if (!value) { return ''; diff --git a/src/imports/meta/buildReferences.ts b/src/imports/meta/buildReferences.ts index bf0aae1..f4066b0 100644 --- a/src/imports/meta/buildReferences.ts +++ b/src/imports/meta/buildReferences.ts @@ -2,7 +2,7 @@ import { Field } from '@imports/model/Field'; import { Relation } from '@imports/model/Relation'; import { MetaObjectType } from '@imports/types/metadata'; -type Reference = Pick & { field: string }; +type Reference = Pick & { field: string }; type References = { [document: string]: { @@ -40,6 +40,7 @@ export default function buildReferences(Meta: Record) { [fieldName]: { type: field.type, field: fieldName, + name: fieldName, isList: field.isList, descriptionFields: field.descriptionFields, detailFields: field.detailFields,