Skip to content

Commit

Permalink
refactor: update lookup references
Browse files Browse the repository at this point in the history
  • Loading branch information
7sete7 committed Jul 8, 2024
1 parent 7dbadae commit 8ca4f84
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 194 deletions.
4 changes: 2 additions & 2 deletions src/imports/data/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
195 changes: 39 additions & 156 deletions src/imports/konsistent/updateReferences/lookupReference.js
Original file line number Diff line number Diff line change
@@ -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})`);
}
Expand Down
51 changes: 18 additions & 33 deletions src/imports/konsistent/updateReferences/lookupReferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>}
*/
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);
Expand All @@ -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);
});
});
}
}
6 changes: 4 additions & 2 deletions src/imports/konsistent/utils.js
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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 '';
Expand Down
3 changes: 2 additions & 1 deletion src/imports/meta/buildReferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, 'type' | 'isList' | 'descriptionFields' | 'inheritedFields' | 'detailFields'> & { field: string };
type Reference = Pick<Field, 'type' | 'isList' | 'descriptionFields' | 'inheritedFields' | 'detailFields' | 'name'> & { field: string };

type References = {
[document: string]: {
Expand Down Expand Up @@ -40,6 +40,7 @@ export default function buildReferences(Meta: Record<string, MetaObjectType>) {
[fieldName]: {
type: field.type,
field: fieldName,
name: fieldName,
isList: field.isList,
descriptionFields: field.descriptionFields,
detailFields: field.detailFields,
Expand Down

0 comments on commit 8ca4f84

Please sign in to comment.