Skip to content

Commit

Permalink
v1.144.0
Browse files Browse the repository at this point in the history
  • Loading branch information
varovaro committed Nov 27, 2023
2 parents 44f0e79 + 00832c0 commit 6c91cbb
Show file tree
Hide file tree
Showing 40 changed files with 1,324 additions and 805 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ Read the [user guide](https://uwazi.io/page/9852italrtk/support)
Before anything else you will need to install the application dependencies:

- **NodeJs >= 18.16.1** For ease of update, use nvm: https://github.com/creationix/nvm.
- **ElasticSearch 7.17.6** https://www.elastic.co/downloads/past-releases/elasticsearch-7-17-6 Please note that ElasticSearch requires java. Follow the instructions to install the package manually, you also probably need to disable ml module in the ElasticSearch config file:
- **ElasticSearch 7.17.6** https://www.elastic.co/downloads/past-releases/elasticsearch-7-17-6 Please note that ElasticSearch requires Java. Follow the instructions to install the package manually, you also probably need to disable ml module in the ElasticSearch config file:
`xpack.ml.enabled: false`
- **ICU Analysis Plugin (recommended)** [installation](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-icu.html#analysis-icu) Adds support for number sorting in texts and solves other language sorting nuances. This option is activated by setting the env var USE_ELASTIC_ICU=true before running the server (defaults to false/unset).
- **MongoDB 4.2** https://docs.mongodb.com/v4.2/installation/ . If you have a previous version installed, please follow instructions on how to [upgrade here](https://docs.mongodb.com/manual/release-notes/4.2-upgrade-standalone/). The new mongosh dependency needs to be added [installation](https://www.mongodb.com/docs/mongodb-shell/).
- **MongoDB 4.2** https://docs.mongodb.com/v4.2/installation/ . If you have a previous version installed, please follow the instructions on how to [upgrade here](https://docs.mongodb.com/manual/release-notes/4.2-upgrade-standalone/). The new mongosh dependency needs to be added [installation](https://www.mongodb.com/docs/mongodb-shell/).
- **Yarn** https://yarnpkg.com/en/docs/install.
- **pdftotext (Poppler)** tested to work on version 0.86 but its recommended to use the latest available for your platform https://poppler.freedesktop.org/. Make sure to **install libjpeg-dev** if you build from source.
- **pdftotext (Poppler)** tested to work on version 0.86 but it's recommended to use the latest available for your platform https://poppler.freedesktop.org/. Make sure to **install libjpeg-dev** if you build from source.

# Production

Expand Down Expand Up @@ -59,7 +59,7 @@ If the main Uwazi repository had already been cloned/downloaded and now you want
$ git submodule update --init
```

There may be an issue with pngquant not running correctly. If you encounter this issue, you are probably missing library **libpng-dev**. Please run:
There may be an issue with pngquant not running correctly. If you encounter this issue, you are probably missing the library **libpng-dev**. Please run:

```
$ sudo rm -rf node_modules
Expand Down Expand Up @@ -89,7 +89,7 @@ This will launch a webpack server and nodemon app server for hot reloading any c
$ yarn webpack-server
```

This will launch a webpack server. You can also pass `--analyze`to get a detailed info of the webpack build.
This will launch a webpack server. You can also pass `--analyze`to get detailed info on the webpack build.

### Testing

Expand All @@ -109,7 +109,7 @@ Some suites need MongoDB configured in Replica Set mode to run properly. The pro

For End-to-End testing, we have a full set of fixtures that test the overall functionality. Be advised that, for the time being, these tests are run ON THE SAME DATABASE as the default database (uwazi_developmet), so running these tests will DELETE any existing data and replace it with the testing fixtures. DO NOT RUN ON PRODUCTION ENVIRONMENTS!

Running end to end tests require a running Uwazi app.
Running end to end tests requires a running Uwazi app.

Running tests with Nightmare

Expand All @@ -135,11 +135,11 @@ On a different console tab, run
$ yarn e2e-puppeteer
```

Note that if you already have an instance running, this will likely throw an error of ports already been used. Only one instance of Uwazi may be run in a the same port at the same time.
Note that if you already have an instance running, this will likely throw an error of ports already been used. Only one instance of Uwazi may be run in the same port at the same time.

### Default login

The application's default log in is admin / change this password now
The application's default login is admin / change this password now

Note the subtle nudge ;)

Expand Down
10 changes: 6 additions & 4 deletions app/api/entities/entities.js
Original file line number Diff line number Diff line change
Expand Up @@ -727,15 +727,17 @@ export default {

/** Propagate the deletion metadata.value id to all entity metadata. */
async deleteFromMetadata(deletedId, propertyContent, propTypes) {
const allTemplates = await templates.get({ 'properties.content': propertyContent });
const allTemplates = await templates.get({
'properties.content': { $in: [propertyContent, ''] },
});
const allProperties = allTemplates.reduce((m, t) => m.concat(t.properties), []);
const properties = allProperties.filter(p => propTypes.includes(p.type));
const query = { $or: [] };
const changes = {};
const contentMatches = p =>
(p.content && p.content.toString() === propertyContent.toString()) || p.content === '';
query.$or = properties
.filter(
p => propertyContent && p.content && propertyContent.toString() === p.content.toString()
)
.filter(p => propertyContent && contentMatches(p))
.map(property => {
const p = {};
p[`metadata.${property.name}.value`] = deletedId;
Expand Down
19 changes: 19 additions & 0 deletions app/api/entities/specs/entities.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1323,6 +1323,25 @@ describe('entities', () => {
});
emitSpy.restore();
});

it('should remove the entity from the relationship metadata of related entities', async () => {
await entities.delete('shared2');
const [related] = await db.mongodb
.collection('entities')
.find({ sharedId: 'shared', title: 'Batman finishes' })
.toArray();
expect(related.metadata).toMatchObject({
friends: [],
enemies: [],
});
const [related2] = await db.mongodb
.collection('entities')
.find({ sharedId: 'entityWithOnlyAnyRelationship' })
.toArray();
expect(related2.metadata).toMatchObject({
relationship_to_any_template: [],
});
});
});

describe('addLanguage()', () => {
Expand Down
28 changes: 28 additions & 0 deletions app/api/entities/specs/fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const templateWithEntityAsThesauri = db.id();
const templateWithEntityAsThesauri2 = db.id();
const templateWithOnlySelect = db.id();
const templateWithOnlyMultiselect = db.id();
const templateWithOnlyAnyRelationship = db.id();
const templateChangingNamesProps = {
prop1id: db.id(),
prop2id: db.id(),
Expand Down Expand Up @@ -516,6 +517,19 @@ export default {
some_property: [{ value: 'value3' }],
},
},
{
sharedId: 'entityWithOnlyAnyRelationship',
type: 'entity',
template: templateWithOnlyAnyRelationship,
language: 'en',
title: 'entityWithOnlyAnyRelationship',
published: true,
metadata: {
relationship_to_any_template: [
{ icon: null, label: 'shared2title', type: 'entity', value: 'shared2' },
],
},
},
],
settings: [
{
Expand Down Expand Up @@ -543,6 +557,7 @@ export default {
type: 'relationship',
name: 'friends',
relationType: relationType1,
content: '',
},
{
_id: db.id(),
Expand Down Expand Up @@ -625,6 +640,18 @@ export default {
name: 'entityGetTestTemplate',
properties: [{ _id: db.id(), type: 'text', name: 'some_property' }],
},
{
_id: templateWithOnlyAnyRelationship,
name: 'templateWithOnlyAnyRelationship',
properties: [
{
_id: db.id(),
type: 'relationship',
name: 'relationship_to_any_template',
content: '',
},
],
},
],
connections: [
{ _id: referenceId, entity: 'shared', template: null, hub: hub1, entityData: {} },
Expand Down Expand Up @@ -788,6 +815,7 @@ export {
templateChangingNames,
templateChangingNamesProps,
templateWithEntityAsThesauri,
templateWithOnlyAnyRelationship,
docId1,
shared2,
docId2,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/* eslint-disable no-await-in-loop */
import { Db, FindCursor, ObjectId } from 'mongodb';
import { EntitySchema, TemplateSchema } from '../143-parse-numeric-fields/types';

type UpdateOperation = {
updateOne: {
filter: { _id: ObjectId };
update: { $set: { metadata: EntitySchema['metadata'] } };
};
};

export default {
delta: 149,

name: 'remove_inconsistent_relationships_metadata',

description:
'Removes entries from relationships properties in the metadata that do not have the referenced entity in the database.',

reindex: false,

batchSize: 1000,

updates: [] as UpdateOperation[],

async prepareTemplates(db: Db) {
const templates = await db
.collection<TemplateSchema>('templates')
.find({ 'properties.type': 'relationship' })
.toArray();
const templateIds = templates.map(t => new ObjectId(t._id));
const relationshipProperties = templates
.map(t => t.properties || [])
.flat()
.filter(p => p.type === 'relationship');
const relationshipPropertyNames = new Set(relationshipProperties.map(p => p.name));
return { templateIds, relationshipPropertyNames };
},

async getNextBatch(entities: FindCursor<EntitySchema>) {
const batch: EntitySchema[] = [];
while (await entities.hasNext()) {
const entity = await entities.next();
if (entity) batch.push(entity);
if (batch.length >= this.batchSize) {
break;
}
}
return batch;
},

updateObject(id: ObjectId, metadata: EntitySchema['metadata']): UpdateOperation {
return {
updateOne: {
filter: { _id: id },
update: { $set: { metadata } },
},
};
},

async flushUpdates(db: Db, force = false) {
if (this.updates.length > 0 && (force || this.updates.length >= this.batchSize)) {
await db.collection<EntitySchema>('entities').bulkWrite(this.updates);
this.reindex = true;
this.updates = [];
}
},

getRelatedSharedIds(entities: EntitySchema[], relationshipPropertyNames: Set<string>) {
const relatedSharedIds: Set<string> = new Set();
entities.forEach(entity => {
Object.entries(entity.metadata || {}).forEach(([k, arr]) => {
if (relationshipPropertyNames.has(k)) {
(arr || []).forEach(v => {
if (v.value && typeof v.value === 'string') {
relatedSharedIds.add(v.value);
}
});
}
});
});
return Array.from(relatedSharedIds);
},

async filterExistingSharedIds(db: Db, sharedIds: string[]) {
const existingRelatedSharedIds = await db
.collection<EntitySchema>('entities')
.aggregate([{ $match: { sharedId: { $in: sharedIds } } }, { $project: { sharedId: 1 } }])
.toArray();
return new Set(existingRelatedSharedIds.map(e => e.sharedId));
},

prepareEntityUpdates(
entity: EntitySchema,
relationshipPropertyNames: Set<string>,
existingRelatedSharedIds: Set<string>
) {
let updated = false;
const oldMetadata = entity.metadata || {};
const newMetadata: EntitySchema['metadata'] = { ...oldMetadata };
Object.entries(oldMetadata).forEach(([k, arr]) => {
if (relationshipPropertyNames.has(k)) {
const original = arr || [];
const filtered = original.filter(
v => typeof v.value !== 'string' || existingRelatedSharedIds.has(v.value)
);
newMetadata[k] = filtered;
if (filtered.length !== original.length) {
updated = true;
}
} else {
newMetadata[k] = arr;
}
});
if (updated) {
this.updates.push(this.updateObject(new ObjectId(entity._id!), newMetadata));
}
},

prepareBatchUpdates(
entities: EntitySchema[],
relationshipPropertyNames: Set<string>,
existingRelatedSharedIds: Set<string>
) {
entities.forEach(entity =>
this.prepareEntityUpdates(entity, relationshipPropertyNames, existingRelatedSharedIds)
);
},

async updateEntities(db: Db, templateIds: ObjectId[], relationshipPropertyNames: Set<string>) {
const entities = db
.collection<EntitySchema>('entities')
.find({ template: { $in: templateIds } });

while (await entities.hasNext()) {
const batch = await this.getNextBatch(entities);
const relatedSharedIds = this.getRelatedSharedIds(batch, relationshipPropertyNames);
const existingRelatedSharedIds = await this.filterExistingSharedIds(db, relatedSharedIds);
this.prepareBatchUpdates(batch, relationshipPropertyNames, existingRelatedSharedIds);
await this.flushUpdates(db);
}
await this.flushUpdates(db, true);
},

async up(db: Db) {
process.stdout.write(`${this.name}...\r\n`);

const { templateIds, relationshipPropertyNames } = await this.prepareTemplates(db);

await this.updateEntities(db, templateIds, relationshipPropertyNames);
},
};
Loading

0 comments on commit 6c91cbb

Please sign in to comment.