From 8c365765d11f77c0b26c6883d83c389bc18c3ebb Mon Sep 17 00:00:00 2001 From: Federico Nocetti Date: Wed, 2 Jun 2021 19:23:52 -0300 Subject: [PATCH 01/27] Backup advances --- app/api/entities/entities.js | 3 +- .../entities/specs/denormalization.spec.ts | 111 ++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 app/api/entities/specs/denormalization.spec.ts diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index 748c8a5aeb..024420cdba 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -394,7 +394,8 @@ export default { async updateDenormalizedMetadataInRelatedEntities(entity) { const related = await relationships.getByDocument(entity.sharedId, entity.language); const sharedIds = related.map(r => r.entityData.sharedId); - await this.updateMetdataFromRelationships(sharedIds, entity.language); + console.log(sharedIds); + await this.updateMetdataFromRelationships(['A1'], entity.language); }, async denormalize(_doc, { user, language }) { diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts new file mode 100644 index 0000000000..c4c8e24dde --- /dev/null +++ b/app/api/entities/specs/denormalization.spec.ts @@ -0,0 +1,111 @@ +import db from 'api/utils/testing_db'; +import entities from 'api/entities'; + +import { ObjectIdSchema } from 'shared/types/commonTypes'; + +function getIdCache() { + const map = new Map(); + + return function setAndGet(key: string) { + if (!map.has(key)) map.set(key, db.id()); + + return map.get(key); + }; +} + +describe('getIdCache', () => { + it('should create a new id', () => { + const ids = getIdCache(); + + expect(ids('key')).toBeDefined(); + }); + + it('should create a different ids', () => { + const ids = getIdCache(); + + expect(ids('key1')).not.toEqual(ids('key2')); + }); + + it('should cache ids', () => { + const ids = getIdCache(); + + expect(ids('key')).toEqual(ids('key')); + }); +}); + +const load = async (data: any) => db.setupFixturesAndContext(data); + +afterAll(async () => db.disconnect()); + +describe('Denormalizing relationships', () => { + describe('When entities are of different templates', () => { + const ids = getIdCache(); + + const fixtures = { + templates: [ + { + _id: ids('A'), + name: 'A', + properties: [ + { + type: 'relationship', + name: 'relationship_property_a', + relationType: ids('rel1'), + content: ids('B'), + }, + ], + }, + { + _id: ids('B'), + name: 'B', + properties: [], + }, + ], + entities: [ + { + _id: ids('A1'), + sharedId: 'A1', + type: 'entity', + template: ids('A'), + language: 'en', + title: 'A1', + metadata: { + relationship_property_a: [{ icon: null, label: 'B1', type: 'entity', value: 'B1' }], + }, + }, + { + _id: ids('B1'), + sharedId: 'B1', + type: 'entity', + template: ids('B'), + language: 'en', + title: 'B1', + metadata: {}, + }, + ], + connections: [ + { entity: 'A1', template: null, hub: ids('hub1'), entityData: {} }, + { entity: 'B1', template: ids('rel1'), hub: ids('hub1'), entityData: {} }, + ], + settings: [{ _id: db.id(), languages: [{ key: 'en', default: true }] }], + }; + + it('should create a connection if setting relationship property', async () => { + await load(fixtures); + + await entities.save( + { + ...fixtures.entities[1], + title: 'New title', + }, + { language: 'en', user: {} }, + true + ); + + const relatedEntity = await entities.getById('A1', 'en'); + console.log(JSON.stringify(relatedEntity, null, 2)); + + expect(relatedEntity!.metadata!.relationship_property_a![0].label).toBe('New title'); + }); + }); +}); From 98afbae804efec46a95809cf79cf8be2fae57fd8 Mon Sep 17 00:00:00 2001 From: Daneryl Date: Thu, 3 Jun 2021 17:32:29 +0200 Subject: [PATCH 02/27] WIP, denormalize inherited specs --- app/api/entities/entities.js | 23 +- .../entities/specs/denormalization.spec.ts | 220 ++++++++++++++---- 2 files changed, 186 insertions(+), 57 deletions(-) diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index 024420cdba..9c5084f719 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -148,6 +148,20 @@ async function updateEntity(entity, _template, unrestricted = false) { await this.renameRelatedEntityInMetadata({ ...currentDoc, ...entity }); } + const [relatedEntity] = await model.get({ + 'metadata.relationship_property_inherited.value': entity.sharedId, + }); + if (relatedEntity) { + relatedEntity.metadata.relationship_property_inherited = relatedEntity.metadata.relationship_property_inherited.map( + prop => { + return { + ...prop, + ...(prop.value === entity.sharedId ? { inheritedValue: entity.metadata.text } : {}), + }; + } + ); + } + const toSave = { ...entity }; delete toSave.published; @@ -382,7 +396,7 @@ export default { )[0]; if (updateRelationships) { await relationships.saveEntityBasedReferences(entity, language); - await this.updateDenormalizedMetadataInRelatedEntities(entity); + // await this.updateDenormalizedMetadataInRelatedEntities(entity); } if (index) { await search.indexEntities({ sharedId }, '+fullText'); @@ -391,13 +405,6 @@ export default { return entity; }, - async updateDenormalizedMetadataInRelatedEntities(entity) { - const related = await relationships.getByDocument(entity.sharedId, entity.language); - const sharedIds = related.map(r => r.entityData.sharedId); - console.log(sharedIds); - await this.updateMetdataFromRelationships(['A1'], entity.language); - }, - async denormalize(_doc, { user, language }) { await validateEntity(_doc); const doc = _doc; diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index c4c8e24dde..c97e448ff7 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -1,4 +1,4 @@ -import db from 'api/utils/testing_db'; +import db, { DBFixture } from 'api/utils/testing_db'; import entities from 'api/entities'; import { ObjectIdSchema } from 'shared/types/commonTypes'; @@ -41,71 +41,193 @@ describe('Denormalizing relationships', () => { describe('When entities are of different templates', () => { const ids = getIdCache(); - const fixtures = { - templates: [ - { - _id: ids('A'), - name: 'A', - properties: [ - { - type: 'relationship', - name: 'relationship_property_a', - relationType: ids('rel1'), - content: ids('B'), + it('should create a connection if setting relationship property', async () => { + const fixtures: DBFixture = { + templates: [ + { + _id: ids('A'), + name: 'A', + properties: [ + { + type: 'relationship', + name: 'relationship_property_a', + relationType: ids('rel1'), + content: ids('B').toString(), + }, + ], + }, + { + _id: ids('B'), + name: 'B', + properties: [], + }, + ], + entities: [ + { + _id: ids('A1'), + sharedId: 'A1', + type: 'entity', + template: ids('A'), + language: 'en', + title: 'A1', + metadata: { + relationship_property_a: [ + { icon: null, label: 'B1', type: 'entity', value: 'B1' }, + { icon: null, label: 'B2', type: 'entity', value: 'B2' }, + ], }, - ], - }, - { - _id: ids('B'), - name: 'B', - properties: [], - }, - ], - entities: [ - { - _id: ids('A1'), - sharedId: 'A1', - type: 'entity', - template: ids('A'), - language: 'en', - title: 'A1', - metadata: { - relationship_property_a: [{ icon: null, label: 'B1', type: 'entity', value: 'B1' }], }, + { + _id: ids('B1'), + sharedId: 'B1', + type: 'entity', + template: ids('B'), + language: 'en', + title: 'B1', + metadata: {}, + }, + { + _id: ids('B2'), + sharedId: 'B2', + type: 'entity', + template: ids('B'), + language: 'en', + title: 'B2', + metadata: {}, + }, + ], + settings: [{ _id: db.id(), languages: [{ key: 'en', default: true }] }], + }; + await load(fixtures); + + await entities.save( + { + ...fixtures.entities[1], + title: 'New title', }, + { language: 'en', user: {} }, + true + ); + + await entities.save( { - _id: ids('B1'), - sharedId: 'B1', - type: 'entity', - template: ids('B'), - language: 'en', - title: 'B1', - metadata: {}, + ...fixtures.entities[2], + title: 'New title 2', }, - ], - connections: [ - { entity: 'A1', template: null, hub: ids('hub1'), entityData: {} }, - { entity: 'B1', template: ids('rel1'), hub: ids('hub1'), entityData: {} }, - ], - settings: [{ _id: db.id(), languages: [{ key: 'en', default: true }] }], - }; + { language: 'en', user: {} }, + true + ); - it('should create a connection if setting relationship property', async () => { + const relatedEntity = await entities.getById('A1', 'en'); + expect(relatedEntity!.metadata!.relationship_property_a![0].label).toBe('New title'); + expect(relatedEntity!.metadata!.relationship_property_a![1].label).toBe('New title 2'); + }); + it('should update inherited labels from related entities', async () => { + const fixtures: DBFixture = { + templates: [ + { + _id: ids('A'), + name: 'A', + properties: [ + { + type: 'relationship_inherited', + name: 'relationship_property_inherited', + relationType: ids('rel1'), + content: ids('B').toString(), + inherit: { + type: 'text', + property: ids('idB').toString(), + }, + }, + ], + }, + { + _id: ids('B'), + name: 'B', + properties: [ + { + id: ids('idB').toString(), + type: 'text', + name: 'text', + }, + ], + }, + ], + entities: [ + { + _id: ids('A1'), + sharedId: 'A1', + type: 'entity', + template: ids('A'), + language: 'en', + title: 'A1', + metadata: { + relationship_property_inherited: [ + { + icon: null, + label: 'B1', + type: 'entity', + value: 'B1', + inheritedValue: [{ value: 'text1' }], + }, + { + icon: null, + label: 'B2', + type: 'entity', + value: 'B2', + inheritedValue: [{ value: 'text2' }], + }, + ], + }, + }, + { + _id: ids('B1'), + sharedId: 'B1', + type: 'entity', + template: ids('B'), + language: 'en', + title: 'B1', + metadata: { text: [{ value: 'text1' }] }, + }, + { + _id: ids('B2'), + sharedId: 'B2', + type: 'entity', + template: ids('B'), + language: 'en', + title: 'B2', + metadata: { text: [{ value: 'text2' }] }, + }, + ], + settings: [{ _id: db.id(), languages: [{ key: 'en', default: true }] }], + }; await load(fixtures); await entities.save( { ...fixtures.entities[1], - title: 'New title', + metadata: { + text: [{ value: 'text 1 changed' }], + }, }, { language: 'en', user: {} }, true ); - const relatedEntity = await entities.getById('A1', 'en'); - console.log(JSON.stringify(relatedEntity, null, 2)); + await entities.save( + { + ...fixtures.entities[2], + metadata: { + text: [{ value: 'text 2 changed' }], + }, + }, + { language: 'en', user: {} }, + true + ); - expect(relatedEntity!.metadata!.relationship_property_a![0].label).toBe('New title'); + const relatedEntity = await entities.getById('A1', 'en'); + expect(relatedEntity!.metadata!.relationship_property_a![0].inheritedValue[0].value).toBe('text 1 changed'); + expect(relatedEntity!.metadata!.relationship_property_a![1].inheritedValue[0].value).toBe('text 2 changed'); }); }); }); From 83deb587853516e14cda407351f13a98c42bfd4c Mon Sep 17 00:00:00 2001 From: Federico Nocetti Date: Thu, 3 Jun 2021 19:06:43 -0300 Subject: [PATCH 03/27] Style changes, finished ad hoc implementation for green test --- app/api/entities/entities.js | 4 ++ .../entities/specs/denormalization.spec.ts | 38 ++++++++++--------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index 9c5084f719..d2721e35a9 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -148,6 +148,7 @@ async function updateEntity(entity, _template, unrestricted = false) { await this.renameRelatedEntityInMetadata({ ...currentDoc, ...entity }); } + //Crappy draft code starts const [relatedEntity] = await model.get({ 'metadata.relationship_property_inherited.value': entity.sharedId, }); @@ -160,8 +161,11 @@ async function updateEntity(entity, _template, unrestricted = false) { }; } ); + model.saveUnrestricted(relatedEntity); } + //Crappy draft code ends + const toSave = { ...entity }; delete toSave.published; diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index c97e448ff7..e0def5cace 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -1,47 +1,47 @@ import db, { DBFixture } from 'api/utils/testing_db'; import entities from 'api/entities'; -import { ObjectIdSchema } from 'shared/types/commonTypes'; +import { ObjectId } from 'mongodb'; -function getIdCache() { - const map = new Map(); +function getIdMapper() { + const map = new Map(); return function setAndGet(key: string) { - if (!map.has(key)) map.set(key, db.id()); + if (!map.has(key)) map.set(key, db.id() as ObjectId); - return map.get(key); + return map.get(key)!; }; } describe('getIdCache', () => { it('should create a new id', () => { - const ids = getIdCache(); + const ids = getIdMapper(); expect(ids('key')).toBeDefined(); }); it('should create a different ids', () => { - const ids = getIdCache(); + const ids = getIdMapper(); expect(ids('key1')).not.toEqual(ids('key2')); }); it('should cache ids', () => { - const ids = getIdCache(); + const ids = getIdMapper(); expect(ids('key')).toEqual(ids('key')); }); }); -const load = async (data: any) => db.setupFixturesAndContext(data); +const load = async (data: DBFixture) => db.setupFixturesAndContext(data); afterAll(async () => db.disconnect()); describe('Denormalizing relationships', () => { describe('When entities are of different templates', () => { - const ids = getIdCache(); + const ids = getIdMapper(); - it('should create a connection if setting relationship property', async () => { + it('should update the title in related entities', async () => { const fixtures: DBFixture = { templates: [ { @@ -102,7 +102,7 @@ describe('Denormalizing relationships', () => { await entities.save( { - ...fixtures.entities[1], + ...fixtures.entities![1], title: 'New title', }, { language: 'en', user: {} }, @@ -111,7 +111,7 @@ describe('Denormalizing relationships', () => { await entities.save( { - ...fixtures.entities[2], + ...fixtures.entities![2], title: 'New title 2', }, { language: 'en', user: {} }, @@ -205,7 +205,7 @@ describe('Denormalizing relationships', () => { await entities.save( { - ...fixtures.entities[1], + ...fixtures.entities![1], metadata: { text: [{ value: 'text 1 changed' }], }, @@ -216,7 +216,7 @@ describe('Denormalizing relationships', () => { await entities.save( { - ...fixtures.entities[2], + ...fixtures.entities![2], metadata: { text: [{ value: 'text 2 changed' }], }, @@ -226,8 +226,12 @@ describe('Denormalizing relationships', () => { ); const relatedEntity = await entities.getById('A1', 'en'); - expect(relatedEntity!.metadata!.relationship_property_a![0].inheritedValue[0].value).toBe('text 1 changed'); - expect(relatedEntity!.metadata!.relationship_property_a![1].inheritedValue[0].value).toBe('text 2 changed'); + expect( + relatedEntity!.metadata!.relationship_property_inherited![0].inheritedValue![0].value + ).toBe('text 1 changed'); + expect( + relatedEntity!.metadata!.relationship_property_inherited![1].inheritedValue![0].value + ).toBe('text 2 changed'); }); }); }); From d392b1d710e86222aa1a06a624bea50dd7cbcf19 Mon Sep 17 00:00:00 2001 From: Daneryl Date: Fri, 4 Jun 2021 15:56:26 +0200 Subject: [PATCH 04/27] WIP select/relationship denormalization test cases --- app/api/entities/entities.js | 88 +++- .../entities/specs/denormalization.spec.ts | 401 +++++++++++------- 2 files changed, 316 insertions(+), 173 deletions(-) diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index d2721e35a9..0680edd2ac 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -148,24 +148,6 @@ async function updateEntity(entity, _template, unrestricted = false) { await this.renameRelatedEntityInMetadata({ ...currentDoc, ...entity }); } - //Crappy draft code starts - const [relatedEntity] = await model.get({ - 'metadata.relationship_property_inherited.value': entity.sharedId, - }); - if (relatedEntity) { - relatedEntity.metadata.relationship_property_inherited = relatedEntity.metadata.relationship_property_inherited.map( - prop => { - return { - ...prop, - ...(prop.value === entity.sharedId ? { inheritedValue: entity.metadata.text } : {}), - }; - } - ); - model.saveUnrestricted(relatedEntity); - } - - //Crappy draft code ends - const toSave = { ...entity }; delete toSave.published; @@ -174,6 +156,38 @@ async function updateEntity(entity, _template, unrestricted = false) { if (entity.metadata) { toSave.metadata = await denormalizeMetadata(entity.metadata, entity, template); } + + //Crappy draft code starts + const [relatedEntity] = await model.get({ + 'metadata.relationship.value': entity.sharedId, + }); + if (relatedEntity && toSave.metadata.text) { + relatedEntity.metadata.relationship = relatedEntity.metadata.relationship.map(prop => ({ + ...prop, + ...(prop.value === toSave.sharedId ? { inheritedValue: toSave.metadata.text } : {}), + })); + model.saveUnrestricted(relatedEntity); + } + if (relatedEntity && toSave.metadata.multiselect) { + relatedEntity.metadata.relationship = relatedEntity.metadata.relationship.map(prop => ({ + ...prop, + ...(prop.value === toSave.sharedId + ? { inheritedValue: toSave.metadata.multiselect } + : {}), + })); + model.saveUnrestricted(relatedEntity); + } + if (relatedEntity && toSave.metadata.relationshipB) { + relatedEntity.metadata.relationship = relatedEntity.metadata.relationship.map(prop => ({ + ...prop, + ...(prop.value === toSave.sharedId + ? { inheritedValue: toSave.metadata.relationshipB } + : {}), + })); + model.saveUnrestricted(relatedEntity); + } + //Crappy draft code ends + if (entity.suggestedMetadata) { toSave.suggestedMetadata = await denormalizeMetadata( entity.suggestedMetadata, @@ -768,7 +782,12 @@ export default { }, /** Propagate the change of a thesaurus or related entity label to all entity metadata. */ - async renameInMetadata(valueId, changes, propertyContent, { types, restrictLanguage = null }) { + async renameInMetadata( + valueId, + changes, + propertyContent /* thesauriId*/, + { types, restrictLanguage = null } + ) { const properties = (await templates.get({ 'properties.content': propertyContent })) .reduce((m, t) => m.concat(t.properties), []) .filter(p => types.includes(p.type)) @@ -776,10 +795,41 @@ export default { p => propertyContent && p.content && propertyContent.toString() === p.content.toString() ); + const inheritedProperties = ( + await templates.get({ 'properties.inherit.property': { $in: properties.map(p => p.id) } }) + ).reduce((m, t) => m.concat(t.properties), []); + // .filter(p => types.includes(p.type)) + // .filter( + // p => propertyContent && p.content && propertyContent.toString() === p.content.toString() + // ); + if (!properties.length) { return Promise.resolve(); } + await Promise.all( + inheritedProperties.map(property => + model.updateMany( + { + language: restrictLanguage, + [`metadata.${property.name}.inheritedValue.value`]: valueId, + }, + { + $set: Object.keys(changes).reduce( + (set, prop) => ({ + ...set, + [`metadata.${property.name}.$.inheritedValue.$[valueObject].${prop}`]: changes[ + prop + ], + }), + {} + ), + }, + { arrayFilters: [{ 'valueObject.value': valueId }] } + ) + ) + ); + await Promise.all( properties.map(property => model.updateMany( diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index e0def5cace..d8ff181ce1 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -2,6 +2,9 @@ import db, { DBFixture } from 'api/utils/testing_db'; import entities from 'api/entities'; import { ObjectId } from 'mongodb'; +import { EntitySchema } from 'shared/types/entityType'; +import { PropertySchema } from 'shared/types/commonTypes'; +import thesauris from 'api/thesauri'; function getIdMapper() { const map = new Map(); @@ -33,205 +36,295 @@ describe('getIdCache', () => { }); }); -const load = async (data: DBFixture) => db.setupFixturesAndContext(data); +const load = async (data: DBFixture) => + db.setupFixturesAndContext({ + ...data, + settings: [{ _id: db.id(), languages: [{ key: 'en', default: true }] }], + translations: [{ locale: 'en', contexts: [] }], + }); afterAll(async () => db.disconnect()); -describe('Denormalizing relationships', () => { - describe('When entities are of different templates', () => { - const ids = getIdMapper(); +describe('Denormalize relationships', () => { + const ids = getIdMapper(); + + const entity = (id: string, props = {}): EntitySchema => ({ + _id: ids(id), + sharedId: id, + language: 'en', + title: id, + ...props, + }); + + const relationshipProp = (name: string, relation: string, props = {}): PropertySchema => ({ + id: ids(name).toString(), + label: name, + name, + type: 'relationship', + relationType: ids('rel1').toString(), + content: ids(relation).toString(), + ...props, + }); + + const property = ( + name: string, + type: PropertySchema['type'] = 'text', + props = {} + ): PropertySchema => ({ + id: name, + label: name, + name, + type, + ...props, + }); + + const metadataValue = (value: string) => ({ value, label: value }); - it('should update the title in related entities', async () => { + const modifyEntity = async (id: string, entityData: EntitySchema) => { + await entities.save({ ...entity(id), ...entityData }, { language: 'en', user: {} }, true); + }; + + describe('title and inherited text', () => { + it('should update title and text property on related entities denormalized properties', async () => { const fixtures: DBFixture = { templates: [ { - _id: ids('A'), - name: 'A', + _id: ids('templateA'), properties: [ - { - type: 'relationship', - name: 'relationship_property_a', - relationType: ids('rel1'), - content: ids('B').toString(), - }, + relationshipProp('relationship', 'templateB', { + inherit: { type: 'text', property: ids('text').toString() }, + }), ], }, - { - _id: ids('B'), - name: 'B', - properties: [], - }, + { _id: ids('templateB'), properties: [property('text')] }, ], entities: [ - { - _id: ids('A1'), - sharedId: 'A1', - type: 'entity', - template: ids('A'), - language: 'en', - title: 'A1', + entity('A1', { + template: ids('templateA'), metadata: { - relationship_property_a: [ - { icon: null, label: 'B1', type: 'entity', value: 'B1' }, - { icon: null, label: 'B2', type: 'entity', value: 'B2' }, - ], + relationship: [metadataValue('B1'), metadataValue('B2')], }, - }, - { - _id: ids('B1'), - sharedId: 'B1', - type: 'entity', - template: ids('B'), - language: 'en', - title: 'B1', - metadata: {}, - }, - { - _id: ids('B2'), - sharedId: 'B2', - type: 'entity', - template: ids('B'), - language: 'en', - title: 'B2', - metadata: {}, - }, + }), + entity('B1', { template: ids('templateB') }), + entity('B2', { template: ids('templateB') }), ], - settings: [{ _id: db.id(), languages: [{ key: 'en', default: true }] }], }; - await load(fixtures); - await entities.save( - { - ...fixtures.entities![1], - title: 'New title', - }, - { language: 'en', user: {} }, - true - ); - - await entities.save( - { - ...fixtures.entities![2], - title: 'New title 2', - }, - { language: 'en', user: {} }, - true - ); + await load(fixtures); + await modifyEntity('B1', { + title: 'new Title', + metadata: { text: [{ value: 'text 1 changed' }] }, + }); + await modifyEntity('B2', { + title: 'new Title 2', + metadata: { text: [{ value: 'text 2 changed' }] }, + }); const relatedEntity = await entities.getById('A1', 'en'); - expect(relatedEntity!.metadata!.relationship_property_a![0].label).toBe('New title'); - expect(relatedEntity!.metadata!.relationship_property_a![1].label).toBe('New title 2'); + expect(relatedEntity?.metadata).toEqual({ + relationship: [ + expect.objectContaining({ + label: 'new Title', + inheritedValue: [{ value: 'text 1 changed' }], + }), + expect.objectContaining({ + label: 'new Title 2', + inheritedValue: [{ value: 'text 2 changed' }], + }), + ], + }); }); - it('should update inherited labels from related entities', async () => { + }); + + describe('inherited select/multiselect (thesauri)', () => { + beforeEach(async () => { const fixtures: DBFixture = { templates: [ { - _id: ids('A'), - name: 'A', + _id: ids('templateA'), properties: [ - { - type: 'relationship_inherited', - name: 'relationship_property_inherited', - relationType: ids('rel1'), - content: ids('B').toString(), - inherit: { - type: 'text', - property: ids('idB').toString(), - }, - }, + relationshipProp('relationship', 'templateB', { + inherit: { type: 'multiselect', property: 'multiselect' }, + }), ], }, { - _id: ids('B'), - name: 'B', + _id: ids('templateB'), properties: [ - { - id: ids('idB').toString(), - type: 'text', - name: 'text', - }, + property('multiselect', 'multiselect', { + content: ids('thesauri').toString(), + }), ], }, ], - entities: [ + dictionaries: [ { - _id: ids('A1'), - sharedId: 'A1', - type: 'entity', - template: ids('A'), - language: 'en', - title: 'A1', + name: 'thesauri', + _id: ids('thesauri'), + values: [ + { _id: ids('T1'), id: 'T1', label: 'T1' }, + { _id: ids('T2'), id: 'T2', label: 'T2' }, + { _id: ids('T3'), id: 'T3', label: 'T3' }, + ], + }, + ], + entities: [ + entity('A1', { + template: ids('templateA'), metadata: { - relationship_property_inherited: [ - { - icon: null, - label: 'B1', - type: 'entity', - value: 'B1', - inheritedValue: [{ value: 'text1' }], - }, - { - icon: null, - label: 'B2', - type: 'entity', - value: 'B2', - inheritedValue: [{ value: 'text2' }], - }, - ], + relationship: [metadataValue('B1'), metadataValue('B2')], }, + }), + entity('B1', { + template: ids('templateB'), + metadata: { multiselect: [metadataValue('T1')] }, + }), + entity('B2', { template: ids('templateB') }), + ], + }; + await load(fixtures); + }); + + it('should update denormalized properties when thesauri selected changes', async () => { + await modifyEntity('B1', { + metadata: { multiselect: [{ value: 'T2' }, { value: 'T3' }] }, + }); + + await modifyEntity('B2', { + metadata: { multiselect: [{ value: 'T1' }] }, + }); + + const relatedEntity = await entities.getById('A1', 'en'); + expect(relatedEntity?.metadata).toEqual({ + relationship: [ + expect.objectContaining({ + inheritedValue: [ + { value: 'T2', label: 'T2' }, + { value: 'T3', label: 'T3' }, + ], + }), + expect.objectContaining({ + inheritedValue: [{ value: 'T1', label: 'T1' }], + }), + ], + }); + }); + + it('should update denormalized properties when thesauri label changes', async () => { + await modifyEntity('B1', { + metadata: { multiselect: [{ value: 'T2' }, { value: 'T3' }] }, + }); + await modifyEntity('B2', { + metadata: { multiselect: [{ value: 'T1' }] }, + }); + + await thesauris.save({ + name: 'thesauri', + _id: ids('thesauri'), + values: [ + { _id: ids('T1'), id: 'T1', label: 'new 1' }, + { _id: ids('T2'), id: 'T2', label: 'T2' }, + { _id: ids('T3'), id: 'T3', label: 'new 3' }, + ], + }); + + const relatedEntity = await entities.getById('A1', 'en'); + expect(relatedEntity?.metadata).toEqual({ + relationship: [ + expect.objectContaining({ + inheritedValue: [ + { value: 'T2', label: 'T2' }, + { value: 'T3', label: 'new 3' }, + ], + }), + expect.objectContaining({ + inheritedValue: [{ value: 'T1', label: 'new 1' }], + }), + ], + }); + }); + }); + + describe('inherited relationship', () => { + beforeEach(async () => { + const fixtures: DBFixture = { + templates: [ + { + _id: ids('templateA'), + properties: [ + relationshipProp('relationship', 'templateB', { + inherit: { type: 'multiselect', property: 'multiselect' }, + }), + ], }, { - _id: ids('B1'), - sharedId: 'B1', - type: 'entity', - template: ids('B'), - language: 'en', - title: 'B1', - metadata: { text: [{ value: 'text1' }] }, + _id: ids('templateB'), + properties: [relationshipProp('relationshipB', 'templateC')], }, { - _id: ids('B2'), - sharedId: 'B2', - type: 'entity', - template: ids('B'), - language: 'en', - title: 'B2', - metadata: { text: [{ value: 'text2' }] }, + _id: ids('templateC'), + properties: [], }, ], - settings: [{ _id: db.id(), languages: [{ key: 'en', default: true }] }], + entities: [ + entity('A1', { + template: ids('templateA'), + metadata: { + relationship: [metadataValue('B1'), metadataValue('B2')], + }, + }), + entity('B1', { + template: ids('templateB'), + metadata: { relationshipB: [metadataValue('T1')] }, + }), + entity('B2', { template: ids('templateB') }), + + entity('C1', { template: ids('templateC') }), + entity('C2', { template: ids('templateC') }), + ], }; await load(fixtures); + }); - await entities.save( - { - ...fixtures.entities![1], - metadata: { - text: [{ value: 'text 1 changed' }], - }, - }, - { language: 'en', user: {} }, - true - ); - - await entities.save( - { - ...fixtures.entities![2], - metadata: { - text: [{ value: 'text 2 changed' }], - }, - }, - { language: 'en', user: {} }, - true - ); + it('should update denormalized properties when relationship selected changes', async () => { + await modifyEntity('B1', { + metadata: { relationshipB: [{ value: 'C1' }] }, + }); + + await modifyEntity('B2', { + metadata: { relationshipB: [{ value: 'C2' }] }, + }); const relatedEntity = await entities.getById('A1', 'en'); - expect( - relatedEntity!.metadata!.relationship_property_inherited![0].inheritedValue![0].value - ).toBe('text 1 changed'); - expect( - relatedEntity!.metadata!.relationship_property_inherited![1].inheritedValue![0].value - ).toBe('text 2 changed'); + expect(relatedEntity?.metadata).toEqual({ + relationship: [ + expect.objectContaining({ + inheritedValue: [expect.objectContaining({ value: 'C1', label: 'C1' })], + }), + expect.objectContaining({ + inheritedValue: [expect.objectContaining({ value: 'C2', label: 'C2' })], + }), + ], + }); + }); + + it('should update denormalized properties when relationship inherited label changes', async () => { + await modifyEntity('B1', { metadata: { relationshipB: [{ value: 'C1' }] } }); + await modifyEntity('B2', { metadata: { relationshipB: [{ value: 'C2' }] } }); + await modifyEntity('C1', { title: 'new C1' }); + await modifyEntity('C2', { title: 'new C2' }); + + const relatedEntity = await entities.getById('A1', 'en'); + expect(relatedEntity?.metadata).toEqual({ + relationship: [ + expect.objectContaining({ + inheritedValue: [expect.objectContaining({ value: 'C1', label: 'new C1' })], + }), + expect.objectContaining({ + inheritedValue: [expect.objectContaining({ value: 'C2', label: 'new C2' })], + }), + ], + }); }); }); }); From 9954a796415e6483feff42a7227b31a593696b40 Mon Sep 17 00:00:00 2001 From: Federico Nocetti Date: Fri, 4 Jun 2021 18:30:29 -0300 Subject: [PATCH 05/27] Pass test for transitive inheritance --- app/api/entities/entities.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index 0680edd2ac..758e3587d3 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -186,6 +186,24 @@ async function updateEntity(entity, _template, unrestricted = false) { })); model.saveUnrestricted(relatedEntity); } + + const [inheritingEntity] = await model.get({ + 'metadata.relationship.inheritedValue.value': entity.sharedId, + }); + + if (inheritingEntity) { + inheritingEntity.metadata.relationship = inheritingEntity.metadata.relationship.map( + prop => ({ + ...prop, + inheritedValue: prop.inheritedValue.map(inhe => ({ + ...inhe, + label: inhe.value === toSave.sharedId ? toSave.title : inhe.label, + })), + }) + ); + model.saveUnrestricted(inheritingEntity); + } + //Crappy draft code ends if (entity.suggestedMetadata) { From 9ff574052e423168d089ba6cced89c6f05c6ec64 Mon Sep 17 00:00:00 2001 From: Federico Nocetti Date: Fri, 4 Jun 2021 18:31:01 -0300 Subject: [PATCH 06/27] Absctract new fixtures factory and a bit of refactor --- .../entities/specs/denormalization.spec.ts | 165 +++++------------- app/api/utils/fixturesFactory.ts | 63 +++++++ app/api/utils/specs/fixturesFactory.spec.ts | 35 ++++ 3 files changed, 142 insertions(+), 121 deletions(-) create mode 100644 app/api/utils/fixturesFactory.ts create mode 100644 app/api/utils/specs/fixturesFactory.spec.ts diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index d8ff181ce1..6b29972f8d 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -1,40 +1,9 @@ import db, { DBFixture } from 'api/utils/testing_db'; import entities from 'api/entities'; -import { ObjectId } from 'mongodb'; import { EntitySchema } from 'shared/types/entityType'; -import { PropertySchema } from 'shared/types/commonTypes'; import thesauris from 'api/thesauri'; - -function getIdMapper() { - const map = new Map(); - - return function setAndGet(key: string) { - if (!map.has(key)) map.set(key, db.id() as ObjectId); - - return map.get(key)!; - }; -} - -describe('getIdCache', () => { - it('should create a new id', () => { - const ids = getIdMapper(); - - expect(ids('key')).toBeDefined(); - }); - - it('should create a different ids', () => { - const ids = getIdMapper(); - - expect(ids('key1')).not.toEqual(ids('key2')); - }); - - it('should cache ids', () => { - const ids = getIdMapper(); - - expect(ids('key')).toEqual(ids('key')); - }); -}); +import { getFixturesFactory } from '../../utils/fixturesFactory'; const load = async (data: DBFixture) => db.setupFixturesAndContext({ @@ -46,42 +15,14 @@ const load = async (data: DBFixture) => afterAll(async () => db.disconnect()); describe('Denormalize relationships', () => { - const ids = getIdMapper(); - - const entity = (id: string, props = {}): EntitySchema => ({ - _id: ids(id), - sharedId: id, - language: 'en', - title: id, - ...props, - }); - - const relationshipProp = (name: string, relation: string, props = {}): PropertySchema => ({ - id: ids(name).toString(), - label: name, - name, - type: 'relationship', - relationType: ids('rel1').toString(), - content: ids(relation).toString(), - ...props, - }); - - const property = ( - name: string, - type: PropertySchema['type'] = 'text', - props = {} - ): PropertySchema => ({ - id: name, - label: name, - name, - type, - ...props, - }); - - const metadataValue = (value: string) => ({ value, label: value }); + const factory = getFixturesFactory(); const modifyEntity = async (id: string, entityData: EntitySchema) => { - await entities.save({ ...entity(id), ...entityData }, { language: 'en', user: {} }, true); + await entities.save( + { ...factory.entity(id), ...entityData }, + { language: 'en', user: {} }, + true + ); }; describe('title and inherited text', () => { @@ -89,24 +30,24 @@ describe('Denormalize relationships', () => { const fixtures: DBFixture = { templates: [ { - _id: ids('templateA'), + _id: factory.id('templateA'), properties: [ - relationshipProp('relationship', 'templateB', { - inherit: { type: 'text', property: ids('text').toString() }, + factory.relationshipProp('relationship', 'templateB', 'rel1', { + inherit: { type: 'text', property: factory.id('text').toString() }, }), ], }, - { _id: ids('templateB'), properties: [property('text')] }, + { _id: factory.id('templateB'), properties: [factory.property('text')] }, ], entities: [ - entity('A1', { - template: ids('templateA'), + factory.entity('A1', { + template: factory.id('templateA'), metadata: { - relationship: [metadataValue('B1'), metadataValue('B2')], + relationship: [factory.metadataValue('B1'), factory.metadataValue('B2')], }, }), - entity('B1', { template: ids('templateB') }), - entity('B2', { template: ids('templateB') }), + factory.entity('B1', { template: factory.id('templateB') }), + factory.entity('B2', { template: factory.id('templateB') }), ], }; @@ -141,45 +82,35 @@ describe('Denormalize relationships', () => { const fixtures: DBFixture = { templates: [ { - _id: ids('templateA'), + _id: factory.id('templateA'), properties: [ - relationshipProp('relationship', 'templateB', { + factory.relationshipProp('relationship', 'templateB', 'rel1', { inherit: { type: 'multiselect', property: 'multiselect' }, }), ], }, { - _id: ids('templateB'), + _id: factory.id('templateB'), properties: [ - property('multiselect', 'multiselect', { - content: ids('thesauri').toString(), + factory.property('multiselect', 'multiselect', { + content: factory.id('thesauri').toString(), }), ], }, ], - dictionaries: [ - { - name: 'thesauri', - _id: ids('thesauri'), - values: [ - { _id: ids('T1'), id: 'T1', label: 'T1' }, - { _id: ids('T2'), id: 'T2', label: 'T2' }, - { _id: ids('T3'), id: 'T3', label: 'T3' }, - ], - }, - ], + dictionaries: [factory.thesauri('thesauri', ['T1', 'T2', 'T3'])], entities: [ - entity('A1', { - template: ids('templateA'), + factory.entity('A1', { + template: factory.id('templateA'), metadata: { - relationship: [metadataValue('B1'), metadataValue('B2')], + relationship: [factory.metadataValue('B1'), factory.metadataValue('B2')], }, }), - entity('B1', { - template: ids('templateB'), - metadata: { multiselect: [metadataValue('T1')] }, + factory.entity('B1', { + template: factory.id('templateB'), + metadata: { multiselect: [factory.metadataValue('T1')] }, }), - entity('B2', { template: ids('templateB') }), + factory.entity('B2', { template: factory.id('templateB') }), ], }; await load(fixtures); @@ -218,15 +149,7 @@ describe('Denormalize relationships', () => { metadata: { multiselect: [{ value: 'T1' }] }, }); - await thesauris.save({ - name: 'thesauri', - _id: ids('thesauri'), - values: [ - { _id: ids('T1'), id: 'T1', label: 'new 1' }, - { _id: ids('T2'), id: 'T2', label: 'T2' }, - { _id: ids('T3'), id: 'T3', label: 'new 3' }, - ], - }); + await thesauris.save(factory.thesauri('thesauri', [['T1', 'new 1'], 'T2', ['T3', 'new 3']])); const relatedEntity = await entities.getById('A1', 'en'); expect(relatedEntity?.metadata).toEqual({ @@ -250,37 +173,37 @@ describe('Denormalize relationships', () => { const fixtures: DBFixture = { templates: [ { - _id: ids('templateA'), + _id: factory.id('templateA'), properties: [ - relationshipProp('relationship', 'templateB', { + factory.relationshipProp('relationship', 'templateB', 'rel1', { inherit: { type: 'multiselect', property: 'multiselect' }, }), ], }, { - _id: ids('templateB'), - properties: [relationshipProp('relationshipB', 'templateC')], + _id: factory.id('templateB'), + properties: [factory.relationshipProp('relationshipB', 'templateC', 'rel1')], }, { - _id: ids('templateC'), + _id: factory.id('templateC'), properties: [], }, ], entities: [ - entity('A1', { - template: ids('templateA'), + factory.entity('A1', { + template: factory.id('templateA'), metadata: { - relationship: [metadataValue('B1'), metadataValue('B2')], + relationship: [factory.metadataValue('B1'), factory.metadataValue('B2')], }, }), - entity('B1', { - template: ids('templateB'), - metadata: { relationshipB: [metadataValue('T1')] }, + factory.entity('B1', { + template: factory.id('templateB'), + metadata: { relationshipB: [factory.metadataValue('T1')] }, }), - entity('B2', { template: ids('templateB') }), + factory.entity('B2', { template: factory.id('templateB') }), - entity('C1', { template: ids('templateC') }), - entity('C2', { template: ids('templateC') }), + factory.entity('C1', { template: factory.id('templateC') }), + factory.entity('C2', { template: factory.id('templateC') }), ], }; await load(fixtures); diff --git a/app/api/utils/fixturesFactory.ts b/app/api/utils/fixturesFactory.ts new file mode 100644 index 0000000000..c2e3683574 --- /dev/null +++ b/app/api/utils/fixturesFactory.ts @@ -0,0 +1,63 @@ +import { ObjectId } from 'mongodb'; +import db from 'api/utils/testing_db'; +import { EntitySchema } from 'shared/types/entityType'; +import { PropertySchema } from 'shared/types/commonTypes'; + +export function getIdMapper() { + const map = new Map(); + + return function setAndGet(key: string) { + if (!map.has(key)) map.set(key, db.id() as ObjectId); + + return map.get(key)!; + }; +} + +export function getFixturesFactory() { + const idMapper = getIdMapper(); + + return Object.freeze({ + id: idMapper, + + entity: (id: string, props = {}): EntitySchema => ({ + _id: idMapper(id), + sharedId: id, + language: 'en', + title: id, + ...props, + }), + + relationshipProp: ( + name: string, + content: string, + relation: string, + props = {} + ): PropertySchema => ({ + id: idMapper(name).toString(), + label: name, + name, + type: 'relationship', + relationType: idMapper(relation).toString(), + content: idMapper(content).toString(), + ...props, + }), + + property: ( + name: string, + type: PropertySchema['type'] = 'text', + props = {} + ): PropertySchema => ({ id: name, label: name, name, type, ...props }), + + metadataValue: (value: string) => ({ value, label: value }), + + thesauri: (name: string, values: Array) => ({ + name, + _id: idMapper(name), + values: values.map(value => + typeof value === 'string' + ? { _id: idMapper(value), id: value, label: value } + : { _id: idMapper(value[0]), id: value[0], label: value[1] } + ), + }), + }); +} diff --git a/app/api/utils/specs/fixturesFactory.spec.ts b/app/api/utils/specs/fixturesFactory.spec.ts new file mode 100644 index 0000000000..c7ec3061ef --- /dev/null +++ b/app/api/utils/specs/fixturesFactory.spec.ts @@ -0,0 +1,35 @@ +import { getIdMapper, getFixturesFactory } from '../fixturesFactory'; + +describe('getIdMapper', () => { + it('should create a new id', () => { + const ids = getIdMapper(); + + expect(ids('key')).toBeDefined(); + }); + + it('should create a different ids', () => { + const ids = getIdMapper(); + + expect(ids('key1')).not.toEqual(ids('key2')); + }); + + it('should cache ids', () => { + const ids = getIdMapper(); + + expect(ids('key')).toEqual(ids('key')); + }); +}); + +describe('getFixturesFactory', () => { + it('should return different instances', () => { + expect(getFixturesFactory()).not.toBe(getFixturesFactory); + }); + + it('should map the ids correctly encapsulated in the instance', () => { + const factory1 = getFixturesFactory(); + const factory2 = getFixturesFactory(); + + expect(factory1.id('some')).toBe(factory1.id('some')); + expect(factory1.id('some')).not.toBe(factory2.id('some')); + }); +}); From cfa30f6c34b12080a14bc27e213738e237727f8b Mon Sep 17 00:00:00 2001 From: Daneryl Date: Mon, 7 Jun 2021 17:53:17 +0200 Subject: [PATCH 07/27] first abstraction denormalization cases --- app/api/entities/entities.js | 97 ++++++++++--------- .../entities/specs/denormalization.spec.ts | 50 +++++----- app/api/utils/fixturesFactory.ts | 3 +- 3 files changed, 78 insertions(+), 72 deletions(-) diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index 758e3587d3..93296a81c1 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -158,52 +158,56 @@ async function updateEntity(entity, _template, unrestricted = false) { } //Crappy draft code starts - const [relatedEntity] = await model.get({ - 'metadata.relationship.value': entity.sharedId, - }); - if (relatedEntity && toSave.metadata.text) { - relatedEntity.metadata.relationship = relatedEntity.metadata.relationship.map(prop => ({ - ...prop, - ...(prop.value === toSave.sharedId ? { inheritedValue: toSave.metadata.text } : {}), - })); - model.saveUnrestricted(relatedEntity); - } - if (relatedEntity && toSave.metadata.multiselect) { - relatedEntity.metadata.relationship = relatedEntity.metadata.relationship.map(prop => ({ - ...prop, - ...(prop.value === toSave.sharedId - ? { inheritedValue: toSave.metadata.multiselect } - : {}), - })); - model.saveUnrestricted(relatedEntity); - } - if (relatedEntity && toSave.metadata.relationshipB) { - relatedEntity.metadata.relationship = relatedEntity.metadata.relationship.map(prop => ({ - ...prop, - ...(prop.value === toSave.sharedId - ? { inheritedValue: toSave.metadata.relationshipB } - : {}), - })); - model.saveUnrestricted(relatedEntity); - } + const fullEntity = { ...currentDoc, ...toSave }; + const properties = (await templates.get({ 'properties.content': template._id.toString(), 'properties.inherit': {$exists: true} })) + .reduce((m, t) => m.concat(t.properties), []) + .filter( + p => template._id?.toString() === p.content?.toString() + ); - const [inheritingEntity] = await model.get({ - 'metadata.relationship.inheritedValue.value': entity.sharedId, - }); + const [property] = properties; + // console.log(properties); + // console.log(template.properties); + const [ toUpdateProp ] = template.properties + .filter( + p => p._id.toString() === property?.inherit?.property + ); - if (inheritingEntity) { - inheritingEntity.metadata.relationship = inheritingEntity.metadata.relationship.map( - prop => ({ - ...prop, - inheritedValue: prop.inheritedValue.map(inhe => ({ - ...inhe, - label: inhe.value === toSave.sharedId ? toSave.title : inhe.label, - })), - }) + if (toUpdateProp && fullEntity.metadata[toUpdateProp.name]) { + await this.renameInMetadata( + fullEntity.sharedId, + { + inheritedValue: fullEntity.metadata[toUpdateProp.name], + /// is this needed ? + label: fullEntity.title, + icon: fullEntity.icon, + /// ?? + }, + fullEntity.template, + { + types: [propertyTypes.select, propertyTypes.multiselect, propertyTypes.relationship], + restrictLanguage: fullEntity.language, + } ); - model.saveUnrestricted(inheritingEntity); } + + // if (toUpdateProp && fullEntity.metadata[toUpdateProp.name]) { + // await this.renameInMetadata( + // fullEntity.sharedId, + // { + // inheritedValue: fullEntity.metadata[toUpdateProp.name], + // label: fullEntity.title, + // icon: fullEntity.icon, + // }, + // fullEntity.template, + // { + // types: [propertyTypes.select, propertyTypes.multiselect, propertyTypes.relationship], + // restrictLanguage: fullEntity.language, + // } + // ); + // } + //Crappy draft code ends if (entity.suggestedMetadata) { @@ -800,12 +804,7 @@ export default { }, /** Propagate the change of a thesaurus or related entity label to all entity metadata. */ - async renameInMetadata( - valueId, - changes, - propertyContent /* thesauriId*/, - { types, restrictLanguage = null } - ) { + async renameInMetadata(valueId, changes, propertyContent, { types, restrictLanguage = null }) { const properties = (await templates.get({ 'properties.content': propertyContent })) .reduce((m, t) => m.concat(t.properties), []) .filter(p => types.includes(p.type)) @@ -813,6 +812,9 @@ export default { p => propertyContent && p.content && propertyContent.toString() === p.content.toString() ); + // console.log(JSON.stringify(await templates.get(), null, ' ')); + // console.log(JSON.stringify(properties, null, ' ')); + const inheritedProperties = ( await templates.get({ 'properties.inherit.property': { $in: properties.map(p => p.id) } }) ).reduce((m, t) => m.concat(t.properties), []); @@ -821,6 +823,7 @@ export default { // p => propertyContent && p.content && propertyContent.toString() === p.content.toString() // ); + // console.log(JSON.stringify(inheritedProperties, null, ' ')); if (!properties.length) { return Promise.resolve(); } diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index 6b29972f8d..15276d6864 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -25,7 +25,7 @@ describe('Denormalize relationships', () => { ); }; - describe('title and inherited text', () => { + describe('title and basic property (text)', () => { it('should update title and text property on related entities denormalized properties', async () => { const fixtures: DBFixture = { templates: [ @@ -35,19 +35,25 @@ describe('Denormalize relationships', () => { factory.relationshipProp('relationship', 'templateB', 'rel1', { inherit: { type: 'text', property: factory.id('text').toString() }, }), + factory.relationshipProp('relationship2', 'templateC', 'rel1', { + inherit: { type: 'text', property: factory.id('another_text').toString() }, + }), ], }, { _id: factory.id('templateB'), properties: [factory.property('text')] }, + { _id: factory.id('templateC'), properties: [factory.property('another_text')] }, ], entities: [ factory.entity('A1', { template: factory.id('templateA'), metadata: { relationship: [factory.metadataValue('B1'), factory.metadataValue('B2')], + relationship2: [factory.metadataValue('C1')], }, }), factory.entity('B1', { template: factory.id('templateB') }), factory.entity('B2', { template: factory.id('templateB') }), + factory.entity('C1', { template: factory.id('templateC') }), ], }; @@ -56,11 +62,17 @@ describe('Denormalize relationships', () => { title: 'new Title', metadata: { text: [{ value: 'text 1 changed' }] }, }); + await modifyEntity('B2', { title: 'new Title 2', metadata: { text: [{ value: 'text 2 changed' }] }, }); + await modifyEntity('C1', { + title: 'new Title C1', + metadata: { another_text: [{ value: 'another text changed' }] }, + }); + const relatedEntity = await entities.getById('A1', 'en'); expect(relatedEntity?.metadata).toEqual({ relationship: [ @@ -73,6 +85,12 @@ describe('Denormalize relationships', () => { inheritedValue: [{ value: 'text 2 changed' }], }), ], + relationship2: [ + expect.objectContaining({ + label: 'new Title C1', + inheritedValue: [{ value: 'another text changed' }], + }), + ], }); }); }); @@ -85,7 +103,7 @@ describe('Denormalize relationships', () => { _id: factory.id('templateA'), properties: [ factory.relationshipProp('relationship', 'templateB', 'rel1', { - inherit: { type: 'multiselect', property: 'multiselect' }, + inherit: { type: 'multiselect', property: factory.id('multiselect').toString() }, }), ], }, @@ -176,7 +194,7 @@ describe('Denormalize relationships', () => { _id: factory.id('templateA'), properties: [ factory.relationshipProp('relationship', 'templateB', 'rel1', { - inherit: { type: 'multiselect', property: 'multiselect' }, + inherit: { type: 'relationship', property: factory.id('relationshipB').toString() }, }), ], }, @@ -184,22 +202,14 @@ describe('Denormalize relationships', () => { _id: factory.id('templateB'), properties: [factory.relationshipProp('relationshipB', 'templateC', 'rel1')], }, - { - _id: factory.id('templateC'), - properties: [], - }, + { _id: factory.id('templateC'), properties: [] }, ], entities: [ factory.entity('A1', { template: factory.id('templateA'), - metadata: { - relationship: [factory.metadataValue('B1'), factory.metadataValue('B2')], - }, - }), - factory.entity('B1', { - template: factory.id('templateB'), - metadata: { relationshipB: [factory.metadataValue('T1')] }, + metadata: { relationship: [factory.metadataValue('B1'), factory.metadataValue('B2')] }, }), + factory.entity('B1', { template: factory.id('templateB') }), factory.entity('B2', { template: factory.id('templateB') }), factory.entity('C1', { template: factory.id('templateC') }), @@ -207,17 +217,11 @@ describe('Denormalize relationships', () => { ], }; await load(fixtures); + await modifyEntity('B1', { metadata: { relationshipB: [{ value: 'C1' }] } }); + await modifyEntity('B2', { metadata: { relationshipB: [{ value: 'C2' }] } }); }); it('should update denormalized properties when relationship selected changes', async () => { - await modifyEntity('B1', { - metadata: { relationshipB: [{ value: 'C1' }] }, - }); - - await modifyEntity('B2', { - metadata: { relationshipB: [{ value: 'C2' }] }, - }); - const relatedEntity = await entities.getById('A1', 'en'); expect(relatedEntity?.metadata).toEqual({ relationship: [ @@ -232,8 +236,6 @@ describe('Denormalize relationships', () => { }); it('should update denormalized properties when relationship inherited label changes', async () => { - await modifyEntity('B1', { metadata: { relationshipB: [{ value: 'C1' }] } }); - await modifyEntity('B2', { metadata: { relationshipB: [{ value: 'C2' }] } }); await modifyEntity('C1', { title: 'new C1' }); await modifyEntity('C2', { title: 'new C2' }); diff --git a/app/api/utils/fixturesFactory.ts b/app/api/utils/fixturesFactory.ts index c2e3683574..b4e84fc79c 100644 --- a/app/api/utils/fixturesFactory.ts +++ b/app/api/utils/fixturesFactory.ts @@ -33,6 +33,7 @@ export function getFixturesFactory() { relation: string, props = {} ): PropertySchema => ({ + _id: idMapper(name), id: idMapper(name).toString(), label: name, name, @@ -46,7 +47,7 @@ export function getFixturesFactory() { name: string, type: PropertySchema['type'] = 'text', props = {} - ): PropertySchema => ({ id: name, label: name, name, type, ...props }), + ): PropertySchema => ({ _id: idMapper(name), id: idMapper(name).toString(), label: name, name, type, ...props }), metadataValue: (value: string) => ({ value, label: value }), From 94b0121893388cdecd0efcee71304cdbe8075427 Mon Sep 17 00:00:00 2001 From: Federico Nocetti Date: Mon, 7 Jun 2021 17:28:32 -0300 Subject: [PATCH 08/27] New test case --- .../entities/specs/denormalization.spec.ts | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index 15276d6864..abde9f06ce 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -93,6 +93,96 @@ describe('Denormalize relationships', () => { ], }); }); + + fit('should update title and text property denormalized on related entities from 2 different templates', async () => { + const fixtures: DBFixture = { + templates: [ + { + _id: factory.id('templateA'), + properties: [factory.property('text')], + }, + { + _id: factory.id('templateB'), + properties: [ + factory.relationshipProp('relationship_b', 'templateA', 'rel1', { + inherit: { type: 'text', property: factory.id('text').toString() }, + }), + ], + }, + { + _id: factory.id('templateC'), + properties: [ + factory.relationshipProp('relationship_c', 'templateA', 'rel1', { + inherit: { type: 'text', property: factory.id('text').toString() }, + }), + ], + }, + ], + entities: [ + factory.entity('A1', { + template: factory.id('templateA'), + }), + factory.entity('B1', { + template: factory.id('templateB'), + metadata: { + relationship_b: [factory.metadataValue('A1')], + }, + }), + factory.entity('B2', { + template: factory.id('templateB'), + metadata: { + relationship_b: [factory.metadataValue('A1')], + }, + }), + factory.entity('C1', { + template: factory.id('templateC'), + metadata: { + relationship_c: [factory.metadataValue('A1')], + }, + }), + ], + }; + + await load(fixtures); + + await modifyEntity('A1', { + title: 'new A1', + metadata: { text: [{ value: 'text 1 changed' }] }, + }); + + const [relatedB1, relatedB2, relatedC] = [ + await entities.getById('B1', 'en'), + await entities.getById('B2', 'en'), + await entities.getById('C1', 'en'), + ]; + + expect(relatedB1?.metadata).toEqual({ + relationship_b: [ + expect.objectContaining({ + label: 'new A1', + inheritedValue: [{ value: 'text 1 changed' }], + }), + ], + }); + + expect(relatedB2?.metadata).toEqual({ + relationship_b: [ + expect.objectContaining({ + label: 'new A1', + inheritedValue: [{ value: 'text 1 changed' }], + }), + ], + }); + + expect(relatedC?.metadata).toEqual({ + relationship_c: [ + expect.objectContaining({ + label: 'new A1', + inheritedValue: [{ value: 'text 1 changed' }], + }), + ], + }); + }); }); describe('inherited select/multiselect (thesauri)', () => { From 0dc6a13ec8c83f02cec3bdd4f012e6ef1849e1e5 Mon Sep 17 00:00:00 2001 From: Federico Nocetti Date: Mon, 7 Jun 2021 17:39:17 -0300 Subject: [PATCH 09/27] Now: the actually useful test --- .../entities/specs/denormalization.spec.ts | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index abde9f06ce..7821502692 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-lines */ import db, { DBFixture } from 'api/utils/testing_db'; import entities from 'api/entities'; @@ -94,7 +95,7 @@ describe('Denormalize relationships', () => { }); }); - fit('should update title and text property denormalized on related entities from 2 different templates', async () => { + it('should update title and text property denormalized on related entities from 2 different templates', async () => { const fixtures: DBFixture = { templates: [ { @@ -183,6 +184,80 @@ describe('Denormalize relationships', () => { ], }); }); + + it('should update title and 2 differente text properties denormalized on related entities', async () => { + const fixtures: DBFixture = { + templates: [ + { + _id: factory.id('templateA'), + properties: [factory.property('text1'), factory.property('text2')], + }, + { + _id: factory.id('templateB'), + properties: [ + factory.relationshipProp('relationship_b', 'templateA', 'rel1', { + inherit: { type: 'text', property: factory.id('text1').toString() }, + }), + ], + }, + { + _id: factory.id('templateC'), + properties: [ + factory.relationshipProp('relationship_c', 'templateA', 'rel1', { + inherit: { type: 'text', property: factory.id('text2').toString() }, + }), + ], + }, + ], + entities: [ + factory.entity('A1', { + template: factory.id('templateA'), + }), + factory.entity('B1', { + template: factory.id('templateB'), + metadata: { + relationship_b: [factory.metadataValue('A1')], + }, + }), + factory.entity('C1', { + template: factory.id('templateC'), + metadata: { + relationship_c: [factory.metadataValue('A1')], + }, + }), + ], + }; + + await load(fixtures); + + await modifyEntity('A1', { + title: 'new A1', + metadata: { text1: [{ value: 'text 1 changed' }], text2: [{ value: 'text 2 changed' }] }, + }); + + const [relatedB, relatedC] = [ + await entities.getById('B1', 'en'), + await entities.getById('C1', 'en'), + ]; + + expect(relatedB?.metadata).toEqual({ + relationship_b: [ + expect.objectContaining({ + label: 'new A1', + inheritedValue: [{ value: 'text 1 changed' }], + }), + ], + }); + + expect(relatedC?.metadata).toEqual({ + relationship_c: [ + expect.objectContaining({ + label: 'new A1', + inheritedValue: [{ value: 'text 2 changed' }], + }), + ], + }); + }); }); describe('inherited select/multiselect (thesauri)', () => { From abff5813d9179a1f63faeab4ed3bb2999093be23 Mon Sep 17 00:00:00 2001 From: Daneryl Date: Tue, 8 Jun 2021 18:22:21 +0200 Subject: [PATCH 10/27] abstract update denormalization methods --- app/api/entities/denormalize.ts | 56 +++++ app/api/entities/entities.js | 237 ++++++++---------- .../entities/specs/denormalization.spec.ts | 28 ++- app/api/templates/templates.ts | 10 + 4 files changed, 186 insertions(+), 145 deletions(-) create mode 100644 app/api/entities/denormalize.ts diff --git a/app/api/entities/denormalize.ts b/app/api/entities/denormalize.ts new file mode 100644 index 0000000000..43a2244813 --- /dev/null +++ b/app/api/entities/denormalize.ts @@ -0,0 +1,56 @@ +import { EntitySchema } from 'shared/types/entityType'; +import { PropertySchema } from 'shared/types/commonTypes'; +import model from './entitiesModel'; + +interface Changes { + label: string; + icon?: EntitySchema['icon']; +} + +interface Params { + id: string; + language: string; +} + +export const updateTransitiveDenormalization = async ( + { id, language }: Params, + changes: Changes, + properties: PropertySchema[] +) => + Promise.all( + properties.map(async property => + model.updateMany( + { language, [`metadata.${property.name}.inheritedValue.value`]: id }, + { + ...(changes.icon + ? { [`metadata.${property.name}.$.inheritedValue.$[valueObject].icon`]: changes.icon } + : {}), + [`metadata.${property.name}.$.inheritedValue.$[valueObject].label`]: changes.label, + }, + { arrayFilters: [{ 'valueObject.value': id }] } + ) + ) + ); + +export const updateDenormalization = async ( + { id, language }: Params, + changes: Changes, + properties: PropertySchema[] +) => + Promise.all( + properties.map(async property => + model.updateMany( + { language, [`metadata.${property.name}.value`]: id }, + { + $set: Object.keys(changes).reduce( + (set, prop) => ({ + ...set, + [`metadata.${property.name}.$[valueObject].${prop}`]: changes[prop], + }), + {} + ), + }, + { arrayFilters: [{ 'valueObject.value': id }] } + ) + ) + ); diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index 93296a81c1..9f173d09a9 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -21,6 +21,7 @@ import { validateEntity } from 'shared/types/entitySchema'; import { deleteFiles, deleteUploadedFiles } from '../files/filesystem'; import model from './entitiesModel'; import settings from '../settings'; +import { updateTransitiveDenormalization, updateDenormalization } from './denormalize'; /** Repopulate metadata object .label from thesauri and relationships. */ async function denormalizeMetadata(metadata, entity, template, dictionariesByKey) { @@ -145,7 +146,7 @@ async function updateEntity(entity, _template, unrestricted = false) { (entity.icon && !currentDoc.icon) || (entity.icon && currentDoc.icon && currentDoc.icon._id !== entity.icon._id) ) { - await this.renameRelatedEntityInMetadata({ ...currentDoc, ...entity }); + // await this.renameRelatedEntityInMetadata({ ...currentDoc, ...entity }); } const toSave = { ...entity }; @@ -158,57 +159,52 @@ async function updateEntity(entity, _template, unrestricted = false) { } //Crappy draft code starts + + // entidad(title entidadC) <- entidadB <- entidadC; + // entidad(title thesauri) <- entidadB <- thesauri; + + // entidadB(title && inherited prop) <- entidadA; + // entidadC(title && inherited prop) <- entidadA; + + // entidad(thesauriValue) <- thesauri; + const fullEntity = { ...currentDoc, ...toSave }; - const properties = (await templates.get({ 'properties.content': template._id.toString(), 'properties.inherit': {$exists: true} })) + + const properties = ( + await templates.get({ + 'properties.content': template._id.toString(), + 'properties.inherit': { $exists: true }, + }) + ) .reduce((m, t) => m.concat(t.properties), []) - .filter( - p => template._id?.toString() === p.content?.toString() - ); + .filter(p => template._id?.toString() === p.content?.toString()); - const [property] = properties; - // console.log(properties); - // console.log(template.properties); - const [ toUpdateProp ] = template.properties - .filter( - p => p._id.toString() === property?.inherit?.property - ); + const inheritIds = properties.map(p => p.inherit?.property); + const toUpdateProps = template.properties.filter(p => + inheritIds.includes(p._id.toString()) + ); + + await updateTransitiveDenormalization( + { id: fullEntity.sharedId, language: fullEntity.language }, + { label: fullEntity.title, icon: fullEntity.icon }, + await templates.esteNombreEsUnAskoCambiar(template._id.toString()) + ); - if (toUpdateProp && fullEntity.metadata[toUpdateProp.name]) { - await this.renameInMetadata( - fullEntity.sharedId, + await toUpdateProps.reduce(async (prev, prop) => { + await prev; + return updateDenormalization( + { id: fullEntity.sharedId, language: fullEntity.language }, { - inheritedValue: fullEntity.metadata[toUpdateProp.name], - /// is this needed ? + inheritedValue: fullEntity.metadata[prop.name], label: fullEntity.title, icon: fullEntity.icon, - /// ?? }, - fullEntity.template, - { - types: [propertyTypes.select, propertyTypes.multiselect, propertyTypes.relationship], - restrictLanguage: fullEntity.language, - } + properties.filter(p => { + return prop.id === p.inherit?.property; + }) ); - } - - - // if (toUpdateProp && fullEntity.metadata[toUpdateProp.name]) { - // await this.renameInMetadata( - // fullEntity.sharedId, - // { - // inheritedValue: fullEntity.metadata[toUpdateProp.name], - // label: fullEntity.title, - // icon: fullEntity.icon, - // }, - // fullEntity.template, - // { - // types: [propertyTypes.select, propertyTypes.multiselect, propertyTypes.relationship], - // restrictLanguage: fullEntity.language, - // } - // ); - // } - - //Crappy draft code ends + }, Promise.resolve()); + ////Crappy draft code ends if (entity.suggestedMetadata) { toSave.suggestedMetadata = await denormalizeMetadata( @@ -804,104 +800,75 @@ export default { }, /** Propagate the change of a thesaurus or related entity label to all entity metadata. */ - async renameInMetadata(valueId, changes, propertyContent, { types, restrictLanguage = null }) { - const properties = (await templates.get({ 'properties.content': propertyContent })) - .reduce((m, t) => m.concat(t.properties), []) - .filter(p => types.includes(p.type)) - .filter( - p => propertyContent && p.content && propertyContent.toString() === p.content.toString() - ); - - // console.log(JSON.stringify(await templates.get(), null, ' ')); - // console.log(JSON.stringify(properties, null, ' ')); - - const inheritedProperties = ( - await templates.get({ 'properties.inherit.property': { $in: properties.map(p => p.id) } }) - ).reduce((m, t) => m.concat(t.properties), []); - // .filter(p => types.includes(p.type)) - // .filter( - // p => propertyContent && p.content && propertyContent.toString() === p.content.toString() - // ); - - // console.log(JSON.stringify(inheritedProperties, null, ' ')); - if (!properties.length) { - return Promise.resolve(); - } - - await Promise.all( - inheritedProperties.map(property => - model.updateMany( - { - language: restrictLanguage, - [`metadata.${property.name}.inheritedValue.value`]: valueId, - }, - { - $set: Object.keys(changes).reduce( - (set, prop) => ({ - ...set, - [`metadata.${property.name}.$.inheritedValue.$[valueObject].${prop}`]: changes[ - prop - ], - }), - {} - ), - }, - { arrayFilters: [{ 'valueObject.value': valueId }] } - ) - ) - ); - - await Promise.all( - properties.map(property => - model.updateMany( - { language: restrictLanguage, [`metadata.${property.name}.value`]: valueId }, - { - $set: Object.keys(changes).reduce( - (set, prop) => ({ - ...set, - [`metadata.${property.name}.$[valueObject].${prop}`]: changes[prop], - }), - {} - ), - }, - { arrayFilters: [{ 'valueObject.value': valueId }] } - ) - ) - ); - - return search.indexEntities({ - $and: [ - { - language: restrictLanguage, - }, - { - $or: properties.map(property => ({ [`metadata.${property.name}.value`]: valueId })), - }, - ], - }); - }, + // async renameInMetadata( + // valueId, + // changes, + // propertyContent, + // { types, restrictLanguage = null, props } + // ) { + // let properties = props || []; + + // if (!properties.length) { + // return Promise.resolve(); + // } + + // await Promise.all( + // properties.map(property => + // model.updateMany( + // { language: restrictLanguage, [`metadata.${property.name}.value`]: valueId }, + // { + // $set: Object.keys(changes).reduce( + // (set, prop) => ({ + // ...set, + // [`metadata.${property.name}.$[valueObject].${prop}`]: changes[prop], + // }), + // {} + // ), + // }, + // { arrayFilters: [{ 'valueObject.value': valueId }] } + // ) + // ) + // ); + + // return search.indexEntities({ + // $and: [ + // { + // language: restrictLanguage, + // }, + // { + // $or: properties.map(property => ({ [`metadata.${property.name}.value`]: valueId })), + // }, + // ], + // }); + // }, /** Propagate the change of a thesaurus label to all entity metadata. */ async renameThesaurusInMetadata(valueId, newLabel, thesaurusId, language) { - await this.renameInMetadata(valueId, { label: newLabel }, thesaurusId, { - types: [propertyTypes.select, propertyTypes.multiselect], - restrictLanguage: language, - }); - }, - - /** Propagate the title change of a related entity to all entity metadata. */ - async renameRelatedEntityInMetadata(relatedEntity) { - await this.renameInMetadata( - relatedEntity.sharedId, - { label: relatedEntity.title, icon: relatedEntity.icon }, - relatedEntity.template, - { - types: [propertyTypes.select, propertyTypes.multiselect, propertyTypes.relationship], - restrictLanguage: relatedEntity.language, - } + // await this.renameInMetadata(valueId, { label: newLabel }, thesaurusId, { + // types: [propertyTypes.select, propertyTypes.multiselect], + // restrictLanguage: language, + // }); + + await updateTransitiveDenormalization( + { id: valueId, language }, + { label: newLabel }, + await templates.esteNombreEsUnAskoCambiar(thesaurusId.toString()) ); }, + // /** Propagate the title change of a related entity to all entity metadata. */ + // async renameRelatedEntityInMetadata(relatedEntity) { + // await this.renameInMetadata( + // relatedEntity.sharedId, + // { label: relatedEntity.title, icon: relatedEntity.icon }, + // relatedEntity.template, + // { + // types: [propertyTypes.select, propertyTypes.multiselect, propertyTypes.relationship], + // restrictLanguage: relatedEntity.language, + // } + // ); + // }, + async createThumbnail(entity) { const filePath = filesystem.uploadsPath(entity.file.filename); return new PDF({ filename: filePath }).createThumbnail(entity._id.toString()); diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index 7821502692..9d1e30471b 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -210,20 +210,14 @@ describe('Denormalize relationships', () => { }, ], entities: [ - factory.entity('A1', { - template: factory.id('templateA'), - }), + factory.entity('A1', { template: factory.id('templateA') }), factory.entity('B1', { template: factory.id('templateB'), - metadata: { - relationship_b: [factory.metadataValue('A1')], - }, + metadata: { relationship_b: [factory.metadataValue('A1')] }, }), factory.entity('C1', { template: factory.id('templateC'), - metadata: { - relationship_c: [factory.metadataValue('A1')], - }, + metadata: { relationship_c: [factory.metadataValue('A1')] }, }), ], }; @@ -332,6 +326,12 @@ describe('Denormalize relationships', () => { metadata: { multiselect: [{ value: 'T1' }] }, }); + await modifyEntity('A1', { + metadata: { + relationship: [factory.metadataValue('B1'), factory.metadataValue('B2')], + }, + }); + await thesauris.save(factory.thesauri('thesauri', [['T1', 'new 1'], 'T2', ['T3', 'new 3']])); const relatedEntity = await entities.getById('A1', 'en'); @@ -372,7 +372,12 @@ describe('Denormalize relationships', () => { entities: [ factory.entity('A1', { template: factory.id('templateA'), - metadata: { relationship: [factory.metadataValue('B1'), factory.metadataValue('B2')] }, + metadata: { + relationship: [ + { value: 'B1', inheritedValue: [{ value: 'C1' }] }, + { value: 'B2', inheritedValue: [{ value: 'C2' }] }, + ], + }, }), factory.entity('B1', { template: factory.id('templateB') }), factory.entity('B2', { template: factory.id('templateB') }), @@ -384,6 +389,9 @@ describe('Denormalize relationships', () => { await load(fixtures); await modifyEntity('B1', { metadata: { relationshipB: [{ value: 'C1' }] } }); await modifyEntity('B2', { metadata: { relationshipB: [{ value: 'C2' }] } }); + // await modifyEntity('A1', { + // metadata: { relationship: [factory.metadataValue('B1'), factory.metadataValue('B2')] }, + // }); }); it('should update denormalized properties when relationship selected changes', async () => { diff --git a/app/api/templates/templates.ts b/app/api/templates/templates.ts index bc2e5724e3..bb4efd5071 100644 --- a/app/api/templates/templates.ts +++ b/app/api/templates/templates.ts @@ -67,6 +67,16 @@ const updateTranslation = async (currentTemplate: TemplateSchema, template: Temp }; export default { + async esteNombreEsUnAskoCambiar(contentId: string) { + const properties = (await model.get({ 'properties.content': contentId })) + .reduce((m, t) => m.concat(t.properties || []), []) + .filter(p => contentId === p.content?.toString()); + + return ( + await model.get({ 'properties.inherit.property': { $in: properties.map(p => p.id) } }) + ).reduce((m, t) => m.concat(t.properties || []), []); + }, + async save(template: TemplateSchema, language: string, reindex = true) { /* eslint-disable no-param-reassign */ template.properties = template.properties || []; From 4d4bdfa6b699840bfdf32a5a8e6348e0840bc583 Mon Sep 17 00:00:00 2001 From: Daneryl Date: Tue, 8 Jun 2021 19:01:01 +0200 Subject: [PATCH 11/27] fix testsuite --- app/api/csv/specs/fixtures.js | 7 + app/api/entities/entities.js | 188 ++++++++++++---------- app/api/entities/specs/entities.spec.js | 67 +------- app/api/entities/specs/fixtures.js | 69 ++++---- app/api/evidences_vault/specs/fixtures.js | 5 + 5 files changed, 154 insertions(+), 182 deletions(-) diff --git a/app/api/csv/specs/fixtures.js b/app/api/csv/specs/fixtures.js index 74e5c2117f..c630fffa32 100644 --- a/app/api/csv/specs/fixtures.js +++ b/app/api/csv/specs/fixtures.js @@ -19,37 +19,44 @@ export default { name: 'base template', properties: [ { + _id: db.id(), type: propertyTypes.text, label: 'text label', name: templateUtils.safeName('text label'), }, { + _id: db.id(), type: propertyTypes.numeric, label: 'numeric label', name: templateUtils.safeName('numeric label'), }, { + _id: db.id(), type: propertyTypes.select, label: 'select label', name: templateUtils.safeName('select label'), content: thesauri1Id, }, { + _id: db.id(), type: 'non_defined_type', label: 'not defined type', name: templateUtils.safeName('not defined type'), }, { + _id: db.id(), type: propertyTypes.text, label: 'not configured on csv', name: templateUtils.safeName('not configured on csv'), }, { + _id: db.id(), type: propertyTypes.geolocation, label: 'geolocation', name: templateUtils.safeName('geolocation_geolocation'), }, { + _id: db.id(), type: propertyTypes.generatedid, label: 'Auto ID', name: templateUtils.safeName('auto id'), diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index 9f173d09a9..de254e0e48 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -146,7 +146,7 @@ async function updateEntity(entity, _template, unrestricted = false) { (entity.icon && !currentDoc.icon) || (entity.icon && currentDoc.icon && currentDoc.icon._id !== entity.icon._id) ) { - // await this.renameRelatedEntityInMetadata({ ...currentDoc, ...entity }); + await this.renameRelatedEntityInMetadata({ ...currentDoc, ...entity }); } const toSave = { ...entity }; @@ -168,42 +168,44 @@ async function updateEntity(entity, _template, unrestricted = false) { // entidad(thesauriValue) <- thesauri; - const fullEntity = { ...currentDoc, ...toSave }; + if (template._id) { + const fullEntity = { ...currentDoc, ...toSave }; - const properties = ( - await templates.get({ - 'properties.content': template._id.toString(), - 'properties.inherit': { $exists: true }, - }) - ) - .reduce((m, t) => m.concat(t.properties), []) - .filter(p => template._id?.toString() === p.content?.toString()); - - const inheritIds = properties.map(p => p.inherit?.property); - const toUpdateProps = template.properties.filter(p => - inheritIds.includes(p._id.toString()) - ); + const properties = ( + await templates.get({ + 'properties.content': template._id.toString(), + 'properties.inherit': { $exists: true }, + }) + ) + .reduce((m, t) => m.concat(t.properties), []) + .filter(p => template._id?.toString() === p.content?.toString()); - await updateTransitiveDenormalization( - { id: fullEntity.sharedId, language: fullEntity.language }, - { label: fullEntity.title, icon: fullEntity.icon }, - await templates.esteNombreEsUnAskoCambiar(template._id.toString()) - ); + const inheritIds = properties.map(p => p.inherit?.property); + const toUpdateProps = template.properties.filter(p => + inheritIds.includes(p._id.toString()) + ); - await toUpdateProps.reduce(async (prev, prop) => { - await prev; - return updateDenormalization( + await updateTransitiveDenormalization( { id: fullEntity.sharedId, language: fullEntity.language }, - { - inheritedValue: fullEntity.metadata[prop.name], - label: fullEntity.title, - icon: fullEntity.icon, - }, - properties.filter(p => { - return prop.id === p.inherit?.property; - }) + { label: fullEntity.title, icon: fullEntity.icon }, + await templates.esteNombreEsUnAskoCambiar(template._id.toString()) ); - }, Promise.resolve()); + + await toUpdateProps.reduce(async (prev, prop) => { + await prev; + return updateDenormalization( + { id: fullEntity.sharedId, language: fullEntity.language }, + { + inheritedValue: fullEntity.metadata[prop.name], + label: fullEntity.title, + icon: fullEntity.icon, + }, + properties.filter(p => { + return prop.id === p.inherit?.property; + }) + ); + }, Promise.resolve()); + } ////Crappy draft code ends if (entity.suggestedMetadata) { @@ -800,54 +802,64 @@ export default { }, /** Propagate the change of a thesaurus or related entity label to all entity metadata. */ - // async renameInMetadata( - // valueId, - // changes, - // propertyContent, - // { types, restrictLanguage = null, props } - // ) { - // let properties = props || []; - - // if (!properties.length) { - // return Promise.resolve(); - // } - - // await Promise.all( - // properties.map(property => - // model.updateMany( - // { language: restrictLanguage, [`metadata.${property.name}.value`]: valueId }, - // { - // $set: Object.keys(changes).reduce( - // (set, prop) => ({ - // ...set, - // [`metadata.${property.name}.$[valueObject].${prop}`]: changes[prop], - // }), - // {} - // ), - // }, - // { arrayFilters: [{ 'valueObject.value': valueId }] } - // ) - // ) - // ); - - // return search.indexEntities({ - // $and: [ - // { - // language: restrictLanguage, - // }, - // { - // $or: properties.map(property => ({ [`metadata.${property.name}.value`]: valueId })), - // }, - // ], - // }); - // }, + async renameInMetadata( + valueId, + changes, + propertyContent, + { types, restrictLanguage = null, props } + ) { + let properties = props || []; + + if (!properties.length) { + return Promise.resolve(); + } + + await Promise.all( + properties.map(property => + model.updateMany( + { language: restrictLanguage, [`metadata.${property.name}.value`]: valueId }, + { + $set: Object.keys(changes).reduce( + (set, prop) => ({ + ...set, + [`metadata.${property.name}.$[valueObject].${prop}`]: changes[prop], + }), + {} + ), + }, + { arrayFilters: [{ 'valueObject.value': valueId }] } + ) + ) + ); + + return search.indexEntities({ + $and: [ + { + language: restrictLanguage, + }, + { + $or: properties.map(property => ({ [`metadata.${property.name}.value`]: valueId })), + }, + ], + }); + }, /** Propagate the change of a thesaurus label to all entity metadata. */ async renameThesaurusInMetadata(valueId, newLabel, thesaurusId, language) { - // await this.renameInMetadata(valueId, { label: newLabel }, thesaurusId, { - // types: [propertyTypes.select, propertyTypes.multiselect], - // restrictLanguage: language, - // }); + + const properties = ( + await templates.get({ + 'properties.content': thesaurusId, + }) + ) + .reduce((m, t) => m.concat(t.properties), []) + .filter(p => thesaurusId === p.content?.toString()); + + await updateDenormalization( + { id: valueId, language }, + { label: newLabel }, + properties + ); await updateTransitiveDenormalization( { id: valueId, language }, @@ -857,17 +869,17 @@ export default { }, // /** Propagate the title change of a related entity to all entity metadata. */ - // async renameRelatedEntityInMetadata(relatedEntity) { - // await this.renameInMetadata( - // relatedEntity.sharedId, - // { label: relatedEntity.title, icon: relatedEntity.icon }, - // relatedEntity.template, - // { - // types: [propertyTypes.select, propertyTypes.multiselect, propertyTypes.relationship], - // restrictLanguage: relatedEntity.language, - // } - // ); - // }, + async renameRelatedEntityInMetadata(relatedEntity) { + await this.renameInMetadata( + relatedEntity.sharedId, + { label: relatedEntity.title, icon: relatedEntity.icon }, + relatedEntity.template, + { + types: [propertyTypes.select, propertyTypes.multiselect, propertyTypes.relationship], + restrictLanguage: relatedEntity.language, + } + ); + }, async createThumbnail(entity) { const filePath = filesystem.uploadsPath(entity.file.filename); diff --git a/app/api/entities/specs/entities.spec.js b/app/api/entities/specs/entities.spec.js index 95722d8a4d..debc8a0956 100644 --- a/app/api/entities/specs/entities.spec.js +++ b/app/api/entities/specs/entities.spec.js @@ -252,73 +252,8 @@ describe('entities', () => { }); }); - describe('when icon changes', () => { - it('should update icon on entities with the entity as relationship', async () => { - const doc = { - _id: shared2, - sharedId: 'shared2', - icon: { - _id: 'changedIcon', - }, - }; - - await entities.save(doc, { language: 'en' }); - let relatedEntity = await entities.getById('shared', 'en'); - expect(relatedEntity.metadata.enemies[0].icon._id).toBe('changedIcon'); - - relatedEntity = await entities.getById('other', 'en'); - expect(relatedEntity.metadata.enemies[1].icon._id).toBe('changedIcon'); - expect(relatedEntity.metadata.enemies[0].icon).toBe(null); - expect(relatedEntity.metadata.enemies[2].icon).toBe(null); - - const updatedDoc = { - _id: shared2, - sharedId: 'shared2', - icon: { - _id: 'changedIconAgain', - }, - }; - - await entities.save(updatedDoc, { language: 'en' }); - relatedEntity = await entities.getById('shared', 'en'); - expect(relatedEntity.metadata.enemies[0].icon._id).toBe('changedIconAgain'); - }); - }); - describe('when title changes', () => { - it('should update title on entities with the entity as relationship', async () => { - const doc = { - _id: shared2, - sharedId: 'shared2', - title: 'changedTitle', - }; - - await entities.save(doc, { language: 'en' }); - let relatedEntity = await entities.getById('shared', 'en'); - expect(relatedEntity.metadata.enemies[0].label).toBe('changedTitle'); - - relatedEntity = await entities.getById('other', 'en'); - expect(relatedEntity.metadata.enemies[1].label).toBe('changedTitle'); - expect(relatedEntity.metadata.enemies[0].label).toBe('shouldNotChange'); - expect(relatedEntity.metadata.enemies[2].label).toBe('shouldNotChange1'); - }); - - it('should not change related labels on other languages', async () => { - const doc = { - _id: shared2, - sharedId: 'shared2', - title: 'changedTitle', - }; - - await entities.save(doc, { language: 'en' }); - - const relatedEntity = await entities.getById('other', 'es'); - - expect(relatedEntity.metadata.enemies[0].label).toBe('translated1'); - expect(relatedEntity.metadata.enemies[1].label).toBe('translated2'); - }); - - it('should index entities changed after propagating label change', async () => { + xit('should index entities changed after propagating label change', async () => { const doc = { _id: shared2, sharedId: 'shared2', diff --git a/app/api/entities/specs/fixtures.js b/app/api/entities/specs/fixtures.js index 59c350765a..8776ba0562 100644 --- a/app/api/entities/specs/fixtures.js +++ b/app/api/entities/specs/fixtures.js @@ -390,22 +390,24 @@ export default { _id: templateId, name: 'template_test', properties: [ - { type: 'text', name: 'text' }, + { _id: db.id(), type: 'text', name: 'text' }, { _id: inheritedProperty, type: 'text', name: 'property1' }, - { type: 'text', name: 'property2' }, - { type: 'text', name: 'description' }, - { type: 'select', name: 'select', content: dictionary }, - { type: 'multiselect', name: 'multiselect', content: dictionary }, - { type: 'date', name: 'date' }, - { type: 'multidate', name: 'multidate' }, - { type: 'multidaterange', name: 'multidaterange' }, - { type: 'daterange', name: 'daterange' }, + { _id: db.id(), type: 'text', name: 'property2' }, + { _id: db.id(), type: 'text', name: 'description' }, + { _id: db.id(), type: 'select', name: 'select', content: dictionary }, + { _id: db.id(), type: 'multiselect', name: 'multiselect', content: dictionary }, + { _id: db.id(), type: 'date', name: 'date' }, + { _id: db.id(), type: 'multidate', name: 'multidate' }, + { _id: db.id(), type: 'multidaterange', name: 'multidaterange' }, + { _id: db.id(), type: 'daterange', name: 'daterange' }, { + _id: db.id(), type: 'relationship', name: 'friends', relationType: relationType1, }, { + _id: db.id(), type: 'relationship', name: 'enemies', relationType: relationType4, @@ -413,8 +415,8 @@ export default { inherit: true, inheritProperty: inheritedProperty, }, - { type: 'nested', name: 'field_nested' }, - { type: 'numeric', name: 'numeric' }, + { _id: db.id(), type: 'nested', name: 'field_nested' }, + { _id: db.id(), type: 'numeric', name: 'numeric' }, ], }, { @@ -422,6 +424,7 @@ export default { name: 'templateWithOnlyMultiSelectSelect', properties: [ { + _id: db.id(), type: 'relationship', name: 'multiselect', content: templateWithEntityAsThesauri.toString(), @@ -432,23 +435,33 @@ export default { _id: templateWithOnlySelect, name: 'templateWithOnlySelect', properties: [ - { type: 'relationship', name: 'select', content: templateChangingNames.toString() }, + { + _id: db.id(), + type: 'relationship', + name: 'select', + content: templateChangingNames.toString(), + }, ], }, { _id: templateWithEntityAsThesauri, name: 'template_with_thesauri_as_template', properties: [ - { type: 'relationship', name: 'select', content: templateId.toString() }, - { type: 'relationship', name: 'multiselect', content: templateId.toString() }, + { _id: db.id(), type: 'relationship', name: 'select', content: templateId.toString() }, + { _id: db.id(), type: 'relationship', name: 'multiselect', content: templateId.toString() }, ], }, { _id: templateWithEntityAsThesauri2, name: 'template_with_thesauri_as_template', properties: [ - { type: 'relationship', name: 'select2', content: templateId.toString() }, - { type: 'relationship', name: 'multiselect2', content: templateId.toString() }, + { _id: db.id(), type: 'relationship', name: 'select2', content: templateId.toString() }, + { + _id: db.id(), + type: 'relationship', + name: 'multiselect2', + content: templateId.toString(), + }, ], }, { @@ -456,23 +469,23 @@ export default { name: 'template_changing_names', default: true, properties: [ - { id: '1', type: 'text', name: 'property1' }, - { id: '2', type: 'text', name: 'property2' }, - { id: '3', type: 'text', name: 'property3' }, + { _id: db.id(), id: '1', type: 'text', name: 'property1' }, + { _id: db.id(), id: '2', type: 'text', name: 'property2' }, + { _id: db.id(), id: '3', type: 'text', name: 'property3' }, ], }, ], connections: [ { _id: referenceId, entity: 'shared', template: null, hub: hub1, entityData: {} }, - { entity: 'shared2', template: relationType1, hub: hub1, entityData: {} }, - { entity: 'shared', template: null, hub: hub2, entityData: {} }, - { entity: 'source2', template: relationType2, hub: hub2, entityData: {} }, - { entity: 'another', template: relationType3, hub: hub3, entityData: {} }, - { entity: 'document', template: relationType3, hub: hub3, entityData: {} }, - { entity: 'shared', template: relationType2, hub: hub4, entityData: {} }, - { entity: 'shared1', template: relationType2, hub: hub4, entityData: {} }, - { entity: 'shared1', template: relationType2, hub: hub5, entityData: {} }, - { entity: 'shared', template: relationType2, hub: hub5, entityData: {} }, + { _id: db.id(), entity: 'shared2', template: relationType1, hub: hub1, entityData: {} }, + { _id: db.id(), entity: 'shared', template: null, hub: hub2, entityData: {} }, + { _id: db.id(), entity: 'source2', template: relationType2, hub: hub2, entityData: {} }, + { _id: db.id(), entity: 'another', template: relationType3, hub: hub3, entityData: {} }, + { _id: db.id(), entity: 'document', template: relationType3, hub: hub3, entityData: {} }, + { _id: db.id(), entity: 'shared', template: relationType2, hub: hub4, entityData: {} }, + { _id: db.id(), entity: 'shared1', template: relationType2, hub: hub4, entityData: {} }, + { _id: db.id(), entity: 'shared1', template: relationType2, hub: hub5, entityData: {} }, + { _id: db.id(), entity: 'shared', template: relationType2, hub: hub5, entityData: {} }, ], dictionaries: [ { diff --git a/app/api/evidences_vault/specs/fixtures.js b/app/api/evidences_vault/specs/fixtures.js index 67cbe3ef84..16ada31dc9 100644 --- a/app/api/evidences_vault/specs/fixtures.js +++ b/app/api/evidences_vault/specs/fixtures.js @@ -12,26 +12,31 @@ export default { name: 'template', properties: [ { + _id: db.id(), type: propertyTypes.media, label: 'video', name: templateUtils.safeName('video'), }, { + _id: db.id(), type: propertyTypes.link, label: 'original url', name: templateUtils.safeName('original url'), }, { + _id: db.id(), type: propertyTypes.image, label: 'screenshot', name: templateUtils.safeName('screenshot'), }, { + _id: db.id(), type: propertyTypes.date, label: 'time of request', name: templateUtils.safeName('time of request'), }, { + _id: db.id(), type: propertyTypes.markdown, label: 'data', name: templateUtils.safeName('data'), From e159edbdc6c845512bfd3593b9ecbdf01ad85ac0 Mon Sep 17 00:00:00 2001 From: Federico Nocetti Date: Tue, 8 Jun 2021 19:05:28 -0300 Subject: [PATCH 12/27] tests for icon and language --- app/api/entities/denormalize.ts | 2 +- .../entities/specs/denormalization.spec.ts | 112 ++++++++++++++++-- app/api/utils/fixturesFactory.ts | 17 ++- 3 files changed, 118 insertions(+), 13 deletions(-) diff --git a/app/api/entities/denormalize.ts b/app/api/entities/denormalize.ts index 43a2244813..33b9b2ced9 100644 --- a/app/api/entities/denormalize.ts +++ b/app/api/entities/denormalize.ts @@ -45,7 +45,7 @@ export const updateDenormalization = async ( $set: Object.keys(changes).reduce( (set, prop) => ({ ...set, - [`metadata.${property.name}.$[valueObject].${prop}`]: changes[prop], + [`metadata.${property.name}.$[valueObject].${prop}`]: changes[prop], }), {} ), diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index 9d1e30471b..af490dc2fc 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -9,8 +9,11 @@ import { getFixturesFactory } from '../../utils/fixturesFactory'; const load = async (data: DBFixture) => db.setupFixturesAndContext({ ...data, - settings: [{ _id: db.id(), languages: [{ key: 'en', default: true }] }], - translations: [{ locale: 'en', contexts: [] }], + settings: [{ _id: db.id(), languages: [{ key: 'en', default: true }, { key: 'es' }] }], + translations: [ + { locale: 'en', contexts: [] }, + { locale: 'es', contexts: [] }, + ], }); afterAll(async () => db.disconnect()); @@ -18,16 +21,16 @@ afterAll(async () => db.disconnect()); describe('Denormalize relationships', () => { const factory = getFixturesFactory(); - const modifyEntity = async (id: string, entityData: EntitySchema) => { + const modifyEntity = async (id: string, entityData: EntitySchema, language?: string) => { await entities.save( - { ...factory.entity(id), ...entityData }, - { language: 'en', user: {} }, + { ...factory.entity(id, entityData, language) }, + { language: language || 'en', user: {} }, true ); }; describe('title and basic property (text)', () => { - it('should update title and text property on related entities denormalized properties', async () => { + it('should update title, icon and text property on related entities denormalized properties', async () => { const fixtures: DBFixture = { templates: [ { @@ -52,7 +55,10 @@ describe('Denormalize relationships', () => { relationship2: [factory.metadataValue('C1')], }, }), - factory.entity('B1', { template: factory.id('templateB') }), + factory.entity('B1', { + template: factory.id('templateB'), + icon: { _id: 'icon_id', label: 'icon_label', type: 'icon_type' }, + }), factory.entity('B2', { template: factory.id('templateB') }), factory.entity('C1', { template: factory.id('templateC') }), ], @@ -79,6 +85,7 @@ describe('Denormalize relationships', () => { relationship: [ expect.objectContaining({ label: 'new Title', + icon: { _id: 'icon_id', label: 'icon_label', type: 'icon_type' }, inheritedValue: [{ value: 'text 1 changed' }], }), expect.objectContaining({ @@ -425,4 +432,95 @@ describe('Denormalize relationships', () => { }); }); }); + + describe('languages', () => { + it('should denormalize the title and a simple property in the correct language', async () => { + await load({ + templates: [ + { + _id: factory.id('templateA'), + properties: [ + factory.relationshipProp('relationship', 'templateB', 'rel1', { + inherit: { type: 'text', property: factory.id('text').toString() }, + }), + ], + }, + { + _id: factory.id('templateB'), + properties: [factory.property('text')], + }, + ], + entities: [ + factory.entity('A1', { + template: factory.id('templateA'), + metadata: { + relationship: [factory.metadataValue('B1')], + }, + }), + factory.entity( + 'A1', + { + template: factory.id('templateA'), + metadata: { + relationship: [factory.metadataValue('B1')], + }, + }, + 'es' + ), + factory.entity('B1', { + template: factory.id('templateB'), + }), + factory.entity( + 'B1', + { + sharedId: 'B1', + language: 'es', + template: factory.id('templateB'), + }, + 'es' + ), + ], + }); + + await modifyEntity('B1', { metadata: { text: [{ value: 'text' }] } }); + await modifyEntity('B1', { metadata: { text: [{ value: 'texto' }] } }, 'es'); + + await modifyEntity( + 'B1', + { + metadata: { text: [{ value: 'nuevo texto para ES' }] }, + }, + 'es' + ); + + const relatedEn = await entities.getById('A1', 'en'); + const relatedEs = await entities.getById('A1', 'es'); + + expect(relatedEn?.metadata).toEqual({ + relationship: [ + expect.objectContaining({ + value: 'B1', + inheritedValue: [ + { + value: 'text', + }, + ], + }), + ], + }); + + expect(relatedEs?.metadata).toEqual({ + relationship: [ + expect.objectContaining({ + value: 'B1', + inheritedValue: [ + { + value: 'nuevo texto para ES', + }, + ], + }), + ], + }); + }); + }); }); diff --git a/app/api/utils/fixturesFactory.ts b/app/api/utils/fixturesFactory.ts index b4e84fc79c..0d091ad047 100644 --- a/app/api/utils/fixturesFactory.ts +++ b/app/api/utils/fixturesFactory.ts @@ -19,11 +19,11 @@ export function getFixturesFactory() { return Object.freeze({ id: idMapper, - entity: (id: string, props = {}): EntitySchema => ({ - _id: idMapper(id), + entity: (id: string, props = {}, language?: string): EntitySchema => ({ + _id: idMapper(language ? `${id}-${language}` : id), sharedId: id, - language: 'en', - title: id, + language: language || 'en', + title: language ? `${id}-${language}` : id, ...props, }), @@ -47,7 +47,14 @@ export function getFixturesFactory() { name: string, type: PropertySchema['type'] = 'text', props = {} - ): PropertySchema => ({ _id: idMapper(name), id: idMapper(name).toString(), label: name, name, type, ...props }), + ): PropertySchema => ({ + _id: idMapper(name), + id: idMapper(name).toString(), + label: name, + name, + type, + ...props, + }), metadataValue: (value: string) => ({ value, label: value }), From 7eb34dbfdbc392cea0d96f7de1f72766f9d3b555 Mon Sep 17 00:00:00 2001 From: Daneryl Date: Wed, 9 Jun 2021 10:40:09 +0200 Subject: [PATCH 13/27] some refactors --- app/api/entities/entities.js | 69 +--- .../entities/specs/denormalization.spec.ts | 317 +++++------------- app/api/templates/templates.ts | 6 +- app/api/utils/fixturesFactory.ts | 36 +- 4 files changed, 117 insertions(+), 311 deletions(-) diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index de254e0e48..b80b0cdadb 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -141,16 +141,7 @@ async function updateEntity(entity, _template, unrestricted = false) { return Promise.all( docLanguages.map(async d => { if (d._id.toString() === entity._id.toString()) { - if ( - (entity.title && currentDoc.title !== entity.title) || - (entity.icon && !currentDoc.icon) || - (entity.icon && currentDoc.icon && currentDoc.icon._id !== entity.icon._id) - ) { - await this.renameRelatedEntityInMetadata({ ...currentDoc, ...entity }); - } - const toSave = { ...entity }; - delete toSave.published; delete toSave.permissions; @@ -201,10 +192,11 @@ async function updateEntity(entity, _template, unrestricted = false) { icon: fullEntity.icon, }, properties.filter(p => { - return prop.id === p.inherit?.property; + return prop._id.toString() === p.inherit?.property; }) ); }, Promise.resolve()); + } ////Crappy draft code ends @@ -801,52 +793,8 @@ export default { ]); }, - /** Propagate the change of a thesaurus or related entity label to all entity metadata. */ - async renameInMetadata( - valueId, - changes, - propertyContent, - { types, restrictLanguage = null, props } - ) { - let properties = props || []; - - if (!properties.length) { - return Promise.resolve(); - } - - await Promise.all( - properties.map(property => - model.updateMany( - { language: restrictLanguage, [`metadata.${property.name}.value`]: valueId }, - { - $set: Object.keys(changes).reduce( - (set, prop) => ({ - ...set, - [`metadata.${property.name}.$[valueObject].${prop}`]: changes[prop], - }), - {} - ), - }, - { arrayFilters: [{ 'valueObject.value': valueId }] } - ) - ) - ); - - return search.indexEntities({ - $and: [ - { - language: restrictLanguage, - }, - { - $or: properties.map(property => ({ [`metadata.${property.name}.value`]: valueId })), - }, - ], - }); - }, - /** Propagate the change of a thesaurus label to all entity metadata. */ async renameThesaurusInMetadata(valueId, newLabel, thesaurusId, language) { - const properties = ( await templates.get({ 'properties.content': thesaurusId, @@ -868,19 +816,6 @@ export default { ); }, - // /** Propagate the title change of a related entity to all entity metadata. */ - async renameRelatedEntityInMetadata(relatedEntity) { - await this.renameInMetadata( - relatedEntity.sharedId, - { label: relatedEntity.title, icon: relatedEntity.icon }, - relatedEntity.template, - { - types: [propertyTypes.select, propertyTypes.multiselect, propertyTypes.relationship], - restrictLanguage: relatedEntity.language, - } - ); - }, - async createThumbnail(entity) { const filePath = filesystem.uploadsPath(entity.file.filename); return new PDF({ filename: filePath }).createThumbnail(entity._id.toString()); diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index af490dc2fc..9a7a54d217 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -16,8 +16,6 @@ const load = async (data: DBFixture) => ], }); -afterAll(async () => db.disconnect()); - describe('Denormalize relationships', () => { const factory = getFixturesFactory(); @@ -29,23 +27,18 @@ describe('Denormalize relationships', () => { ); }; + afterAll(async () => db.disconnect()); + describe('title and basic property (text)', () => { it('should update title, icon and text property on related entities denormalized properties', async () => { const fixtures: DBFixture = { templates: [ - { - _id: factory.id('templateA'), - properties: [ - factory.relationshipProp('relationship', 'templateB', 'rel1', { - inherit: { type: 'text', property: factory.id('text').toString() }, - }), - factory.relationshipProp('relationship2', 'templateC', 'rel1', { - inherit: { type: 'text', property: factory.id('another_text').toString() }, - }), - ], - }, - { _id: factory.id('templateB'), properties: [factory.property('text')] }, - { _id: factory.id('templateC'), properties: [factory.property('another_text')] }, + factory.template('templateA', [ + factory.inherit('relationship', 'templateB', 'text'), + factory.inherit('relationship2', 'templateC', 'another_text'), + ]), + factory.template('templateB', [factory.property('text')]), + factory.template('templateC', [factory.property('another_text')]), ], entities: [ factory.entity('A1', { @@ -81,23 +74,23 @@ describe('Denormalize relationships', () => { }); const relatedEntity = await entities.getById('A1', 'en'); - expect(relatedEntity?.metadata).toEqual({ + expect(relatedEntity?.metadata).toMatchObject({ relationship: [ - expect.objectContaining({ + { label: 'new Title', icon: { _id: 'icon_id', label: 'icon_label', type: 'icon_type' }, inheritedValue: [{ value: 'text 1 changed' }], - }), - expect.objectContaining({ + }, + { label: 'new Title 2', inheritedValue: [{ value: 'text 2 changed' }], - }), + }, ], relationship2: [ - expect.objectContaining({ + { label: 'new Title C1', inheritedValue: [{ value: 'another text changed' }], - }), + }, ], }); }); @@ -105,31 +98,12 @@ describe('Denormalize relationships', () => { it('should update title and text property denormalized on related entities from 2 different templates', async () => { const fixtures: DBFixture = { templates: [ - { - _id: factory.id('templateA'), - properties: [factory.property('text')], - }, - { - _id: factory.id('templateB'), - properties: [ - factory.relationshipProp('relationship_b', 'templateA', 'rel1', { - inherit: { type: 'text', property: factory.id('text').toString() }, - }), - ], - }, - { - _id: factory.id('templateC'), - properties: [ - factory.relationshipProp('relationship_c', 'templateA', 'rel1', { - inherit: { type: 'text', property: factory.id('text').toString() }, - }), - ], - }, + factory.template('templateA', [factory.property('text')]), + factory.template('templateB', [factory.inherit('relationship_b', 'templateA', 'text')]), + factory.template('templateC', [factory.inherit('relationship_c', 'templateA', 'text')]), ], entities: [ - factory.entity('A1', { - template: factory.id('templateA'), - }), + factory.entity('A1', { template: factory.id('templateA') }), factory.entity('B1', { template: factory.id('templateB'), metadata: { @@ -164,57 +138,25 @@ describe('Denormalize relationships', () => { await entities.getById('C1', 'en'), ]; - expect(relatedB1?.metadata).toEqual({ - relationship_b: [ - expect.objectContaining({ - label: 'new A1', - inheritedValue: [{ value: 'text 1 changed' }], - }), - ], - }); + expect(relatedB1?.metadata?.relationship_b).toMatchObject([ + { label: 'new A1', inheritedValue: [{ value: 'text 1 changed' }] }, + ]); - expect(relatedB2?.metadata).toEqual({ - relationship_b: [ - expect.objectContaining({ - label: 'new A1', - inheritedValue: [{ value: 'text 1 changed' }], - }), - ], - }); + expect(relatedB2?.metadata?.relationship_b).toMatchObject([ + { label: 'new A1', inheritedValue: [{ value: 'text 1 changed' }] }, + ]); - expect(relatedC?.metadata).toEqual({ - relationship_c: [ - expect.objectContaining({ - label: 'new A1', - inheritedValue: [{ value: 'text 1 changed' }], - }), - ], - }); + expect(relatedC?.metadata?.relationship_c).toMatchObject([ + { label: 'new A1', inheritedValue: [{ value: 'text 1 changed' }] }, + ]); }); it('should update title and 2 differente text properties denormalized on related entities', async () => { const fixtures: DBFixture = { templates: [ - { - _id: factory.id('templateA'), - properties: [factory.property('text1'), factory.property('text2')], - }, - { - _id: factory.id('templateB'), - properties: [ - factory.relationshipProp('relationship_b', 'templateA', 'rel1', { - inherit: { type: 'text', property: factory.id('text1').toString() }, - }), - ], - }, - { - _id: factory.id('templateC'), - properties: [ - factory.relationshipProp('relationship_c', 'templateA', 'rel1', { - inherit: { type: 'text', property: factory.id('text2').toString() }, - }), - ], - }, + factory.template('templateA', [factory.property('text1'), factory.property('text2')]), + factory.template('templateB', [factory.inherit('relationship_b', 'templateA', 'text1')]), + factory.template('templateC', [factory.inherit('relationship_c', 'templateA', 'text2')]), ], entities: [ factory.entity('A1', { template: factory.id('templateA') }), @@ -241,23 +183,13 @@ describe('Denormalize relationships', () => { await entities.getById('C1', 'en'), ]; - expect(relatedB?.metadata).toEqual({ - relationship_b: [ - expect.objectContaining({ - label: 'new A1', - inheritedValue: [{ value: 'text 1 changed' }], - }), - ], - }); + expect(relatedB?.metadata?.relationship_b).toMatchObject([ + { label: 'new A1', inheritedValue: [{ value: 'text 1 changed' }] }, + ]); - expect(relatedC?.metadata).toEqual({ - relationship_c: [ - expect.objectContaining({ - label: 'new A1', - inheritedValue: [{ value: 'text 2 changed' }], - }), - ], - }); + expect(relatedC?.metadata?.relationship_c).toMatchObject([ + { label: 'new A1', inheritedValue: [{ value: 'text 2 changed' }] }, + ]); }); }); @@ -265,22 +197,14 @@ describe('Denormalize relationships', () => { beforeEach(async () => { const fixtures: DBFixture = { templates: [ - { - _id: factory.id('templateA'), - properties: [ - factory.relationshipProp('relationship', 'templateB', 'rel1', { - inherit: { type: 'multiselect', property: factory.id('multiselect').toString() }, - }), - ], - }, - { - _id: factory.id('templateB'), - properties: [ - factory.property('multiselect', 'multiselect', { - content: factory.id('thesauri').toString(), - }), - ], - }, + factory.template('templateA', [ + factory.inherit('relationship', 'templateB', 'multiselect'), + ]), + factory.template('templateB', [ + factory.property('multiselect', 'multiselect', { + content: factory.id('thesauri').toString(), + }), + ]), ], dictionaries: [factory.thesauri('thesauri', ['T1', 'T2', 'T3'])], entities: [ @@ -310,19 +234,17 @@ describe('Denormalize relationships', () => { }); const relatedEntity = await entities.getById('A1', 'en'); - expect(relatedEntity?.metadata).toEqual({ - relationship: [ - expect.objectContaining({ - inheritedValue: [ - { value: 'T2', label: 'T2' }, - { value: 'T3', label: 'T3' }, - ], - }), - expect.objectContaining({ - inheritedValue: [{ value: 'T1', label: 'T1' }], - }), - ], - }); + expect(relatedEntity?.metadata?.relationship).toMatchObject([ + { + inheritedValue: [ + { value: 'T2', label: 'T2' }, + { value: 'T3', label: 'T3' }, + ], + }, + { + inheritedValue: [{ value: 'T1', label: 'T1' }], + }, + ]); }); it('should update denormalized properties when thesauri label changes', async () => { @@ -342,19 +264,17 @@ describe('Denormalize relationships', () => { await thesauris.save(factory.thesauri('thesauri', [['T1', 'new 1'], 'T2', ['T3', 'new 3']])); const relatedEntity = await entities.getById('A1', 'en'); - expect(relatedEntity?.metadata).toEqual({ - relationship: [ - expect.objectContaining({ - inheritedValue: [ - { value: 'T2', label: 'T2' }, - { value: 'T3', label: 'new 3' }, - ], - }), - expect.objectContaining({ - inheritedValue: [{ value: 'T1', label: 'new 1' }], - }), - ], - }); + expect(relatedEntity?.metadata?.relationship).toMatchObject([ + { + inheritedValue: [ + { value: 'T2', label: 'T2' }, + { value: 'T3', label: 'new 3' }, + ], + }, + { + inheritedValue: [{ value: 'T1', label: 'new 1' }], + }, + ]); }); }); @@ -362,28 +282,17 @@ describe('Denormalize relationships', () => { beforeEach(async () => { const fixtures: DBFixture = { templates: [ - { - _id: factory.id('templateA'), - properties: [ - factory.relationshipProp('relationship', 'templateB', 'rel1', { - inherit: { type: 'relationship', property: factory.id('relationshipB').toString() }, - }), - ], - }, - { - _id: factory.id('templateB'), - properties: [factory.relationshipProp('relationshipB', 'templateC', 'rel1')], - }, - { _id: factory.id('templateC'), properties: [] }, + factory.template('templateA', [ + factory.inherit('relationship', 'templateB', 'relationshipB'), + ]), + factory.template('templateB', [factory.relationshipProp('relationshipB', 'templateC')]), + factory.template('templateC', []), ], entities: [ factory.entity('A1', { template: factory.id('templateA'), metadata: { - relationship: [ - { value: 'B1', inheritedValue: [{ value: 'C1' }] }, - { value: 'B2', inheritedValue: [{ value: 'C2' }] }, - ], + relationship: [{ value: 'B1' }, { value: 'B2' }], }, }), factory.entity('B1', { template: factory.id('templateB') }), @@ -403,16 +312,10 @@ describe('Denormalize relationships', () => { it('should update denormalized properties when relationship selected changes', async () => { const relatedEntity = await entities.getById('A1', 'en'); - expect(relatedEntity?.metadata).toEqual({ - relationship: [ - expect.objectContaining({ - inheritedValue: [expect.objectContaining({ value: 'C1', label: 'C1' })], - }), - expect.objectContaining({ - inheritedValue: [expect.objectContaining({ value: 'C2', label: 'C2' })], - }), - ], - }); + expect(relatedEntity?.metadata?.relationship).toMatchObject([ + { inheritedValue: [{ value: 'C1', label: 'C1' }] }, + { inheritedValue: [{ value: 'C2', label: 'C2' }] }, + ]); }); it('should update denormalized properties when relationship inherited label changes', async () => { @@ -420,16 +323,10 @@ describe('Denormalize relationships', () => { await modifyEntity('C2', { title: 'new C2' }); const relatedEntity = await entities.getById('A1', 'en'); - expect(relatedEntity?.metadata).toEqual({ - relationship: [ - expect.objectContaining({ - inheritedValue: [expect.objectContaining({ value: 'C1', label: 'new C1' })], - }), - expect.objectContaining({ - inheritedValue: [expect.objectContaining({ value: 'C2', label: 'new C2' })], - }), - ], - }); + expect(relatedEntity?.metadata?.relationship).toMatchObject([ + { inheritedValue: [{ value: 'C1', label: 'new C1' }] }, + { inheritedValue: [{ value: 'C2', label: 'new C2' }] }, + ]); }); }); @@ -437,18 +334,8 @@ describe('Denormalize relationships', () => { it('should denormalize the title and a simple property in the correct language', async () => { await load({ templates: [ - { - _id: factory.id('templateA'), - properties: [ - factory.relationshipProp('relationship', 'templateB', 'rel1', { - inherit: { type: 'text', property: factory.id('text').toString() }, - }), - ], - }, - { - _id: factory.id('templateB'), - properties: [factory.property('text')], - }, + factory.template('templateA', [factory.inherit('relationship', 'templateB', 'text')]), + factory.template('templateB', [factory.property('text')]), ], entities: [ factory.entity('A1', { @@ -485,42 +372,18 @@ describe('Denormalize relationships', () => { await modifyEntity('B1', { metadata: { text: [{ value: 'text' }] } }); await modifyEntity('B1', { metadata: { text: [{ value: 'texto' }] } }, 'es'); - await modifyEntity( - 'B1', - { - metadata: { text: [{ value: 'nuevo texto para ES' }] }, - }, - 'es' - ); + await modifyEntity('B1', { metadata: { text: [{ value: 'nuevo texto para ES' }] } }, 'es'); const relatedEn = await entities.getById('A1', 'en'); const relatedEs = await entities.getById('A1', 'es'); - expect(relatedEn?.metadata).toEqual({ - relationship: [ - expect.objectContaining({ - value: 'B1', - inheritedValue: [ - { - value: 'text', - }, - ], - }), - ], - }); + expect(relatedEn?.metadata?.relationship).toMatchObject([ + { value: 'B1', inheritedValue: [{ value: 'text' }] }, + ]); - expect(relatedEs?.metadata).toEqual({ - relationship: [ - expect.objectContaining({ - value: 'B1', - inheritedValue: [ - { - value: 'nuevo texto para ES', - }, - ], - }), - ], - }); + expect(relatedEs?.metadata?.relationship).toMatchObject([ + { value: 'B1', inheritedValue: [{ value: 'nuevo texto para ES' }] }, + ]); }); }); }); diff --git a/app/api/templates/templates.ts b/app/api/templates/templates.ts index bb4efd5071..00ae41bbeb 100644 --- a/app/api/templates/templates.ts +++ b/app/api/templates/templates.ts @@ -73,7 +73,11 @@ export default { .filter(p => contentId === p.content?.toString()); return ( - await model.get({ 'properties.inherit.property': { $in: properties.map(p => p.id) } }) + await model.get({ + 'properties.inherit.property': { + $in: properties.map(p => p._id?.toString()).filter(v => v), + }, + }) ).reduce((m, t) => m.concat(t.properties || []), []); }, diff --git a/app/api/utils/fixturesFactory.ts b/app/api/utils/fixturesFactory.ts index 0d091ad047..4d237156e2 100644 --- a/app/api/utils/fixturesFactory.ts +++ b/app/api/utils/fixturesFactory.ts @@ -19,6 +19,11 @@ export function getFixturesFactory() { return Object.freeze({ id: idMapper, + template: (name: string, properties: PropertySchema[]) => ({ + _id: idMapper(name), + properties, + }), + entity: (id: string, props = {}, language?: string): EntitySchema => ({ _id: idMapper(language ? `${id}-${language}` : id), sharedId: id, @@ -27,21 +32,20 @@ export function getFixturesFactory() { ...props, }), - relationshipProp: ( - name: string, - content: string, - relation: string, - props = {} - ): PropertySchema => ({ - _id: idMapper(name), - id: idMapper(name).toString(), - label: name, - name, - type: 'relationship', - relationType: idMapper(relation).toString(), - content: idMapper(content).toString(), - ...props, - }), + inherit(name: string, content: string, property: string, props = {}): PropertySchema { + return this.relationshipProp(name, content, { + inherit: { property: idMapper(property).toString() }, + ...props, + }); + }, + + relationshipProp(name: string, content: string, props = {}): PropertySchema { + return this.property(name, 'relationship', { + relationType: idMapper('rel1').toString(), + content: idMapper(content).toString(), + ...props, + }); + }, property: ( name: string, @@ -49,7 +53,7 @@ export function getFixturesFactory() { props = {} ): PropertySchema => ({ _id: idMapper(name), - id: idMapper(name).toString(), + id: name, label: name, name, type, From df93934753ec897e3ff13f207069711ca21ec194 Mon Sep 17 00:00:00 2001 From: Daneryl Date: Wed, 9 Jun 2021 12:04:56 +0200 Subject: [PATCH 14/27] more compact factory.entity --- app/api/entities/entities.js | 14 +- .../entities/specs/denormalization.spec.ts | 196 ++++++++---------- app/api/utils/fixturesFactory.ts | 28 ++- 3 files changed, 120 insertions(+), 118 deletions(-) diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index b80b0cdadb..36be38f3ce 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -165,7 +165,7 @@ async function updateEntity(entity, _template, unrestricted = false) { const properties = ( await templates.get({ 'properties.content': template._id.toString(), - 'properties.inherit': { $exists: true }, + // 'properties.inherit': { $exists: true }, }) ) .reduce((m, t) => m.concat(t.properties), []) @@ -197,7 +197,19 @@ async function updateEntity(entity, _template, unrestricted = false) { ); }, Promise.resolve()); + //await search.indexEntities({ + // $and: [ + // { + // language: fullEntity.language + // }, + // { + // $or: properties.map(property => ({ [`metadata.${property.name}.value`]: fullEntity.sharedId })), + // }, + // ], + //}); + } + ////Crappy draft code ends if (entity.suggestedMetadata) { diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index 9a7a54d217..64ea4fcb5f 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -7,22 +7,25 @@ import thesauris from 'api/thesauri'; import { getFixturesFactory } from '../../utils/fixturesFactory'; const load = async (data: DBFixture) => - db.setupFixturesAndContext({ - ...data, - settings: [{ _id: db.id(), languages: [{ key: 'en', default: true }, { key: 'es' }] }], - translations: [ - { locale: 'en', contexts: [] }, - { locale: 'es', contexts: [] }, - ], - }); + db.setupFixturesAndContext( + { + ...data, + settings: [{ _id: db.id(), languages: [{ key: 'en', default: true }, { key: 'es' }] }], + translations: [ + { locale: 'en', contexts: [] }, + { locale: 'es', contexts: [] }, + ], + }, + // 'elastic-denormalize-spec-index' + ); describe('Denormalize relationships', () => { const factory = getFixturesFactory(); - const modifyEntity = async (id: string, entityData: EntitySchema, language?: string) => { + const modifyEntity = async (id: string, entityData: EntitySchema, language: string = 'en') => { await entities.save( - { ...factory.entity(id, entityData, language) }, - { language: language || 'en', user: {} }, + { _id: factory.id(`${id}-${language}`), sharedId: id, ...entityData, language }, + { language, user: {} }, true ); }; @@ -41,23 +44,25 @@ describe('Denormalize relationships', () => { factory.template('templateC', [factory.property('another_text')]), ], entities: [ - factory.entity('A1', { - template: factory.id('templateA'), - metadata: { - relationship: [factory.metadataValue('B1'), factory.metadataValue('B2')], - relationship2: [factory.metadataValue('C1')], - }, - }), - factory.entity('B1', { - template: factory.id('templateB'), - icon: { _id: 'icon_id', label: 'icon_label', type: 'icon_type' }, + factory.entity('A1', 'templateA', { + relationship: [factory.metadataValue('B1'), factory.metadataValue('B2')], + relationship2: [factory.metadataValue('C1')], }), - factory.entity('B2', { template: factory.id('templateB') }), - factory.entity('C1', { template: factory.id('templateC') }), + factory.entity( + 'B1', + 'templateB', + {}, + { + icon: { _id: 'icon_id', label: 'icon_label', type: 'icon_type' }, + } + ), + factory.entity('B2', 'templateB'), + factory.entity('C1', 'templateC'), ], }; await load(fixtures); + await modifyEntity('B1', { title: 'new Title', metadata: { text: [{ value: 'text 1 changed' }] }, @@ -103,25 +108,10 @@ describe('Denormalize relationships', () => { factory.template('templateC', [factory.inherit('relationship_c', 'templateA', 'text')]), ], entities: [ - factory.entity('A1', { template: factory.id('templateA') }), - factory.entity('B1', { - template: factory.id('templateB'), - metadata: { - relationship_b: [factory.metadataValue('A1')], - }, - }), - factory.entity('B2', { - template: factory.id('templateB'), - metadata: { - relationship_b: [factory.metadataValue('A1')], - }, - }), - factory.entity('C1', { - template: factory.id('templateC'), - metadata: { - relationship_c: [factory.metadataValue('A1')], - }, - }), + factory.entity('A1', 'templateA'), + factory.entity('B1', 'templateB', { relationship_b: [factory.metadataValue('A1')] }), + factory.entity('B2', 'templateB', { relationship_b: [factory.metadataValue('A1')] }), + factory.entity('C1', 'templateC', { relationship_c: [factory.metadataValue('A1')] }), ], }; @@ -159,15 +149,9 @@ describe('Denormalize relationships', () => { factory.template('templateC', [factory.inherit('relationship_c', 'templateA', 'text2')]), ], entities: [ - factory.entity('A1', { template: factory.id('templateA') }), - factory.entity('B1', { - template: factory.id('templateB'), - metadata: { relationship_b: [factory.metadataValue('A1')] }, - }), - factory.entity('C1', { - template: factory.id('templateC'), - metadata: { relationship_c: [factory.metadataValue('A1')] }, - }), + factory.entity('A1', 'templateA'), + factory.entity('B1', 'templateB', { relationship_b: [factory.metadataValue('A1')] }), + factory.entity('C1', 'templateC', { relationship_c: [factory.metadataValue('A1')] }), ], }; @@ -208,17 +192,13 @@ describe('Denormalize relationships', () => { ], dictionaries: [factory.thesauri('thesauri', ['T1', 'T2', 'T3'])], entities: [ - factory.entity('A1', { - template: factory.id('templateA'), - metadata: { - relationship: [factory.metadataValue('B1'), factory.metadataValue('B2')], - }, + factory.entity('A1', 'templateA', { + relationship: [factory.metadataValue('B1'), factory.metadataValue('B2')], }), - factory.entity('B1', { - template: factory.id('templateB'), - metadata: { multiselect: [factory.metadataValue('T1')] }, + factory.entity('B1', 'templateB', { + multiselect: [factory.metadataValue('T1')], }), - factory.entity('B2', { template: factory.id('templateB') }), + factory.entity('B2', 'templateB'), ], }; await load(fixtures); @@ -286,28 +266,19 @@ describe('Denormalize relationships', () => { factory.inherit('relationship', 'templateB', 'relationshipB'), ]), factory.template('templateB', [factory.relationshipProp('relationshipB', 'templateC')]), - factory.template('templateC', []), + factory.template('templateC'), ], entities: [ - factory.entity('A1', { - template: factory.id('templateA'), - metadata: { - relationship: [{ value: 'B1' }, { value: 'B2' }], - }, - }), - factory.entity('B1', { template: factory.id('templateB') }), - factory.entity('B2', { template: factory.id('templateB') }), - - factory.entity('C1', { template: factory.id('templateC') }), - factory.entity('C2', { template: factory.id('templateC') }), + factory.entity('A1', 'templateA', { relationship: [{ value: 'B1' }, { value: 'B2' }] }), + factory.entity('B1', 'templateB'), + factory.entity('B2', 'templateB'), + factory.entity('C1', 'templateC'), + factory.entity('C2', 'templateC'), ], }; await load(fixtures); await modifyEntity('B1', { metadata: { relationshipB: [{ value: 'C1' }] } }); await modifyEntity('B2', { metadata: { relationshipB: [{ value: 'C2' }] } }); - // await modifyEntity('A1', { - // metadata: { relationship: [factory.metadataValue('B1'), factory.metadataValue('B2')] }, - // }); }); it('should update denormalized properties when relationship selected changes', async () => { @@ -334,55 +305,64 @@ describe('Denormalize relationships', () => { it('should denormalize the title and a simple property in the correct language', async () => { await load({ templates: [ - factory.template('templateA', [factory.inherit('relationship', 'templateB', 'text')]), - factory.template('templateB', [factory.property('text')]), + factory.template('templateA', [ + factory.inherit('relationshipA', 'templateB', 'relationshipB'), + ]), + factory.template('templateB', [factory.inherit('relationshipB', 'templateC', 'text')]), + factory.template('templateC', [factory.property('text')]), ], entities: [ - factory.entity('A1', { - template: factory.id('templateA'), - metadata: { - relationship: [factory.metadataValue('B1')], - }, - }), + factory.entity('A1', 'templateA', { relationshipA: [factory.metadataValue('B1')] }), factory.entity( 'A1', - { - template: factory.id('templateA'), - metadata: { - relationship: [factory.metadataValue('B1')], - }, - }, - 'es' + 'templateA', + { relationshipA: [factory.metadataValue('B1')] }, + { language: 'es' } ), - factory.entity('B1', { - template: factory.id('templateB'), - }), + factory.entity('B1', 'templateB', { relationshipB: [factory.metadataValue('C1')] }), factory.entity( 'B1', - { - sharedId: 'B1', - language: 'es', - template: factory.id('templateB'), - }, - 'es' + 'templateB', + { relationshipB: [factory.metadataValue('C1')] }, + { language: 'es' } ), + factory.entity('C1', 'templateC'), + factory.entity('C1', 'templateC', {}, { language: 'es' }), ], }); - await modifyEntity('B1', { metadata: { text: [{ value: 'text' }] } }); - await modifyEntity('B1', { metadata: { text: [{ value: 'texto' }] } }, 'es'); + /// generate inherited values ! + await modifyEntity('B1', { relationshipB: [factory.metadataValue('C1')] }, 'en'); + await modifyEntity('B1', { relationshipB: [factory.metadataValue('C1')] }, 'es'); + + await modifyEntity('A1', { relationshipA: [factory.metadataValue('B1')] }, 'en'); + await modifyEntity('A1', { relationshipA: [factory.metadataValue('B1')] }, 'es'); + + await modifyEntity('C1', { metadata: { text: [{ value: 'text' }] } }); + await modifyEntity('C1', { metadata: { text: [{ value: 'texto' }] } }, 'es'); + /// generate inherited values ! + + await modifyEntity('C1', { title: 'new Es title', metadata: { text: [{ value: 'nuevo texto para ES' }] } }, 'es'); - await modifyEntity('B1', { metadata: { text: [{ value: 'nuevo texto para ES' }] } }, 'es'); + const relatedEn = await entities.getById('B1', 'en'); + const relatedEs = await entities.getById('B1', 'es'); + + expect(relatedEn?.metadata?.relationshipB).toMatchObject([ + { value: 'C1', inheritedValue: [{ value: 'text' }] }, + ]); + expect(relatedEs?.metadata?.relationshipB).toMatchObject([ + { value: 'C1', inheritedValue: [{ value: 'nuevo texto para ES' }] }, + ]); - const relatedEn = await entities.getById('A1', 'en'); - const relatedEs = await entities.getById('A1', 'es'); + const transitiveEn = await entities.getById('A1', 'en'); + const transitiveEs = await entities.getById('A1', 'es'); - expect(relatedEn?.metadata?.relationship).toMatchObject([ - { value: 'B1', inheritedValue: [{ value: 'text' }] }, + expect(transitiveEn?.metadata?.relationshipA).toMatchObject([ + { value: 'B1', inheritedValue: [{ label: 'C1' }] }, ]); - expect(relatedEs?.metadata?.relationship).toMatchObject([ - { value: 'B1', inheritedValue: [{ value: 'nuevo texto para ES' }] }, + expect(transitiveEs?.metadata?.relationshipA).toMatchObject([ + { value: 'B1', inheritedValue: [{ label: 'new Es title' }] }, ]); }); }); diff --git a/app/api/utils/fixturesFactory.ts b/app/api/utils/fixturesFactory.ts index 4d237156e2..f564a3bca2 100644 --- a/app/api/utils/fixturesFactory.ts +++ b/app/api/utils/fixturesFactory.ts @@ -1,7 +1,7 @@ import { ObjectId } from 'mongodb'; import db from 'api/utils/testing_db'; import { EntitySchema } from 'shared/types/entityType'; -import { PropertySchema } from 'shared/types/commonTypes'; +import { PropertySchema, MetadataSchema } from 'shared/types/commonTypes'; export function getIdMapper() { const map = new Map(); @@ -19,18 +19,28 @@ export function getFixturesFactory() { return Object.freeze({ id: idMapper, - template: (name: string, properties: PropertySchema[]) => ({ + template: (name: string, properties: PropertySchema[] = []) => ({ _id: idMapper(name), properties, }), - entity: (id: string, props = {}, language?: string): EntitySchema => ({ - _id: idMapper(language ? `${id}-${language}` : id), - sharedId: id, - language: language || 'en', - title: language ? `${id}-${language}` : id, - ...props, - }), + entity: ( + id: string, + template?: string, + metadata: MetadataSchema = {}, + props: EntitySchema = { language: 'en' } + ): EntitySchema => { + const language = props.language || 'en'; + return { + _id: idMapper(`${id}-${language}`), + sharedId: id, + title: `${id}`, + ...(template ? { template: idMapper(template) } : {}), + metadata, + language, + ...props, + }; + }, inherit(name: string, content: string, property: string, props = {}): PropertySchema { return this.relationshipProp(name, content, { From bc3d1c86472e105c7698d095796bbd1ceb7948af Mon Sep 17 00:00:00 2001 From: Federico Nocetti Date: Wed, 9 Jun 2021 10:36:24 -0300 Subject: [PATCH 15/27] Indexation tests, refactored lang tests --- app/api/entities/entities.js | 46 ++++---- .../entities/specs/denormalization.spec.ts | 105 +++++++++++------- app/api/entities/specs/entities.spec.js | 27 ----- 3 files changed, 89 insertions(+), 89 deletions(-) diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index 36be38f3ce..5d84c110ff 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -176,10 +176,14 @@ async function updateEntity(entity, _template, unrestricted = false) { inheritIds.includes(p._id.toString()) ); + const transitiveProperties = await templates.esteNombreEsUnAskoCambiar( + template._id.toString() + ); + await updateTransitiveDenormalization( { id: fullEntity.sharedId, language: fullEntity.language }, { label: fullEntity.title, icon: fullEntity.icon }, - await templates.esteNombreEsUnAskoCambiar(template._id.toString()) + transitiveProperties ); await toUpdateProps.reduce(async (prev, prop) => { @@ -191,23 +195,29 @@ async function updateEntity(entity, _template, unrestricted = false) { label: fullEntity.title, icon: fullEntity.icon, }, - properties.filter(p => { - return prop._id.toString() === p.inherit?.property; - }) + properties.filter(p => prop._id.toString() === p.inherit?.property) ); }, Promise.resolve()); - //await search.indexEntities({ - // $and: [ - // { - // language: fullEntity.language - // }, - // { - // $or: properties.map(property => ({ [`metadata.${property.name}.value`]: fullEntity.sharedId })), - // }, - // ], - //}); - + if (properties.length || transitiveProperties.length) { + await search.indexEntities({ + $and: [ + { + language: fullEntity.language, + }, + { + $or: [ + ...properties.map(property => ({ + [`metadata.${property.name}.value`]: fullEntity.sharedId, + })), + ...transitiveProperties.map(property => ({ + [`metadata.${property.name}.inheritedValue.value`]: fullEntity.sharedId, + })), + ], + }, + ], + }); + } } ////Crappy draft code ends @@ -815,11 +825,7 @@ export default { .reduce((m, t) => m.concat(t.properties), []) .filter(p => thesaurusId === p.content?.toString()); - await updateDenormalization( - { id: valueId, language }, - { label: newLabel }, - properties - ); + await updateDenormalization({ id: valueId, language }, { label: newLabel }, properties); await updateTransitiveDenormalization( { id: valueId, language }, diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index 64ea4fcb5f..b8d3fe2b64 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -1,12 +1,14 @@ +/* eslint-disable max-statements */ /* eslint-disable max-lines */ import db, { DBFixture } from 'api/utils/testing_db'; import entities from 'api/entities'; import { EntitySchema } from 'shared/types/entityType'; import thesauris from 'api/thesauri'; +import { elasticTesting } from 'api/utils/elastic_testing'; import { getFixturesFactory } from '../../utils/fixturesFactory'; -const load = async (data: DBFixture) => +const load = async (data: DBFixture, index?: string) => db.setupFixturesAndContext( { ...data, @@ -16,7 +18,7 @@ const load = async (data: DBFixture) => { locale: 'es', contexts: [] }, ], }, - // 'elastic-denormalize-spec-index' + index ); describe('Denormalize relationships', () => { @@ -301,37 +303,41 @@ describe('Denormalize relationships', () => { }); }); - describe('languages', () => { - it('should denormalize the title and a simple property in the correct language', async () => { - await load({ - templates: [ - factory.template('templateA', [ - factory.inherit('relationshipA', 'templateB', 'relationshipB'), - ]), - factory.template('templateB', [factory.inherit('relationshipB', 'templateC', 'text')]), - factory.template('templateC', [factory.property('text')]), - ], - entities: [ - factory.entity('A1', 'templateA', { relationshipA: [factory.metadataValue('B1')] }), - factory.entity( - 'A1', - 'templateA', - { relationshipA: [factory.metadataValue('B1')] }, - { language: 'es' } - ), - factory.entity('B1', 'templateB', { relationshipB: [factory.metadataValue('C1')] }), - factory.entity( - 'B1', - 'templateB', - { relationshipB: [factory.metadataValue('C1')] }, - { language: 'es' } - ), - factory.entity('C1', 'templateC'), - factory.entity('C1', 'templateC', {}, { language: 'es' }), - ], - }); + describe('languages and indexation', () => { + beforeEach(async () => { + await load( + { + templates: [ + factory.template('templateA', [ + factory.inherit('relationshipA', 'templateB', 'relationshipB'), + ]), + factory.template('templateB', [factory.inherit('relationshipB', 'templateC', 'text')]), + factory.template('templateC', [factory.property('text')]), + ], + entities: [ + factory.entity('A1', 'templateA', { relationshipA: [factory.metadataValue('B1')] }), + factory.entity( + 'A1', + 'templateA', + { relationshipA: [factory.metadataValue('B1')] }, + { language: 'es' } + ), + factory.entity('B1', 'templateB', { relationshipB: [factory.metadataValue('C1')] }), + factory.entity( + 'B1', + 'templateB', + { relationshipB: [factory.metadataValue('C1')] }, + { language: 'es' } + ), + factory.entity('C1', 'templateC'), + factory.entity('C1', 'templateC', {}, { language: 'es' }), + ], + }, + 'index_denormalization' + ); /// generate inherited values ! + await modifyEntity('B1', { relationshipB: [factory.metadataValue('C1')] }, 'en'); await modifyEntity('B1', { relationshipB: [factory.metadataValue('C1')] }, 'es'); @@ -340,28 +346,43 @@ describe('Denormalize relationships', () => { await modifyEntity('C1', { metadata: { text: [{ value: 'text' }] } }); await modifyEntity('C1', { metadata: { text: [{ value: 'texto' }] } }, 'es'); + /// generate inherited values ! + }); + + it('should index the correct entities on a simple relationship', async () => { + await modifyEntity( + 'C1', + { title: 'new Es title', metadata: { text: [{ value: 'nuevo texto para ES' }] } }, + 'es' + ); - await modifyEntity('C1', { title: 'new Es title', metadata: { text: [{ value: 'nuevo texto para ES' }] } }, 'es'); + await elasticTesting.refresh(); + const results = await elasticTesting.getIndexedEntities(); - const relatedEn = await entities.getById('B1', 'en'); - const relatedEs = await entities.getById('B1', 'es'); + const [B1en, B1es] = results.filter(r => r.sharedId === 'B1'); - expect(relatedEn?.metadata?.relationshipB).toMatchObject([ - { value: 'C1', inheritedValue: [{ value: 'text' }] }, + expect(B1en.metadata?.relationshipB).toMatchObject([ + { value: 'C1', label: 'C1', inheritedValue: [{ value: 'text' }] }, ]); - expect(relatedEs?.metadata?.relationshipB).toMatchObject([ - { value: 'C1', inheritedValue: [{ value: 'nuevo texto para ES' }] }, + expect(B1es.metadata?.relationshipB).toMatchObject([ + { value: 'C1', label: 'new Es title', inheritedValue: [{ value: 'nuevo texto para ES' }] }, ]); + }); + + it('should index the correct entities on a transitive relationship', async () => { + await modifyEntity('C1', { title: 'new Es title' }, 'es'); + + await elasticTesting.refresh(); + const results = await elasticTesting.getIndexedEntities(); - const transitiveEn = await entities.getById('A1', 'en'); - const transitiveEs = await entities.getById('A1', 'es'); + const [A1en, A1es] = results.filter(r => r.sharedId === 'A1'); - expect(transitiveEn?.metadata?.relationshipA).toMatchObject([ + expect(A1en?.metadata?.relationshipA).toMatchObject([ { value: 'B1', inheritedValue: [{ label: 'C1' }] }, ]); - expect(transitiveEs?.metadata?.relationshipA).toMatchObject([ + expect(A1es?.metadata?.relationshipA).toMatchObject([ { value: 'B1', inheritedValue: [{ label: 'new Es title' }] }, ]); }); diff --git a/app/api/entities/specs/entities.spec.js b/app/api/entities/specs/entities.spec.js index debc8a0956..26cc694d86 100644 --- a/app/api/entities/specs/entities.spec.js +++ b/app/api/entities/specs/entities.spec.js @@ -252,33 +252,6 @@ describe('entities', () => { }); }); - describe('when title changes', () => { - xit('should index entities changed after propagating label change', async () => { - const doc = { - _id: shared2, - sharedId: 'shared2', - title: 'changedTitle', - }; - - search.indexEntities.and.callThrough(); - - await entities.save(doc, { language: 'en' }); - - const documentsToIndex = search.bulkIndex.calls.argsFor(0)[0]; - - expect(documentsToIndex).toEqual([ - expect.objectContaining({ - sharedId: 'shared', - language: 'en', - }), - expect.objectContaining({ - sharedId: 'other', - language: 'en', - }), - ]); - }); - }); - describe('when published/template/generatedToc property changes', () => { it('should replicate the change for all the languages and ignore the published field', done => { const doc = { From 6193b036505f0e398a85a521190bda4db84b2742 Mon Sep 17 00:00:00 2001 From: Federico Nocetti Date: Wed, 9 Jun 2021 12:27:12 -0300 Subject: [PATCH 16/27] Minor refactors and case for relations with no content type --- app/api/entities/entities.js | 17 ++++++++ .../entities/specs/denormalization.spec.ts | 42 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index 5d84c110ff..faa9aa6c4c 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -171,6 +171,23 @@ async function updateEntity(entity, _template, unrestricted = false) { .reduce((m, t) => m.concat(t.properties), []) .filter(p => template._id?.toString() === p.content?.toString()); + const propertiesAny = ( + await templates.get({ + 'properties.content': '', + }) + ) + .reduce((m, t) => m.concat(t.properties), []) + .filter(p => p.content?.toString() === ''); + + await updateDenormalization( + { id: fullEntity.sharedId, language: fullEntity.language }, + { + label: fullEntity.title, + icon: fullEntity.icon, + }, + propertiesAny + ); + const inheritIds = properties.map(p => p.inherit?.property); const toUpdateProps = template.properties.filter(p => inheritIds.includes(p._id.toString()) diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index b8d3fe2b64..cc38595aab 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -7,6 +7,7 @@ import { EntitySchema } from 'shared/types/entityType'; import thesauris from 'api/thesauri'; import { elasticTesting } from 'api/utils/elastic_testing'; import { getFixturesFactory } from '../../utils/fixturesFactory'; +import relationship from '../../csv/typeParsers/relationship'; const load = async (data: DBFixture, index?: string) => db.setupFixturesAndContext( @@ -179,6 +180,47 @@ describe('Denormalize relationships', () => { }); }); + describe('when the relationship property has no content', () => { + it('should denormalize the title on related entities', async () => { + const fixtures: DBFixture = { + templates: [ + factory.template('templateA', [ + factory.relationshipProp('relationship', '', { content: '' }), + ]), + factory.template('templateB'), + factory.template('templateC'), + ], + entities: [ + factory.entity('A1', 'templateA', { + relationship: [factory.metadataValue('B1'), factory.metadataValue('C1')], + }), + factory.entity('B1', 'templateB'), + factory.entity('C1', 'templateC'), + ], + }; + + await load(fixtures); + + await modifyEntity('A1', { + metadata: { relationship: [factory.metadataValue('B1'), factory.metadataValue('C1')] }, + }); + + await modifyEntity('B1', { title: 'new B1' }); + await modifyEntity('C1', { title: 'new C1' }); + + const relatedEntity = await entities.getById('A1', 'en'); + + expect(relatedEntity?.metadata?.relationship).toMatchObject([ + { + label: 'new B1', + }, + { + label: 'new C1', + }, + ]); + }); + }); + describe('inherited select/multiselect (thesauri)', () => { beforeEach(async () => { const fixtures: DBFixture = { From a8b41dee88c12eb6242aa9dda73b97e68d0c75b9 Mon Sep 17 00:00:00 2001 From: Federico Nocetti Date: Wed, 9 Jun 2021 15:58:46 -0300 Subject: [PATCH 17/27] Add test case for index relations without content and fix --- app/api/entities/entities.js | 8 ++++---- app/api/entities/specs/denormalization.spec.ts | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index faa9aa6c4c..d40003585a 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -171,7 +171,7 @@ async function updateEntity(entity, _template, unrestricted = false) { .reduce((m, t) => m.concat(t.properties), []) .filter(p => template._id?.toString() === p.content?.toString()); - const propertiesAny = ( + const propertiesWithoutContent = ( await templates.get({ 'properties.content': '', }) @@ -185,7 +185,7 @@ async function updateEntity(entity, _template, unrestricted = false) { label: fullEntity.title, icon: fullEntity.icon, }, - propertiesAny + propertiesWithoutContent ); const inheritIds = properties.map(p => p.inherit?.property); @@ -216,7 +216,7 @@ async function updateEntity(entity, _template, unrestricted = false) { ); }, Promise.resolve()); - if (properties.length || transitiveProperties.length) { + if (properties.length || propertiesWithoutContent.length || transitiveProperties.length) { await search.indexEntities({ $and: [ { @@ -224,7 +224,7 @@ async function updateEntity(entity, _template, unrestricted = false) { }, { $or: [ - ...properties.map(property => ({ + ...[...properties, ...propertiesWithoutContent].map(property => ({ [`metadata.${property.name}.value`]: fullEntity.sharedId, })), ...transitiveProperties.map(property => ({ diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index cc38595aab..2234e1c045 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -181,7 +181,7 @@ describe('Denormalize relationships', () => { }); describe('when the relationship property has no content', () => { - it('should denormalize the title on related entities', async () => { + it('should denormalize and index the title on related entities', async () => { const fixtures: DBFixture = { templates: [ factory.template('templateA', [ @@ -199,7 +199,7 @@ describe('Denormalize relationships', () => { ], }; - await load(fixtures); + await load(fixtures, 'index_denormalization'); await modifyEntity('A1', { metadata: { relationship: [factory.metadataValue('B1'), factory.metadataValue('C1')] }, @@ -218,6 +218,20 @@ describe('Denormalize relationships', () => { label: 'new C1', }, ]); + + await elasticTesting.refresh(); + const results = await elasticTesting.getIndexedEntities(); + + const [A1] = results.filter(r => r.sharedId === 'A1'); + + expect(A1?.metadata?.relationship).toMatchObject([ + { + label: 'new B1', + }, + { + label: 'new C1', + }, + ]); }); }); From 7e5ed5bcbe30c702bb39b34e6501e2891e8d4090 Mon Sep 17 00:00:00 2001 From: Federico Nocetti Date: Wed, 9 Jun 2021 22:26:08 -0300 Subject: [PATCH 18/27] test for repeated denormalized data --- app/api/entities/denormalize.ts | 4 +-- .../entities/specs/denormalization.spec.ts | 25 ++++++++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/api/entities/denormalize.ts b/app/api/entities/denormalize.ts index 33b9b2ced9..42ab78acf9 100644 --- a/app/api/entities/denormalize.ts +++ b/app/api/entities/denormalize.ts @@ -23,9 +23,9 @@ export const updateTransitiveDenormalization = async ( { language, [`metadata.${property.name}.inheritedValue.value`]: id }, { ...(changes.icon - ? { [`metadata.${property.name}.$.inheritedValue.$[valueObject].icon`]: changes.icon } + ? { [`metadata.${property.name}.$[].inheritedValue.$[valueObject].icon`]: changes.icon } : {}), - [`metadata.${property.name}.$.inheritedValue.$[valueObject].label`]: changes.label, + [`metadata.${property.name}.$[].inheritedValue.$[valueObject].label`]: changes.label, }, { arrayFilters: [{ 'valueObject.value': id }] } ) diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index 2234e1c045..b586bd9301 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -144,7 +144,7 @@ describe('Denormalize relationships', () => { ]); }); - it('should update title and 2 differente text properties denormalized on related entities', async () => { + it('should update title and 2 different text properties denormalized on related entities', async () => { const fixtures: DBFixture = { templates: [ factory.template('templateA', [factory.property('text1'), factory.property('text2')]), @@ -325,9 +325,13 @@ describe('Denormalize relationships', () => { ]), factory.template('templateB', [factory.relationshipProp('relationshipB', 'templateC')]), factory.template('templateC'), + factory.template('templateD', [ + factory.inherit('relationshipD', 'templateA', 'relationship'), + ]), ], entities: [ factory.entity('A1', 'templateA', { relationship: [{ value: 'B1' }, { value: 'B2' }] }), + factory.entity('A2', 'templateA', { relationship: [{ value: 'B1' }, { value: 'B2' }] }), factory.entity('B1', 'templateB'), factory.entity('B2', 'templateB'), factory.entity('C1', 'templateC'), @@ -336,14 +340,21 @@ describe('Denormalize relationships', () => { }; await load(fixtures); await modifyEntity('B1', { metadata: { relationshipB: [{ value: 'C1' }] } }); - await modifyEntity('B2', { metadata: { relationshipB: [{ value: 'C2' }] } }); + await modifyEntity('B2', { metadata: { relationshipB: [{ value: 'C2' }, { value: 'C1' }] } }); + await modifyEntity('A1', { metadata: { relationship: [{ value: 'B1' }, { value: 'B2' }] } }); + await modifyEntity('A2', { metadata: { relationship: [{ value: 'B1' }, { value: 'B2' }] } }); }); it('should update denormalized properties when relationship selected changes', async () => { const relatedEntity = await entities.getById('A1', 'en'); expect(relatedEntity?.metadata?.relationship).toMatchObject([ { inheritedValue: [{ value: 'C1', label: 'C1' }] }, - { inheritedValue: [{ value: 'C2', label: 'C2' }] }, + { + inheritedValue: [ + { value: 'C2', label: 'C2' }, + { value: 'C1', label: 'C1' }, + ], + }, ]); }); @@ -352,9 +363,15 @@ describe('Denormalize relationships', () => { await modifyEntity('C2', { title: 'new C2' }); const relatedEntity = await entities.getById('A1', 'en'); + expect(relatedEntity?.metadata?.relationship).toMatchObject([ { inheritedValue: [{ value: 'C1', label: 'new C1' }] }, - { inheritedValue: [{ value: 'C2', label: 'new C2' }] }, + { + inheritedValue: [ + { value: 'C2', label: 'new C2' }, + { value: 'C1', label: 'new C1' }, + ], + }, ]); }); }); From 3749d0b38d5edbee3901f53e840627803491ba18 Mon Sep 17 00:00:00 2001 From: Daneryl Date: Thu, 10 Jun 2021 09:54:37 +0200 Subject: [PATCH 19/27] update denormalized labels, without inherit --- app/api/entities/entities.js | 2 +- .../entities/specs/denormalization.spec.ts | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index d40003585a..aceba762c9 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -185,7 +185,7 @@ async function updateEntity(entity, _template, unrestricted = false) { label: fullEntity.title, icon: fullEntity.icon, }, - propertiesWithoutContent + propertiesWithoutContent.concat(properties) ); const inheritIds = properties.map(p => p.inherit?.property); diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index b586bd9301..386699ead3 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -36,6 +36,33 @@ describe('Denormalize relationships', () => { afterAll(async () => db.disconnect()); describe('title and basic property (text)', () => { + it('should update denormalized title and icon', async () => { + const fixtures: DBFixture = { + templates: [ + factory.template('templateA', [ + factory.relationshipProp('relationship', 'templateB', 'text'), + ]), + factory.template('templateB', [factory.property('text')]), + ], + entities: [ + factory.entity('A1', 'templateA', { + relationship: [factory.metadataValue('B1'), factory.metadataValue('B2')], + }), + factory.entity('B1', 'templateB', {}, { icon: { _id: 'icon_id' } }), + factory.entity('B2', 'templateB'), + ], + }; + + await load(fixtures); + await modifyEntity('B1', { title: 'new Title' }); + await modifyEntity('B2', { title: 'new Title 2' }); + + const relatedEntity = await entities.getById('A1', 'en'); + expect(relatedEntity?.metadata).toMatchObject({ + relationship: [{ label: 'new Title', icon: { _id: 'icon_id' } }, { label: 'new Title 2' }], + }); + }); + it('should update title, icon and text property on related entities denormalized properties', async () => { const fixtures: DBFixture = { templates: [ From ff18eaf9c7dbb14657c3692500dc084d79bda6e3 Mon Sep 17 00:00:00 2001 From: Daneryl Date: Thu, 10 Jun 2021 15:25:35 +0200 Subject: [PATCH 20/27] refactor denormalization process --- app/api/entities/entities.js | 45 ++++++++++++------------------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index aceba762c9..0193cdc8cb 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -164,34 +164,14 @@ async function updateEntity(entity, _template, unrestricted = false) { const properties = ( await templates.get({ - 'properties.content': template._id.toString(), - // 'properties.inherit': { $exists: true }, - }) - ) - .reduce((m, t) => m.concat(t.properties), []) - .filter(p => template._id?.toString() === p.content?.toString()); - - const propertiesWithoutContent = ( - await templates.get({ - 'properties.content': '', + $or: [ + { 'properties.content': template._id.toString() }, + { 'properties.content': '' }, + ], }) ) .reduce((m, t) => m.concat(t.properties), []) - .filter(p => p.content?.toString() === ''); - - await updateDenormalization( - { id: fullEntity.sharedId, language: fullEntity.language }, - { - label: fullEntity.title, - icon: fullEntity.icon, - }, - propertiesWithoutContent.concat(properties) - ); - - const inheritIds = properties.map(p => p.inherit?.property); - const toUpdateProps = template.properties.filter(p => - inheritIds.includes(p._id.toString()) - ); + .filter(p => template._id?.toString() === p.content?.toString() || p.content === ''); const transitiveProperties = await templates.esteNombreEsUnAskoCambiar( template._id.toString() @@ -203,20 +183,25 @@ async function updateEntity(entity, _template, unrestricted = false) { transitiveProperties ); - await toUpdateProps.reduce(async (prev, prop) => { + await properties.reduce(async (prev, prop) => { await prev; + const inheritProperty = template.properties.find( + p => prop.inherit?.property === p._id.toString() + ); return updateDenormalization( { id: fullEntity.sharedId, language: fullEntity.language }, { - inheritedValue: fullEntity.metadata[prop.name], + ...(inheritProperty + ? { inheritedValue: fullEntity.metadata[inheritProperty.name] } + : {}), label: fullEntity.title, icon: fullEntity.icon, }, - properties.filter(p => prop._id.toString() === p.inherit?.property) + [prop] ); }, Promise.resolve()); - if (properties.length || propertiesWithoutContent.length || transitiveProperties.length) { + if (properties.length || transitiveProperties.length) { await search.indexEntities({ $and: [ { @@ -224,7 +209,7 @@ async function updateEntity(entity, _template, unrestricted = false) { }, { $or: [ - ...[...properties, ...propertiesWithoutContent].map(property => ({ + ...properties.map(property => ({ [`metadata.${property.name}.value`]: fullEntity.sharedId, })), ...transitiveProperties.map(property => ({ From f80084f4b30adb8d7438143a88f43b0f3034cae0 Mon Sep 17 00:00:00 2001 From: Federico Nocetti Date: Thu, 10 Jun 2021 13:24:47 -0300 Subject: [PATCH 21/27] Add missing indexing to thesauris denorm --- app/api/entities/entities.js | 23 ++++++- .../entities/specs/denormalization.spec.ts | 61 +++++++++++++++++-- 2 files changed, 78 insertions(+), 6 deletions(-) diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index 0193cdc8cb..2a8052ea47 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -829,11 +829,32 @@ export default { await updateDenormalization({ id: valueId, language }, { label: newLabel }, properties); + const transitiveProps = await templates.esteNombreEsUnAskoCambiar(thesaurusId.toString()); await updateTransitiveDenormalization( { id: valueId, language }, { label: newLabel }, - await templates.esteNombreEsUnAskoCambiar(thesaurusId.toString()) + transitiveProps ); + + if (properties.length || transitiveProps.length) { + await search.indexEntities({ + $and: [ + { + language, + }, + { + $or: [ + ...properties.map(property => ({ + [`metadata.${property.name}.value`]: valueId, + })), + ...transitiveProps.map(property => ({ + [`metadata.${property.name}.inheritedValue.value`]: valueId, + })), + ], + }, + ], + }); + } }, async createThumbnail(entity) { diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index 386699ead3..30d6712f40 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -7,7 +7,6 @@ import { EntitySchema } from 'shared/types/entityType'; import thesauris from 'api/thesauri'; import { elasticTesting } from 'api/utils/elastic_testing'; import { getFixturesFactory } from '../../utils/fixturesFactory'; -import relationship from '../../csv/typeParsers/relationship'; const load = async (data: DBFixture, index?: string) => db.setupFixturesAndContext( @@ -286,7 +285,7 @@ describe('Denormalize relationships', () => { factory.entity('B2', 'templateB'), ], }; - await load(fixtures); + await load(fixtures, 'index_denormalize'); }); it('should update denormalized properties when thesauri selected changes', async () => { @@ -312,7 +311,7 @@ describe('Denormalize relationships', () => { ]); }); - it('should update denormalized properties when thesauri label changes', async () => { + it('should update and index denormalized properties when thesauri label changes', async () => { await modifyEntity('B1', { metadata: { multiselect: [{ value: 'T2' }, { value: 'T3' }] }, }); @@ -328,8 +327,12 @@ describe('Denormalize relationships', () => { await thesauris.save(factory.thesauri('thesauri', [['T1', 'new 1'], 'T2', ['T3', 'new 3']])); - const relatedEntity = await entities.getById('A1', 'en'); - expect(relatedEntity?.metadata?.relationship).toMatchObject([ + await elasticTesting.refresh(); + const results = await elasticTesting.getIndexedEntities(); + + const A1 = results.find(r => r.sharedId === 'A1'); + + expect(A1?.metadata?.relationship).toMatchObject([ { inheritedValue: [ { value: 'T2', label: 'T2' }, @@ -487,4 +490,52 @@ describe('Denormalize relationships', () => { ]); }); }); + + // describe('properties value changes that are independent of language', () => { + // beforeEach(async () => { + // await load( + // { + // templates: [ + // factory.template('templateA', [ + // factory.inherit('relationshipA', 'templateB', 'relationshipB'), + // ]), + // factory.template('templateB', [ + // factory.inherit('relationshipMulti', 'templateC', 'multiselect'), + // factory.inherit('relationshipSimple', 'templateC', 'select'), + // ]), + // factory.template('templateC', [ + // factory.property('multiselect', 'multiselect', { + // content: factory.id('thesauri').toString(), + // }), + // factory.property('select', 'select', { + // content: factory.id('thesauri').toString(), + // }) + // ]), + // ], + // entities: [ + // factory.entity('A1', 'templateA', { relationshipA: [factory.metadataValue('B1')] }), + // factory.entity( + // 'A1', + // 'templateA', + // { relationshipA: [factory.metadataValue('B1')] }, + // { language: 'es' } + // ), + // factory.entity('B1', 'templateB', { relationshipB: [factory.metadataValue('C1')] }), + // factory.entity( + // 'B1', + // 'templateB', + // { relationshipB: [factory.metadataValue('C1')] }, + // { language: 'es' } + // ), + // factory.entity('C1', 'templateC'), + // factory.entity('C1', 'templateC', {}, { language: 'es' }), + // ], + // }, + // 'index_denormalization' + // ); + // }); + + // it('should denormalize the changes for all the languages', async () => { + + // }); }); From 859b0608e6db4644c6b751865c47a60aeaad1c8f Mon Sep 17 00:00:00 2001 From: Federico Nocetti Date: Thu, 10 Jun 2021 18:42:09 -0300 Subject: [PATCH 22/27] properties to sync case --- app/api/entities/denormalize.ts | 4 +- app/api/entities/entities.js | 36 +++--- .../entities/specs/denormalization.spec.ts | 108 ++++++++++-------- 3 files changed, 85 insertions(+), 63 deletions(-) diff --git a/app/api/entities/denormalize.ts b/app/api/entities/denormalize.ts index 42ab78acf9..d8f71002b4 100644 --- a/app/api/entities/denormalize.ts +++ b/app/api/entities/denormalize.ts @@ -9,7 +9,7 @@ interface Changes { interface Params { id: string; - language: string; + language?: string; } export const updateTransitiveDenormalization = async ( @@ -40,7 +40,7 @@ export const updateDenormalization = async ( Promise.all( properties.map(async property => model.updateMany( - { language, [`metadata.${property.name}.value`]: id }, + { ...(language ? { language } : {}), [`metadata.${property.name}.value`]: id }, { $set: Object.keys(changes).reduce( (set, prop) => ({ diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index 2a8052ea47..abd523d3ac 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -189,7 +189,12 @@ async function updateEntity(entity, _template, unrestricted = false) { p => prop.inherit?.property === p._id.toString() ); return updateDenormalization( - { id: fullEntity.sharedId, language: fullEntity.language }, + { + id: fullEntity.sharedId, + ...(!FIELD_TYPES_TO_SYNC.includes(prop.type) + ? { language: fullEntity.language } + : {}), + }, { ...(inheritProperty ? { inheritedValue: fullEntity.metadata[inheritProperty.name] } @@ -203,20 +208,25 @@ async function updateEntity(entity, _template, unrestricted = false) { if (properties.length || transitiveProperties.length) { await search.indexEntities({ - $and: [ - { - language: fullEntity.language, - }, - { - $or: [ - ...properties.map(property => ({ + $or: [ + ...properties.map(property => ({ + $and: [ + { + ...(!FIELD_TYPES_TO_SYNC.includes(property.type) + ? { language: fullEntity.language } + : {}), + }, + { [`metadata.${property.name}.value`]: fullEntity.sharedId, - })), - ...transitiveProperties.map(property => ({ - [`metadata.${property.name}.inheritedValue.value`]: fullEntity.sharedId, - })), + }, + ], + })), + ...transitiveProperties.map(property => ({ + $and: [ + { language: fullEntity.language }, + { [`metadata.${property.name}.inheritedValue.value`]: fullEntity.sharedId }, ], - }, + })), ], }); } diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index 30d6712f40..7087c39f23 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -453,7 +453,7 @@ describe('Denormalize relationships', () => { /// generate inherited values ! }); - it('should index the correct entities on a simple relationship', async () => { + fit('should index the correct entities on a simple relationship', async () => { await modifyEntity( 'C1', { title: 'new Es title', metadata: { text: [{ value: 'nuevo texto para ES' }] } }, @@ -491,51 +491,63 @@ describe('Denormalize relationships', () => { }); }); - // describe('properties value changes that are independent of language', () => { - // beforeEach(async () => { - // await load( - // { - // templates: [ - // factory.template('templateA', [ - // factory.inherit('relationshipA', 'templateB', 'relationshipB'), - // ]), - // factory.template('templateB', [ - // factory.inherit('relationshipMulti', 'templateC', 'multiselect'), - // factory.inherit('relationshipSimple', 'templateC', 'select'), - // ]), - // factory.template('templateC', [ - // factory.property('multiselect', 'multiselect', { - // content: factory.id('thesauri').toString(), - // }), - // factory.property('select', 'select', { - // content: factory.id('thesauri').toString(), - // }) - // ]), - // ], - // entities: [ - // factory.entity('A1', 'templateA', { relationshipA: [factory.metadataValue('B1')] }), - // factory.entity( - // 'A1', - // 'templateA', - // { relationshipA: [factory.metadataValue('B1')] }, - // { language: 'es' } - // ), - // factory.entity('B1', 'templateB', { relationshipB: [factory.metadataValue('C1')] }), - // factory.entity( - // 'B1', - // 'templateB', - // { relationshipB: [factory.metadataValue('C1')] }, - // { language: 'es' } - // ), - // factory.entity('C1', 'templateC'), - // factory.entity('C1', 'templateC', {}, { language: 'es' }), - // ], - // }, - // 'index_denormalization' - // ); - // }); - - // it('should denormalize the changes for all the languages', async () => { - - // }); + describe('when changing a multiselect in one language', () => { + beforeEach(async () => { + await load( + { + templates: [ + factory.template('templateA', [ + factory.inherit('relationshipA', 'templateB', 'multiselect'), + ]), + factory.template('templateB', [ + factory.property('multiselect', 'multiselect', { + content: factory.id('thesauri').toString(), + }), + ]), + ], + dictionaries: [factory.thesauri('thesauri', ['T1', 'T2', 'T3'])], + entities: [ + factory.entity('A1', 'templateA', { relationshipA: [factory.metadataValue('B1')] }), + factory.entity( + 'A1', + 'templateA', + { relationshipA: [factory.metadataValue('B1')] }, + { language: 'es' } + ), + factory.entity('B1', 'templateB', { + multiselect: [factory.metadataValue('T1')], + }), + factory.entity( + 'B1', + 'templateB', + { + multiselect: [factory.metadataValue('T1')], + }, + { language: 'es' } + ), + ], + }, + 'index_denormalization' + ); + }); + + it('should denormalize the VALUE for all the languages', async () => { + await modifyEntity('B1', { + metadata: { multiselect: [{ value: 'T1' }, { value: 'T2' }] }, + }); + + await elasticTesting.refresh(); + const results = await elasticTesting.getIndexedEntities(); + + const [A1en, A1es] = results.filter(r => r.sharedId === 'A1'); + + expect(A1en?.metadata?.relationshipA).toMatchObject([ + { value: 'B1', inheritedValue: [{ value: 'T1' }, { value: 'T2' }] }, + ]); + + expect(A1es?.metadata?.relationshipA).toMatchObject([ + { value: 'B1', inheritedValue: [{ value: 'T1' }, { value: 'T2' }] }, + ]); + }); + }); }); From 9f4a3b02b47f9bf2faeaee2e0ebc577e235a525c Mon Sep 17 00:00:00 2001 From: Daneryl Date: Fri, 11 Jun 2021 09:15:23 +0200 Subject: [PATCH 23/27] WIP extract denormalization method --- app/api/entities/denormalize.ts | 91 +++++++++++++++++++ app/api/entities/entities.js | 83 +---------------- .../entities/specs/denormalization.spec.ts | 2 +- 3 files changed, 94 insertions(+), 82 deletions(-) diff --git a/app/api/entities/denormalize.ts b/app/api/entities/denormalize.ts index d8f71002b4..2f16ec50d6 100644 --- a/app/api/entities/denormalize.ts +++ b/app/api/entities/denormalize.ts @@ -1,6 +1,97 @@ import { EntitySchema } from 'shared/types/entityType'; import { PropertySchema } from 'shared/types/commonTypes'; import model from './entitiesModel'; +import templates from 'api/templates'; +import { search } from 'api/search'; +import { propertyTypes } from 'shared/propertyTypes'; + +const FIELD_TYPES_TO_SYNC = [ + propertyTypes.select, + propertyTypes.multiselect, + propertyTypes.date, + propertyTypes.multidate, + propertyTypes.multidaterange, + propertyTypes.nested, + propertyTypes.relationship, + propertyTypes.relationship, + propertyTypes.geolocation, + propertyTypes.numeric, +]; + +export const denormalizeRelated = async (entity, template) => { + //Crappy draft code starts + + // entidad(title entidadC) <- entidadB <- entidadC; + // entidad(title thesauri) <- entidadB <- thesauri; + + // entidadB(title && inherited prop) <- entidadA; + // entidadC(title && inherited prop) <- entidadA; + + // entidad(thesauriValue) <- thesauri; + + const fullEntity = entity; + + const properties = ( + await templates.get({ + $or: [{ 'properties.content': template._id.toString() }, { 'properties.content': '' }], + }) + ) + .reduce((m, t) => m.concat(t.properties), []) + .filter(p => template._id?.toString() === p.content?.toString() || p.content === ''); + + const transitiveProperties = await templates.esteNombreEsUnAskoCambiar(template._id.toString()); + + await updateTransitiveDenormalization( + { id: fullEntity.sharedId, language: fullEntity.language }, + { label: fullEntity.title, icon: fullEntity.icon }, + transitiveProperties + ); + + await properties.reduce(async (prev, prop) => { + await prev; + const inheritProperty = template.properties.find( + p => prop.inherit?.property === p._id.toString() + ); + return updateDenormalization( + { + id: fullEntity.sharedId, + ...(!FIELD_TYPES_TO_SYNC.includes(prop.type) ? { language: fullEntity.language } : {}), + }, + { + ...(inheritProperty ? { inheritedValue: fullEntity.metadata[inheritProperty.name] } : {}), + label: fullEntity.title, + icon: fullEntity.icon, + }, + [prop] + ); + }, Promise.resolve()); + + if (properties.length || transitiveProperties.length) { + await search.indexEntities({ + $or: [ + ...properties.map(property => ({ + $and: [ + { + ...(!FIELD_TYPES_TO_SYNC.includes(property.type) + ? { language: fullEntity.language } + : {}), + }, + { + [`metadata.${property.name}.value`]: fullEntity.sharedId, + }, + ], + })), + ...transitiveProperties.map(property => ({ + $and: [ + { language: fullEntity.language }, + { [`metadata.${property.name}.inheritedValue.value`]: fullEntity.sharedId }, + ], + })), + ], + }); + } + ////Crappy draft code ends +}; interface Changes { label: string; diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index abd523d3ac..125242fc9a 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -21,7 +21,7 @@ import { validateEntity } from 'shared/types/entitySchema'; import { deleteFiles, deleteUploadedFiles } from '../files/filesystem'; import model from './entitiesModel'; import settings from '../settings'; -import { updateTransitiveDenormalization, updateDenormalization } from './denormalize'; +import { denormalizeRelated, updateTransitiveDenormalization, updateDenormalization } from './denormalize'; /** Repopulate metadata object .label from thesauri and relationships. */ async function denormalizeMetadata(metadata, entity, template, dictionariesByKey) { @@ -149,90 +149,11 @@ async function updateEntity(entity, _template, unrestricted = false) { toSave.metadata = await denormalizeMetadata(entity.metadata, entity, template); } - //Crappy draft code starts - - // entidad(title entidadC) <- entidadB <- entidadC; - // entidad(title thesauri) <- entidadB <- thesauri; - - // entidadB(title && inherited prop) <- entidadA; - // entidadC(title && inherited prop) <- entidadA; - - // entidad(thesauriValue) <- thesauri; - if (template._id) { const fullEntity = { ...currentDoc, ...toSave }; - - const properties = ( - await templates.get({ - $or: [ - { 'properties.content': template._id.toString() }, - { 'properties.content': '' }, - ], - }) - ) - .reduce((m, t) => m.concat(t.properties), []) - .filter(p => template._id?.toString() === p.content?.toString() || p.content === ''); - - const transitiveProperties = await templates.esteNombreEsUnAskoCambiar( - template._id.toString() - ); - - await updateTransitiveDenormalization( - { id: fullEntity.sharedId, language: fullEntity.language }, - { label: fullEntity.title, icon: fullEntity.icon }, - transitiveProperties - ); - - await properties.reduce(async (prev, prop) => { - await prev; - const inheritProperty = template.properties.find( - p => prop.inherit?.property === p._id.toString() - ); - return updateDenormalization( - { - id: fullEntity.sharedId, - ...(!FIELD_TYPES_TO_SYNC.includes(prop.type) - ? { language: fullEntity.language } - : {}), - }, - { - ...(inheritProperty - ? { inheritedValue: fullEntity.metadata[inheritProperty.name] } - : {}), - label: fullEntity.title, - icon: fullEntity.icon, - }, - [prop] - ); - }, Promise.resolve()); - - if (properties.length || transitiveProperties.length) { - await search.indexEntities({ - $or: [ - ...properties.map(property => ({ - $and: [ - { - ...(!FIELD_TYPES_TO_SYNC.includes(property.type) - ? { language: fullEntity.language } - : {}), - }, - { - [`metadata.${property.name}.value`]: fullEntity.sharedId, - }, - ], - })), - ...transitiveProperties.map(property => ({ - $and: [ - { language: fullEntity.language }, - { [`metadata.${property.name}.inheritedValue.value`]: fullEntity.sharedId }, - ], - })), - ], - }); - } + await denormalizeRelated(fullEntity, template); } - ////Crappy draft code ends if (entity.suggestedMetadata) { toSave.suggestedMetadata = await denormalizeMetadata( diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index 7087c39f23..3c573cea45 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -453,7 +453,7 @@ describe('Denormalize relationships', () => { /// generate inherited values ! }); - fit('should index the correct entities on a simple relationship', async () => { + it('should index the correct entities on a simple relationship', async () => { await modifyEntity( 'C1', { title: 'new Es title', metadata: { text: [{ value: 'nuevo texto para ES' }] } }, From 788c6be8f1fbb19140def737adf9e89ac29c294a Mon Sep 17 00:00:00 2001 From: Daneryl Date: Fri, 11 Jun 2021 15:54:50 +0200 Subject: [PATCH 24/27] update denormalizations once every language --- app/api/entities/denormalize.ts | 184 ++++++++---------- app/api/entities/entities.js | 23 ++- .../entities/specs/denormalization.spec.ts | 13 +- app/api/templates/templates.ts | 12 +- 4 files changed, 113 insertions(+), 119 deletions(-) diff --git a/app/api/entities/denormalize.ts b/app/api/entities/denormalize.ts index 2f16ec50d6..37d28e73c8 100644 --- a/app/api/entities/denormalize.ts +++ b/app/api/entities/denormalize.ts @@ -1,97 +1,11 @@ import { EntitySchema } from 'shared/types/entityType'; import { PropertySchema } from 'shared/types/commonTypes'; -import model from './entitiesModel'; -import templates from 'api/templates'; +import templates from 'api/templates/templates'; import { search } from 'api/search'; -import { propertyTypes } from 'shared/propertyTypes'; - -const FIELD_TYPES_TO_SYNC = [ - propertyTypes.select, - propertyTypes.multiselect, - propertyTypes.date, - propertyTypes.multidate, - propertyTypes.multidaterange, - propertyTypes.nested, - propertyTypes.relationship, - propertyTypes.relationship, - propertyTypes.geolocation, - propertyTypes.numeric, -]; - -export const denormalizeRelated = async (entity, template) => { - //Crappy draft code starts - - // entidad(title entidadC) <- entidadB <- entidadC; - // entidad(title thesauri) <- entidadB <- thesauri; - - // entidadB(title && inherited prop) <- entidadA; - // entidadC(title && inherited prop) <- entidadA; - - // entidad(thesauriValue) <- thesauri; +import { TemplateSchema } from 'shared/types/templateType'; +import { WithId } from 'api/odm'; - const fullEntity = entity; - - const properties = ( - await templates.get({ - $or: [{ 'properties.content': template._id.toString() }, { 'properties.content': '' }], - }) - ) - .reduce((m, t) => m.concat(t.properties), []) - .filter(p => template._id?.toString() === p.content?.toString() || p.content === ''); - - const transitiveProperties = await templates.esteNombreEsUnAskoCambiar(template._id.toString()); - - await updateTransitiveDenormalization( - { id: fullEntity.sharedId, language: fullEntity.language }, - { label: fullEntity.title, icon: fullEntity.icon }, - transitiveProperties - ); - - await properties.reduce(async (prev, prop) => { - await prev; - const inheritProperty = template.properties.find( - p => prop.inherit?.property === p._id.toString() - ); - return updateDenormalization( - { - id: fullEntity.sharedId, - ...(!FIELD_TYPES_TO_SYNC.includes(prop.type) ? { language: fullEntity.language } : {}), - }, - { - ...(inheritProperty ? { inheritedValue: fullEntity.metadata[inheritProperty.name] } : {}), - label: fullEntity.title, - icon: fullEntity.icon, - }, - [prop] - ); - }, Promise.resolve()); - - if (properties.length || transitiveProperties.length) { - await search.indexEntities({ - $or: [ - ...properties.map(property => ({ - $and: [ - { - ...(!FIELD_TYPES_TO_SYNC.includes(property.type) - ? { language: fullEntity.language } - : {}), - }, - { - [`metadata.${property.name}.value`]: fullEntity.sharedId, - }, - ], - })), - ...transitiveProperties.map(property => ({ - $and: [ - { language: fullEntity.language }, - { [`metadata.${property.name}.inheritedValue.value`]: fullEntity.sharedId }, - ], - })), - ], - }); - } - ////Crappy draft code ends -}; +import model from './entitiesModel'; interface Changes { label: string; @@ -103,7 +17,7 @@ interface Params { language?: string; } -export const updateTransitiveDenormalization = async ( +export const updateDenormalization = async ( { id, language }: Params, changes: Changes, properties: PropertySchema[] @@ -111,19 +25,22 @@ export const updateTransitiveDenormalization = async ( Promise.all( properties.map(async property => model.updateMany( - { language, [`metadata.${property.name}.inheritedValue.value`]: id }, + { ...(language ? { language } : {}), [`metadata.${property.name}.value`]: id }, { - ...(changes.icon - ? { [`metadata.${property.name}.$[].inheritedValue.$[valueObject].icon`]: changes.icon } - : {}), - [`metadata.${property.name}.$[].inheritedValue.$[valueObject].label`]: changes.label, + $set: Object.keys(changes).reduce( + (set, prop) => ({ + ...set, + [`metadata.${property.name}.$[valueObject].${prop}`]: changes[prop], + }), + {} + ), }, { arrayFilters: [{ 'valueObject.value': id }] } ) ) ); -export const updateDenormalization = async ( +export const updateTransitiveDenormalization = async ( { id, language }: Params, changes: Changes, properties: PropertySchema[] @@ -131,17 +48,74 @@ export const updateDenormalization = async ( Promise.all( properties.map(async property => model.updateMany( - { ...(language ? { language } : {}), [`metadata.${property.name}.value`]: id }, + { language, [`metadata.${property.name}.inheritedValue.value`]: id }, { - $set: Object.keys(changes).reduce( - (set, prop) => ({ - ...set, - [`metadata.${property.name}.$[valueObject].${prop}`]: changes[prop], - }), - {} - ), + ...(changes.icon + ? { [`metadata.${property.name}.$[].inheritedValue.$[valueObject].icon`]: changes.icon } + : {}), + [`metadata.${property.name}.$[].inheritedValue.$[valueObject].label`]: changes.label, }, { arrayFilters: [{ 'valueObject.value': id }] } ) ) ); + +export const denormalizeRelated = async ( + entity: WithId, + template: WithId +) => { + if (!entity.title || !entity.language || !entity.sharedId) { + throw new Error('denormalization requires an entity with title, sharedId and language'); + } + + const transitiveProperties = await templates.propsThatNeedInheritDenormalization( + template._id.toString() + ); + const properties = await templates.propsThatNeedDenormalization(template._id.toString()); + + await updateTransitiveDenormalization( + { id: entity.sharedId, language: entity.language }, + { label: entity.title, icon: entity.icon }, + transitiveProperties + ); + + await Promise.all( + properties.map(async prop => { + const inheritProperty = (template.properties || []).find( + p => prop.inherit?.property === p._id?.toString() + ); + return updateDenormalization( + { + // @ts-ignore we have a sharedId guard, why ts does not like this ? bug ? + id: entity.sharedId, + language: entity.language, + }, + { + ...(inheritProperty ? { inheritedValue: entity.metadata?.[inheritProperty.name] } : {}), + label: entity.title, + icon: entity.icon, + }, + [prop] + ); + }) + ); + + if (properties.length || transitiveProperties.length) { + await search.indexEntities({ + $and: [ + { language: entity.language }, + { + $or: [ + ...properties.map(property => ({ + [`metadata.${property.name}.value`]: entity.sharedId, + })), + ...transitiveProperties.map(property => ({ + [`metadata.${property.name}.inheritedValue.value`]: entity.sharedId, + })), + ], + }, + ], + }); + } + ////Crappy draft code ends +}; diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index 125242fc9a..c5e79afa4f 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -21,7 +21,11 @@ import { validateEntity } from 'shared/types/entitySchema'; import { deleteFiles, deleteUploadedFiles } from '../files/filesystem'; import model from './entitiesModel'; import settings from '../settings'; -import { denormalizeRelated, updateTransitiveDenormalization, updateDenormalization } from './denormalize'; +import { + denormalizeRelated, + updateTransitiveDenormalization, + updateDenormalization, +} from './denormalize'; /** Repopulate metadata object .label from thesauri and relationships. */ async function denormalizeMetadata(metadata, entity, template, dictionariesByKey) { @@ -149,12 +153,6 @@ async function updateEntity(entity, _template, unrestricted = false) { toSave.metadata = await denormalizeMetadata(entity.metadata, entity, template); } - if (template._id) { - const fullEntity = { ...currentDoc, ...toSave }; - await denormalizeRelated(fullEntity, template); - } - - if (entity.suggestedMetadata) { toSave.suggestedMetadata = await denormalizeMetadata( entity.suggestedMetadata, @@ -162,6 +160,10 @@ async function updateEntity(entity, _template, unrestricted = false) { template ); } + if (template._id) { + const fullEntity = { ...currentDoc, ...toSave }; + await denormalizeRelated(fullEntity, template); + } return saveFunc(toSave); } @@ -188,6 +190,11 @@ async function updateEntity(entity, _template, unrestricted = false) { if (typeof entity.generatedToc !== 'undefined') { d.generatedToc = entity.generatedToc; } + + if (template._id) { + await denormalizeRelated(d, template); + } + return saveFunc(d); }) ); @@ -760,7 +767,7 @@ export default { await updateDenormalization({ id: valueId, language }, { label: newLabel }, properties); - const transitiveProps = await templates.esteNombreEsUnAskoCambiar(thesaurusId.toString()); + const transitiveProps = await templates.propsThatNeedInheritDenormalization(thesaurusId.toString()); await updateTransitiveDenormalization( { id: valueId, language }, { label: newLabel }, diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index 3c573cea45..b77aa5c1ef 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -463,12 +463,13 @@ describe('Denormalize relationships', () => { await elasticTesting.refresh(); const results = await elasticTesting.getIndexedEntities(); - const [B1en, B1es] = results.filter(r => r.sharedId === 'B1'); + const B1en = results.find(r => r.sharedId === 'B1' && r.language === 'en'); + const B1es = results.find(r => r.sharedId === 'B1' && r.language === 'es'); - expect(B1en.metadata?.relationshipB).toMatchObject([ + expect(B1en?.metadata?.relationshipB).toMatchObject([ { value: 'C1', label: 'C1', inheritedValue: [{ value: 'text' }] }, ]); - expect(B1es.metadata?.relationshipB).toMatchObject([ + expect(B1es?.metadata?.relationshipB).toMatchObject([ { value: 'C1', label: 'new Es title', inheritedValue: [{ value: 'nuevo texto para ES' }] }, ]); }); @@ -479,7 +480,8 @@ describe('Denormalize relationships', () => { await elasticTesting.refresh(); const results = await elasticTesting.getIndexedEntities(); - const [A1en, A1es] = results.filter(r => r.sharedId === 'A1'); + const A1en = results.find(r => r.sharedId === 'A1' && r.language === 'en'); + const A1es = results.find(r => r.sharedId === 'A1' && r.language === 'es'); expect(A1en?.metadata?.relationshipA).toMatchObject([ { value: 'B1', inheritedValue: [{ label: 'C1' }] }, @@ -539,7 +541,8 @@ describe('Denormalize relationships', () => { await elasticTesting.refresh(); const results = await elasticTesting.getIndexedEntities(); - const [A1en, A1es] = results.filter(r => r.sharedId === 'A1'); + const A1en = results.find(r => r.sharedId === 'A1' && r.language === 'en'); + const A1es = results.find(r => r.sharedId === 'A1' && r.language === 'es'); expect(A1en?.metadata?.relationshipA).toMatchObject([ { value: 'B1', inheritedValue: [{ value: 'T1' }, { value: 'T2' }] }, diff --git a/app/api/templates/templates.ts b/app/api/templates/templates.ts index 00ae41bbeb..9341a880f5 100644 --- a/app/api/templates/templates.ts +++ b/app/api/templates/templates.ts @@ -67,7 +67,17 @@ const updateTranslation = async (currentTemplate: TemplateSchema, template: Temp }; export default { - async esteNombreEsUnAskoCambiar(contentId: string) { + async propsThatNeedDenormalization(templateId: string) { + return ( + await model.get({ + $or: [{ 'properties.content': templateId }, { 'properties.content': '' }], + }) + ) + .reduce((m, t) => m.concat(t.properties || []), []) + .filter(p => templateId === p.content?.toString() || p.content === ''); + }, + + async propsThatNeedInheritDenormalization(contentId: string) { const properties = (await model.get({ 'properties.content': contentId })) .reduce((m, t) => m.concat(t.properties || []), []) .filter(p => contentId === p.content?.toString()); From 5332ba1d4ff4dffedc88bc8f4515a333c0b39d38 Mon Sep 17 00:00:00 2001 From: Daneryl Date: Fri, 11 Jun 2021 17:00:56 +0200 Subject: [PATCH 25/27] adapt to inheritProperty on master --- app/api/entities/denormalize.ts | 2 +- app/api/entities/entities.js | 6 ++++-- app/api/entities/specs/entities.spec.js | 1 - app/api/templates/templates.ts | 2 +- app/api/utils/fixturesFactory.ts | 4 +++- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/api/entities/denormalize.ts b/app/api/entities/denormalize.ts index 37d28e73c8..6cf62374b0 100644 --- a/app/api/entities/denormalize.ts +++ b/app/api/entities/denormalize.ts @@ -82,7 +82,7 @@ export const denormalizeRelated = async ( await Promise.all( properties.map(async prop => { const inheritProperty = (template.properties || []).find( - p => prop.inherit?.property === p._id?.toString() + p => prop.inheritProperty === p._id?.toString() ); return updateDenormalization( { diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index c5e79afa4f..48c1049af7 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -763,11 +763,13 @@ export default { }) ) .reduce((m, t) => m.concat(t.properties), []) - .filter(p => thesaurusId === p.content?.toString()); + .filter(p => thesaurusId === p.content.toString()); await updateDenormalization({ id: valueId, language }, { label: newLabel }, properties); - const transitiveProps = await templates.propsThatNeedInheritDenormalization(thesaurusId.toString()); + const transitiveProps = await templates.propsThatNeedInheritDenormalization( + thesaurusId.toString() + ); await updateTransitiveDenormalization( { id: valueId, language }, { label: newLabel }, diff --git a/app/api/entities/specs/entities.spec.js b/app/api/entities/specs/entities.spec.js index 26cc694d86..24f38f925d 100644 --- a/app/api/entities/specs/entities.spec.js +++ b/app/api/entities/specs/entities.spec.js @@ -15,7 +15,6 @@ import { UserRole } from 'shared/types/userSchema'; import entities from '../entities.js'; import fixtures, { batmanFinishesId, - shared2, templateId, templateChangingNames, syncPropertiesEntityId, diff --git a/app/api/templates/templates.ts b/app/api/templates/templates.ts index 9341a880f5..2368589a5e 100644 --- a/app/api/templates/templates.ts +++ b/app/api/templates/templates.ts @@ -84,7 +84,7 @@ export default { return ( await model.get({ - 'properties.inherit.property': { + 'properties.inheritProperty': { $in: properties.map(p => p._id?.toString()).filter(v => v), }, }) diff --git a/app/api/utils/fixturesFactory.ts b/app/api/utils/fixturesFactory.ts index f564a3bca2..998c05d3b1 100644 --- a/app/api/utils/fixturesFactory.ts +++ b/app/api/utils/fixturesFactory.ts @@ -44,7 +44,9 @@ export function getFixturesFactory() { inherit(name: string, content: string, property: string, props = {}): PropertySchema { return this.relationshipProp(name, content, { - inherit: { property: idMapper(property).toString() }, + // inherit: { property: idMapper(property).toString() }, + inheritProperty: idMapper(property).toString(), + inherit: true, ...props, }); }, From 4d32f68c3d8849513a743376160dc140f3d4c55b Mon Sep 17 00:00:00 2001 From: Daneryl Date: Sat, 12 Jun 2021 08:54:08 +0200 Subject: [PATCH 26/27] fixed thesauri name change denormalization --- app/api/entities/entities.js | 3 ++- app/api/entities/specs/denormalization.spec.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index 48c1049af7..d52603a7e4 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -763,13 +763,14 @@ export default { }) ) .reduce((m, t) => m.concat(t.properties), []) - .filter(p => thesaurusId === p.content.toString()); + .filter(p => p.content && thesaurusId === p.content.toString()); await updateDenormalization({ id: valueId, language }, { label: newLabel }, properties); const transitiveProps = await templates.propsThatNeedInheritDenormalization( thesaurusId.toString() ); + await updateTransitiveDenormalization( { id: valueId, language }, { label: newLabel }, diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index b77aa5c1ef..f6a4b45f6d 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -272,6 +272,7 @@ describe('Denormalize relationships', () => { factory.property('multiselect', 'multiselect', { content: factory.id('thesauri').toString(), }), + factory.property('property_without_content'), ]), ], dictionaries: [factory.thesauri('thesauri', ['T1', 'T2', 'T3'])], From c55eef542cb24429841c476e82f596cec55e9782 Mon Sep 17 00:00:00 2001 From: Daneryl Date: Mon, 14 Jun 2021 10:16:52 +0200 Subject: [PATCH 27/27] same relationship and different inherit case --- app/api/entities/denormalize.ts | 12 +++++-- app/api/entities/entities.js | 2 +- .../entities/specs/denormalization.spec.ts | 34 ++++++++++++------- app/api/templates/templates.ts | 18 +++++++--- 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/app/api/entities/denormalize.ts b/app/api/entities/denormalize.ts index 6cf62374b0..4e1dcce4e1 100644 --- a/app/api/entities/denormalize.ts +++ b/app/api/entities/denormalize.ts @@ -15,17 +15,22 @@ interface Changes { interface Params { id: string; language?: string; + template?: string; } export const updateDenormalization = async ( - { id, language }: Params, + { id, language, template }: Params, changes: Changes, properties: PropertySchema[] ) => Promise.all( properties.map(async property => model.updateMany( - { ...(language ? { language } : {}), [`metadata.${property.name}.value`]: id }, + { + ...(template ? { template } : {}), + ...(language ? { language } : {}), + [`metadata.${property.name}.value`]: id, + }, { $set: Object.keys(changes).reduce( (set, prop) => ({ @@ -68,7 +73,7 @@ export const denormalizeRelated = async ( throw new Error('denormalization requires an entity with title, sharedId and language'); } - const transitiveProperties = await templates.propsThatNeedInheritDenormalization( + const transitiveProperties = await templates.propsThatNeedTransitiveDenormalization( template._id.toString() ); const properties = await templates.propsThatNeedDenormalization(template._id.toString()); @@ -89,6 +94,7 @@ export const denormalizeRelated = async ( // @ts-ignore we have a sharedId guard, why ts does not like this ? bug ? id: entity.sharedId, language: entity.language, + template: prop.template, }, { ...(inheritProperty ? { inheritedValue: entity.metadata?.[inheritProperty.name] } : {}), diff --git a/app/api/entities/entities.js b/app/api/entities/entities.js index d52603a7e4..47d09e7d52 100644 --- a/app/api/entities/entities.js +++ b/app/api/entities/entities.js @@ -767,7 +767,7 @@ export default { await updateDenormalization({ id: valueId, language }, { label: newLabel }, properties); - const transitiveProps = await templates.propsThatNeedInheritDenormalization( + const transitiveProps = await templates.propsThatNeedTransitiveDenormalization( thesaurusId.toString() ); diff --git a/app/api/entities/specs/denormalization.spec.ts b/app/api/entities/specs/denormalization.spec.ts index f6a4b45f6d..ba94043824 100644 --- a/app/api/entities/specs/denormalization.spec.ts +++ b/app/api/entities/specs/denormalization.spec.ts @@ -132,15 +132,20 @@ describe('Denormalize relationships', () => { it('should update title and text property denormalized on related entities from 2 different templates', async () => { const fixtures: DBFixture = { templates: [ - factory.template('templateA', [factory.property('text')]), - factory.template('templateB', [factory.inherit('relationship_b', 'templateA', 'text')]), - factory.template('templateC', [factory.inherit('relationship_c', 'templateA', 'text')]), + factory.template('templateA', [ + factory.property('text'), + factory.property('another_text'), + ]), + factory.template('templateB', [factory.inherit('relationship', 'templateA', 'text')]), + factory.template('templateC', [ + factory.inherit('relationship', 'templateA', 'another_text'), + ]), ], entities: [ factory.entity('A1', 'templateA'), - factory.entity('B1', 'templateB', { relationship_b: [factory.metadataValue('A1')] }), - factory.entity('B2', 'templateB', { relationship_b: [factory.metadataValue('A1')] }), - factory.entity('C1', 'templateC', { relationship_c: [factory.metadataValue('A1')] }), + factory.entity('B1', 'templateB', { relationship: [factory.metadataValue('A1')] }), + factory.entity('B2', 'templateB', { relationship: [factory.metadataValue('A1')] }), + factory.entity('C1', 'templateC', { relationship: [factory.metadataValue('A1')] }), ], }; @@ -148,7 +153,10 @@ describe('Denormalize relationships', () => { await modifyEntity('A1', { title: 'new A1', - metadata: { text: [{ value: 'text 1 changed' }] }, + metadata: { + text: [{ value: 'text changed' }], + another_text: [{ value: 'another_text changed' }], + }, }); const [relatedB1, relatedB2, relatedC] = [ @@ -157,16 +165,16 @@ describe('Denormalize relationships', () => { await entities.getById('C1', 'en'), ]; - expect(relatedB1?.metadata?.relationship_b).toMatchObject([ - { label: 'new A1', inheritedValue: [{ value: 'text 1 changed' }] }, + expect(relatedB1?.metadata?.relationship).toMatchObject([ + { label: 'new A1', inheritedValue: [{ value: 'text changed' }] }, ]); - expect(relatedB2?.metadata?.relationship_b).toMatchObject([ - { label: 'new A1', inheritedValue: [{ value: 'text 1 changed' }] }, + expect(relatedB2?.metadata?.relationship).toMatchObject([ + { label: 'new A1', inheritedValue: [{ value: 'text changed' }] }, ]); - expect(relatedC?.metadata?.relationship_c).toMatchObject([ - { label: 'new A1', inheritedValue: [{ value: 'text 1 changed' }] }, + expect(relatedC?.metadata?.relationship).toMatchObject([ + { label: 'new A1', inheritedValue: [{ value: 'another_text changed' }] }, ]); }); diff --git a/app/api/templates/templates.ts b/app/api/templates/templates.ts index 2368589a5e..3d226bae2c 100644 --- a/app/api/templates/templates.ts +++ b/app/api/templates/templates.ts @@ -72,12 +72,22 @@ export default { await model.get({ $or: [{ 'properties.content': templateId }, { 'properties.content': '' }], }) - ) - .reduce((m, t) => m.concat(t.properties || []), []) - .filter(p => templateId === p.content?.toString() || p.content === ''); + ).reduce<{ [k: string]: string | undefined }[]>( + (m, t) => + m.concat( + t.properties + ?.filter(p => templateId === p.content?.toString() || p.content === '') + .map(p => ({ + name: p.name, + inheritProperty: p.inheritProperty, + template: t._id, + })) || [] + ), + [] + ); }, - async propsThatNeedInheritDenormalization(contentId: string) { + async propsThatNeedTransitiveDenormalization(contentId: string) { const properties = (await model.get({ 'properties.content': contentId })) .reduce((m, t) => m.concat(t.properties || []), []) .filter(p => contentId === p.content?.toString());