diff --git a/.github/workflows/ci_e2e_cypress.yml b/.github/workflows/ci_e2e_cypress.yml index 76917fe779..7966bdeb1f 100644 --- a/.github/workflows/ci_e2e_cypress.yml +++ b/.github/workflows/ci_e2e_cypress.yml @@ -100,11 +100,11 @@ jobs: run: sleep 5 && wget --waitretry=5 --retry-connrefused -v http://localhost:3000/ timeout-minutes: 2 - name: Cypress run - uses: cypress-io/github-action@v5 + uses: cypress-io/github-action@v6 with: browser: chrome component: false - config: defaultCommandTimeout=8000,requestTimeout=12000 + config-file: cypress.config.ts timeout-minutes: 25 env: DBHOST: localhost:27017 diff --git a/README.md b/README.md index 011c8de3c2..3cdd18e35a 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ Some suites need MongoDB configured in Replica Set mode to run properly. The pro #### End to End (e2e) -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 exisisting data and replace it with the testing fixtures. DO NOT RUN ON PRODUCTION ENVIRONMENTS! +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. diff --git a/app/api/authorization.v2/database/specs/MongoPermissionsDataSource.spec.ts b/app/api/authorization.v2/database/specs/MongoPermissionsDataSource.spec.ts index f345050f52..1a0b1e10e3 100644 --- a/app/api/authorization.v2/database/specs/MongoPermissionsDataSource.spec.ts +++ b/app/api/authorization.v2/database/specs/MongoPermissionsDataSource.spec.ts @@ -1,12 +1,11 @@ import { Db } from 'mongodb'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; -import { getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { MongoResultSet } from 'api/common.v2/database/MongoResultSet'; import { getFixturesFactory } from 'api/utils/fixturesFactory'; import { testingEnvironment } from 'api/utils/testingEnvironment'; import testingDB, { DBFixture } from 'api/utils/testing_db'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { MongoPermissionsDataSource } from '../MongoPermissionsDataSource'; const factory = getFixturesFactory(); @@ -82,10 +81,7 @@ describe('MongoPermissionsDataSource', () => { ])( 'should return the permissions for entities with the given sharedIds', async ({ sharedIds, expected }) => { - const dataSource = new MongoPermissionsDataSource( - db!, - new MongoTransactionManager(getClient()) - ); + const dataSource = new MongoPermissionsDataSource(db!, DefaultTransactionManager()); const resultSet = dataSource.getByEntities(sharedIds); expect(resultSet).toBeInstanceOf(MongoResultSet); const result = await resultSet.all(); diff --git a/app/api/authorization.v2/services/specs/AuthorizationService.spec.ts b/app/api/authorization.v2/services/specs/AuthorizationService.spec.ts index 4fbe646419..7607d046f5 100644 --- a/app/api/authorization.v2/services/specs/AuthorizationService.spec.ts +++ b/app/api/authorization.v2/services/specs/AuthorizationService.spec.ts @@ -1,13 +1,13 @@ import { MongoPermissionsDataSource } from 'api/authorization.v2/database/MongoPermissionsDataSource'; import { UnauthorizedError } from 'api/authorization.v2/errors/UnauthorizedError'; -import { getClient, getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; +import { getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { Relationship } from 'api/relationships.v2/model/Relationship'; import { MongoRelationshipsDataSource } from 'api/relationships.v2/database/MongoRelationshipsDataSource'; import { User, UserRole } from 'api/users.v2/model/User'; import { getFixturesFactory } from 'api/utils/fixturesFactory'; import { testingEnvironment } from 'api/utils/testingEnvironment'; import { DBFixture } from 'api/utils/testing_db'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { AccessLevels, AuthorizationService } from '../AuthorizationService'; const factory = getFixturesFactory(); @@ -58,7 +58,7 @@ describe("When there's no authenticated user", () => { describe('and the entity is not public', () => { it('should return false', async () => { const auth = new AuthorizationService( - new MongoPermissionsDataSource(getConnection(), new MongoTransactionManager(getClient())), + new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()), undefined ); expect(await auth.isAuthorized('read', ['entity1'])).toBe(false); @@ -67,7 +67,7 @@ describe("When there's no authenticated user", () => { it('should throw an error on validation', async () => { const auth = new AuthorizationService( - new MongoPermissionsDataSource(getConnection(), new MongoTransactionManager(getClient())), + new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()), undefined ); await expect(async () => auth.validateAccess('read', ['entity1'])).rejects.toThrow( @@ -82,7 +82,7 @@ describe("When there's no authenticated user", () => { describe('and the entity is public', () => { it('should only allow to read', async () => { const auth = new AuthorizationService( - new MongoPermissionsDataSource(getConnection(), new MongoTransactionManager(getClient())), + new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()), undefined ); expect(await auth.isAuthorized('read', ['entity3'])).toBe(true); @@ -93,7 +93,7 @@ describe("When there's no authenticated user", () => { describe('and not all the entities are public', () => { it('should not allow to read nor write', async () => { const auth = new AuthorizationService( - new MongoPermissionsDataSource(getConnection(), new MongoTransactionManager(getClient())), + new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()), undefined ); expect(await auth.isAuthorized('read', ['entity3', 'entity1'])).toBe(false); @@ -103,7 +103,7 @@ describe("When there's no authenticated user", () => { it('should allow empty read', async () => { const auth = new AuthorizationService( - new MongoPermissionsDataSource(getConnection(), new MongoTransactionManager(getClient())), + new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()), undefined ); expect(await auth.isAuthorized('read', [])).toBe(true); @@ -116,7 +116,7 @@ describe("When there's an authenticated user", () => { it('should return true', async () => { const adminUser = new User(factory.id('admin').toHexString(), role, []); const auth = new AuthorizationService( - new MongoPermissionsDataSource(getConnection(), new MongoTransactionManager(getClient())), + new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()), adminUser ); expect(await auth.isAuthorized('read', ['entity1'])).toBe(true); @@ -128,7 +128,7 @@ describe("When there's an authenticated user", () => { it('should not throw an error', async () => { const adminUser = new User(factory.id('admin').toHexString(), role, []); const auth = new AuthorizationService( - new MongoPermissionsDataSource(getConnection(), new MongoTransactionManager(getClient())), + new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()), adminUser ); @@ -161,7 +161,7 @@ describe("When there's an authenticated user", () => { 'should return [$result] if [$user] wants to [$level] from/to $entities', async ({ user, entities, level, result }) => { const auth = new AuthorizationService( - new MongoPermissionsDataSource(getConnection(), new MongoTransactionManager(getClient())), + new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()), new User(factory.id(user).toHexString(), 'collaborator', []) ); @@ -175,7 +175,7 @@ describe("When there's an authenticated user", () => { { entities: ['entity3'], level: 'write', result: true }, ])('should consider the user groups', async ({ entities, level, result }) => { const auth = new AuthorizationService( - new MongoPermissionsDataSource(getConnection(), new MongoTransactionManager(getClient())), + new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()), new User(factory.id('grouped user').toHexString(), 'collaborator', [ factory.id('group1').toHexString(), factory.id('group2').toHexString(), @@ -246,7 +246,7 @@ describe('filterEntities()', () => { 'should filter entities for a/an $usertype to $level', async ({ user, level, expectedResult }) => { const auth = new AuthorizationService( - new MongoPermissionsDataSource(getConnection(), new MongoTransactionManager(getClient())), + new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()), user ); @@ -261,10 +261,7 @@ describe('filterRelationships()', () => { let allRelationships: Relationship[]; beforeAll(async () => { - const ds = new MongoRelationshipsDataSource( - getConnection(), - new MongoTransactionManager(getClient()) - ); + const ds = new MongoRelationshipsDataSource(getConnection(), DefaultTransactionManager()); allRelationships = await ds.getAll().all(); }); @@ -300,7 +297,7 @@ describe('filterRelationships()', () => { }, ])('should filter for $case', async ({ user, level, expectedIds }) => { const auth = new AuthorizationService( - new MongoPermissionsDataSource(getConnection(), new MongoTransactionManager(getClient())), + new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()), user ); @@ -321,7 +318,7 @@ describe('filterRelationships()', () => { ])('$role should be able to $level everything', async ({ role: username, level }) => { const user = new User(factory.idString(username), username, []); const auth = new AuthorizationService( - new MongoPermissionsDataSource(getConnection(), new MongoTransactionManager(getClient())), + new MongoPermissionsDataSource(getConnection(), DefaultTransactionManager()), user ); diff --git a/app/api/common.v2/database/MongoTransactionManager.ts b/app/api/common.v2/database/MongoTransactionManager.ts index 62f3d54131..c92923538f 100644 --- a/app/api/common.v2/database/MongoTransactionManager.ts +++ b/app/api/common.v2/database/MongoTransactionManager.ts @@ -1,18 +1,22 @@ import { MongoClient, ClientSession } from 'mongodb'; +import { Logger } from 'api/log.v2/contracts/Logger'; import { TransactionManager } from '../contracts/TransactionManager'; export class MongoTransactionManager implements TransactionManager { private mongoClient: MongoClient; + private logger: Logger; + private session?: ClientSession; private onCommitHandlers: ((returnValue: any) => Promise)[]; private finished = false; - constructor(mongoClient: MongoClient) { + constructor(mongoClient: MongoClient, logger: Logger) { this.onCommitHandlers = []; this.mongoClient = mongoClient; + this.logger = logger; } async executeOnCommitHandlers(returnValue: unknown) { @@ -35,6 +39,7 @@ export class MongoTransactionManager implements TransactionManager { this.finished = true; } catch (error) { if (error.hasErrorLabel && error.hasErrorLabel('UnknownTransactionCommitResult')) { + this.logger.debug(error); await this.commitWithRetry(); } else { throw error; @@ -57,7 +62,7 @@ export class MongoTransactionManager implements TransactionManager { await this.commitWithRetry(); return returnValue; } catch (error) { - if (error.code !== 251) { + if (this.session?.inTransaction()) { await this.abortTransaction(); } @@ -70,6 +75,7 @@ export class MongoTransactionManager implements TransactionManager { return await this.runInTransaction(callback); } catch (error) { if (retries > 0 && error.hasErrorLabel && error.hasErrorLabel('TransientTransactionError')) { + this.logger.debug(error); return this.runWithRetry(callback, retries - 1); } diff --git a/app/api/common.v2/database/data_source_defaults.ts b/app/api/common.v2/database/data_source_defaults.ts index 8268b03075..4295e3ca1c 100644 --- a/app/api/common.v2/database/data_source_defaults.ts +++ b/app/api/common.v2/database/data_source_defaults.ts @@ -1,3 +1,4 @@ +import { DefaultLogger } from 'api/log.v2/infrastructure/StandardLogger'; import { IdGenerator } from '../contracts/IdGenerator'; import { getClient } from './getConnectionForCurrentTenant'; import { MongoIdHandler } from './MongoIdGenerator'; @@ -5,7 +6,8 @@ import { MongoTransactionManager } from './MongoTransactionManager'; const DefaultTransactionManager = () => { const client = getClient(); - return new MongoTransactionManager(client); + const logger = DefaultLogger(); + return new MongoTransactionManager(client, logger); }; const DefaultIdGenerator: IdGenerator = MongoIdHandler; diff --git a/app/api/common.v2/database/specs/MongoDataSource.spec.ts b/app/api/common.v2/database/specs/MongoDataSource.spec.ts index fdb76a5ec2..6c39148244 100644 --- a/app/api/common.v2/database/specs/MongoDataSource.spec.ts +++ b/app/api/common.v2/database/specs/MongoDataSource.spec.ts @@ -3,8 +3,8 @@ import testingDB from 'api/utils/testing_db'; import { testingEnvironment } from 'api/utils/testingEnvironment'; import { ObjectId } from 'mongodb'; import { MongoDataSource } from '../MongoDataSource'; -import { MongoTransactionManager } from '../MongoTransactionManager'; -import { getClient, getConnection } from '../getConnectionForCurrentTenant'; +import { getConnection } from '../getConnectionForCurrentTenant'; +import { DefaultTransactionManager } from '../data_source_defaults'; const blankState = [ { @@ -133,7 +133,7 @@ describe('session scoped collection', () => { it.each(casesForUpdates)( '$method should write changes transactionally', async ({ callback, expectedOnAbort, expectedOnSuccess }) => { - const transactionManager1 = new MongoTransactionManager(getClient()); + const transactionManager1 = DefaultTransactionManager(); const dataSource1 = new DataSource(getConnection(), transactionManager1); try { @@ -150,7 +150,7 @@ describe('session scoped collection', () => { ); } - const transactionManager2 = new MongoTransactionManager(getClient()); + const transactionManager2 = DefaultTransactionManager(); const dataSource2 = new DataSource(getConnection(), transactionManager2); await transactionManager2.run(async () => { @@ -203,7 +203,7 @@ describe('session scoped collection', () => { it.each(casesForReads)( '$method should read data from the transaction', async ({ callback, expectedInTransaction }) => { - const transactionManager1 = new MongoTransactionManager(getClient()); + const transactionManager1 = DefaultTransactionManager(); const dataSource1 = new DataSource(getConnection(), transactionManager1); let result; @@ -238,7 +238,7 @@ describe('session scoped collection', () => { it.each(otherCases)( '$method should return the information according to the transaction state', async ({ callback, expectedInTransaction, expectedNoTransaction }) => { - const transactionManager1 = new MongoTransactionManager(getClient()); + const transactionManager1 = DefaultTransactionManager(); const dataSource1 = new DataSource(getConnection(), transactionManager1); let result; @@ -254,7 +254,7 @@ describe('session scoped collection', () => { expect(result).toEqual(expectedInTransaction); } - const transactionManager2 = new MongoTransactionManager(getClient()); + const transactionManager2 = DefaultTransactionManager(); const dataSource2 = new DataSource(getConnection(), transactionManager2); expect(await callback(dataSource2)).toEqual(expectedNoTransaction); diff --git a/app/api/common.v2/database/specs/MongoDataSourceSync.spec.ts b/app/api/common.v2/database/specs/MongoDataSourceSync.spec.ts index e60962a869..3b75ef45b1 100644 --- a/app/api/common.v2/database/specs/MongoDataSourceSync.spec.ts +++ b/app/api/common.v2/database/specs/MongoDataSourceSync.spec.ts @@ -3,9 +3,9 @@ import { getIdMapper } from 'api/utils/fixturesFactory'; import { testingEnvironment } from 'api/utils/testingEnvironment'; import testingDB from 'api/utils/testing_db'; import { ObjectId } from 'mongodb'; -import { getClient, getConnection } from '../getConnectionForCurrentTenant'; +import { getConnection } from '../getConnectionForCurrentTenant'; import { MongoDataSource } from '../MongoDataSource'; -import { MongoTransactionManager } from '../MongoTransactionManager'; +import { DefaultTransactionManager } from '../data_source_defaults'; const id = getIdMapper(); @@ -642,7 +642,7 @@ describe('collection with automatic log to updatelogs', () => { expectedResult, expectedDBStateOnTransactionError = updateLogsBlankState, }) => { - const transactionManager1 = new MongoTransactionManager(getClient()); + const transactionManager1 = DefaultTransactionManager(); const dataSource1 = new DataSource(getConnection(), transactionManager1); try { @@ -659,7 +659,7 @@ describe('collection with automatic log to updatelogs', () => { ); } - const transactionManager2 = new MongoTransactionManager(getClient()); + const transactionManager2 = DefaultTransactionManager(); const dataSource2 = new DataSource(getConnection(), transactionManager2); const result = await transactionManager2.run(async () => callback(dataSource2)); diff --git a/app/api/common.v2/database/specs/MongoTransactionManager.spec.ts b/app/api/common.v2/database/specs/MongoTransactionManager.spec.ts index 28751e8abc..d2be93b0dc 100644 --- a/app/api/common.v2/database/specs/MongoTransactionManager.spec.ts +++ b/app/api/common.v2/database/specs/MongoTransactionManager.spec.ts @@ -2,8 +2,9 @@ import { getIdMapper } from 'api/utils/fixturesFactory'; import { testingEnvironment } from 'api/utils/testingEnvironment'; import testingDB from 'api/utils/testing_db'; -import { MongoError } from 'mongodb'; -import { getClient } from '../getConnectionForCurrentTenant'; +import { MongoClient, MongoError } from 'mongodb'; +import { StandardLogger } from 'api/log.v2/infrastructure/StandardLogger'; +import { getClient, getTenant } from '../getConnectionForCurrentTenant'; import { MongoTransactionManager } from '../MongoTransactionManager'; const ids = getIdMapper(); @@ -61,9 +62,12 @@ class Transactional3 extends TestBase { } } +const createTransactionManager = (client?: MongoClient) => + new MongoTransactionManager(client ?? getClient(), new StandardLogger(() => {}, getTenant())); + describe('When every operation goes well', () => { it('should be reflected in all of the collections affected', async () => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = createTransactionManager(); const source1 = new Transactional1(transactionManager); const source2 = new Transactional2(transactionManager); const source3 = new Transactional3(transactionManager); @@ -88,7 +92,7 @@ describe('When every operation goes well', () => { describe('When one operation fails', () => { // eslint-disable-next-line max-statements it('should not write any changes to the database and re-throw the error', async () => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = createTransactionManager(); const error = new Error('Simulated error'); const source1 = new Transactional1(transactionManager); const source2 = new Transactional2(transactionManager); @@ -133,7 +137,7 @@ describe('When one operation fails', () => { await testingDB.mongodb?.collection('collection1').createIndex({ name: 1 }, { unique: true }); - const tm = new MongoTransactionManager(getClient()); + const tm = createTransactionManager(); const transactional = new Transactional4(tm); try { @@ -183,7 +187,7 @@ describe('when calling run() when a transaction is running', () => { it.each<(typeof cases)[number]>(cases)( 'should throw "transaction in progress"', async ({ cb }) => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = createTransactionManager(); try { await cb(transactionManager); @@ -196,7 +200,7 @@ describe('when calling run() when a transaction is running', () => { describe('when calling run() after the transaction was commited', () => { it('should throw "transaction finished"', async () => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = createTransactionManager(); try { await transactionManager.run(async () => Promise.resolve()); @@ -209,7 +213,7 @@ describe('when calling run() after the transaction was commited', () => { describe('when registering onCommitted event handlers within the run() callback', () => { it('should trigger the handlers after committing', async () => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = createTransactionManager(); const checkpoints = [1]; @@ -235,7 +239,7 @@ describe('when registering onCommitted event handlers within the run() callback' }); it('should not trigger the handlers if aborted', async () => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = createTransactionManager(); const checkpoints = [1]; @@ -265,7 +269,7 @@ describe('when registering onCommitted event handlers within the run() callback' }); it('should manually trigger the onCommit handlers', async () => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = createTransactionManager(); const checkpoints = [1]; @@ -285,7 +289,7 @@ describe('when registering onCommitted event handlers within the run() callback' describe('when registering onCommitted event handlers with the runHandlingOnCommited() call', () => { it('should trigger the handlers with the result after committing', async () => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = createTransactionManager(); const checkpoints = [1]; let transactionResult: string = ''; @@ -312,7 +316,7 @@ describe('when registering onCommitted event handlers with the runHandlingOnComm // https://www.mongodb.com/docs/manual/core/transactions-in-applications/#std-label-transient-transaction-error describe('when the some operation throws a TransientTransactionError', () => { it('should retry the whole transaction', async () => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = createTransactionManager(); let throwed = false; const checkpoints: number[] = []; @@ -334,7 +338,7 @@ describe('when the some operation throws a TransientTransactionError', () => { }); it('should retry the transaction a finite amount of times', async () => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = createTransactionManager(); const errors: Error[] = []; try { await transactionManager.run(async () => { @@ -370,7 +374,7 @@ describe('when the commit operation throws a UnknownTransactionCommitResult', () endSession: () => {}, }), }; - const transactionManager = new MongoTransactionManager(clientMock as any); + const transactionManager = createTransactionManager(clientMock as any); const checkpoints: number[] = []; await transactionManager diff --git a/app/api/entities.v2/database/specs/MongoEntitiesDataSource.spec.ts b/app/api/entities.v2/database/specs/MongoEntitiesDataSource.spec.ts index af910530bc..6b16d872d3 100644 --- a/app/api/entities.v2/database/specs/MongoEntitiesDataSource.spec.ts +++ b/app/api/entities.v2/database/specs/MongoEntitiesDataSource.spec.ts @@ -1,5 +1,4 @@ -import { getClient, getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; +import { getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { partialImplementation } from 'api/common.v2/testing/partialImplementation'; import { MongoSettingsDataSource } from 'api/settings.v2/database/MongoSettingsDataSource'; import { MongoTemplatesDataSource } from 'api/templates.v2/database/MongoTemplatesDataSource'; @@ -8,6 +7,7 @@ import { testingEnvironment } from 'api/utils/testingEnvironment'; import testingDB from 'api/utils/testing_db'; import { MetadataSchema } from 'shared/types/commonTypes'; import { EntitySchema } from 'shared/types/entityType'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { MongoEntitiesDataSource } from '../MongoEntitiesDataSource'; const factory = getFixturesFactory(); @@ -154,7 +154,7 @@ describe('Relationship fields caching strategy', () => { it('should invalidate the cache for the provided entity-property pairs in all languages', async () => { const settingsDsMock = partialImplementation({}); const db = getConnection(); - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); const ds = new MongoEntitiesDataSource( db, new MongoTemplatesDataSource(db, transactionManager), @@ -197,7 +197,7 @@ describe('Relationship fields caching strategy', () => { it('should invalidate the cache for the provided properties in the provided template, in all languages', async () => { const settingsDsMock = partialImplementation({}); const db = getConnection(); - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); const ds = new MongoEntitiesDataSource( db, new MongoTemplatesDataSource(db, transactionManager), @@ -269,7 +269,7 @@ describe('Relationship fields caching strategy', () => { beforeEach(async () => { const settingsDsMock = partialImplementation({}); const db = getConnection(); - const tm = new MongoTransactionManager(getClient()); + const tm = DefaultTransactionManager(); const ds = new MongoEntitiesDataSource( db, new MongoTemplatesDataSource(db, tm), @@ -377,7 +377,7 @@ describe('When checking for the existence of entities', () => { 'should return $expected checking for sharedIds in $ids', async ({ ids, expected }) => { const db = getConnection(); - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); const ds = new MongoEntitiesDataSource( db, new MongoTemplatesDataSource(db, transactionManager), @@ -396,7 +396,7 @@ describe('When checking for the existence of entities', () => { it('should return the sharedIds of the entities that have a particular id within their denormalized values in a metatata prop', async () => { const db = getConnection(); - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); const ds = new MongoEntitiesDataSource( db, new MongoTemplatesDataSource(db, transactionManager), @@ -417,7 +417,7 @@ it('should return the sharedIds of the entities that have a particular id within it('should update the denormalizations value in all related entities', async () => { const db = getConnection(); - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); const ds = new MongoEntitiesDataSource( db, new MongoTemplatesDataSource(db, transactionManager), @@ -495,7 +495,7 @@ it('should update the denormalizations value in all related entities', async () it('should return records containing the obsoleteMetadata', async () => { const db = getConnection(); - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); const ds = new MongoEntitiesDataSource( db, new MongoTemplatesDataSource(db, transactionManager), diff --git a/app/api/entities.v2/services/specs/EntityRelationshipsUpdateService.spec.ts b/app/api/entities.v2/services/specs/EntityRelationshipsUpdateService.spec.ts index f76b267dc4..89d2ad3513 100644 --- a/app/api/entities.v2/services/specs/EntityRelationshipsUpdateService.spec.ts +++ b/app/api/entities.v2/services/specs/EntityRelationshipsUpdateService.spec.ts @@ -1,12 +1,12 @@ import { getFixturesFactory } from 'api/utils/fixturesFactory'; import { testingEnvironment } from 'api/utils/testingEnvironment'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; -import { getClient, getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; +import { getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { MongoEntitiesDataSource } from 'api/entities.v2/database/MongoEntitiesDataSource'; import { MongoSettingsDataSource } from 'api/settings.v2/database/MongoSettingsDataSource'; import { MongoTemplatesDataSource } from 'api/templates.v2/database/MongoTemplatesDataSource'; import { MongoRelationshipsDataSource } from 'api/relationships.v2/database/MongoRelationshipsDataSource'; import testingDB from 'api/utils/testing_db'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { EntityRelationshipsUpdateService } from '../EntityRelationshipsUpdateService'; const factory = getFixturesFactory(); @@ -136,7 +136,7 @@ afterAll(async () => { }); function buildService() { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); const settingsDataSource = new MongoSettingsDataSource(getConnection(), transactionManager); const templateDataSource = new MongoTemplatesDataSource(getConnection(), transactionManager); const entityDataSource = new MongoEntitiesDataSource( diff --git a/app/api/entities/v2_support.ts b/app/api/entities/v2_support.ts index 87059867f5..5c74c71bdf 100644 --- a/app/api/entities/v2_support.ts +++ b/app/api/entities/v2_support.ts @@ -1,14 +1,13 @@ import { DenormalizationService } from 'api/relationships.v2/services/service_factories'; import { DefaultSettingsDataSource } from 'api/settings.v2/database/data_source_defaults'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; -import { getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { EntitySchema } from 'shared/types/entityType'; import { propertyTypes } from 'shared/propertyTypes'; import { TemplateSchema } from 'shared/types/templateType'; import { DefaultRelationshipDataSource } from 'api/relationships.v2/database/data_source_defaults'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; const deleteRelatedNewRelationships = async (sharedId: string) => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); if (await DefaultSettingsDataSource(transactionManager).readNewRelationshipsAllowed()) { const datasource = DefaultRelationshipDataSource(transactionManager); await datasource.deleteByEntities([sharedId]); @@ -22,7 +21,7 @@ const denormalizeAfterEntityCreation = async ({ sharedId: string; language: string; }) => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); if (await DefaultSettingsDataSource(transactionManager).readNewRelationshipsAllowed()) { const denormalizationService = await DenormalizationService(transactionManager); await denormalizationService.denormalizeAfterCreatingEntities([sharedId], language); @@ -37,7 +36,7 @@ const denormalizeAfterEntityUpdate = async ({ sharedId: string; language: string; }) => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); if (await DefaultSettingsDataSource(transactionManager).readNewRelationshipsAllowed()) { const denormalizationService = await DenormalizationService(transactionManager); await denormalizationService.denormalizeAfterUpdatingEntities([sharedId], language); diff --git a/app/api/files.v2/database/specs/MongoFilesDataSource.spec.ts b/app/api/files.v2/database/specs/MongoFilesDataSource.spec.ts index 15bdb5fb67..3704efae91 100644 --- a/app/api/files.v2/database/specs/MongoFilesDataSource.spec.ts +++ b/app/api/files.v2/database/specs/MongoFilesDataSource.spec.ts @@ -1,7 +1,7 @@ -import { getConnection, getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; +import { getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { getFixturesFactory } from 'api/utils/fixturesFactory'; import { testingEnvironment } from 'api/utils/testingEnvironment'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { MongoFilesDataSource } from '../MongoFilesDataSource'; const factory = getFixturesFactory(); @@ -24,7 +24,7 @@ afterAll(async () => { describe('MongoFilesDataSource', () => { it('should return true if the file exists and belongs to the entity', async () => { - const ds = new MongoFilesDataSource(getConnection(), new MongoTransactionManager(getClient())); + const ds = new MongoFilesDataSource(getConnection(), DefaultTransactionManager()); expect( await ds.filesExistForEntities([ diff --git a/app/api/files/v2_support.ts b/app/api/files/v2_support.ts index 4a82e183e9..d22d0ee43a 100644 --- a/app/api/files/v2_support.ts +++ b/app/api/files/v2_support.ts @@ -1,12 +1,11 @@ -import { getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { DefaultRelationshipDataSource } from 'api/relationships.v2/database/data_source_defaults'; import { DenormalizationService } from 'api/relationships.v2/services/service_factories'; import { DefaultSettingsDataSource } from 'api/settings.v2/database/data_source_defaults'; export const V2 = { async deleteTextReferencesToFiles(_ids: string[]) { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); if (!(await DefaultSettingsDataSource(transactionManager).readNewRelationshipsAllowed())) { return; diff --git a/app/api/i18n.v2/services/specs/CreateTranslationsService.spec.ts b/app/api/i18n.v2/services/specs/CreateTranslationsService.spec.ts index 9316f7fc0f..edebd43300 100644 --- a/app/api/i18n.v2/services/specs/CreateTranslationsService.spec.ts +++ b/app/api/i18n.v2/services/specs/CreateTranslationsService.spec.ts @@ -1,5 +1,3 @@ -import { getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; import { DefaultTranslationsDataSource } from 'api/i18n.v2/database/data_source_defaults'; import { LanguageDoesNotExist, @@ -10,6 +8,7 @@ import { getFixturesFactory } from 'api/utils/fixturesFactory'; import { testingEnvironment } from 'api/utils/testingEnvironment'; import testingDB, { DBFixture } from 'api/utils/testing_db'; import { ObjectId } from 'mongodb'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { CreateTranslationsService } from '../CreateTranslationsService'; import { ValidateTranslationsService } from '../ValidateTranslationsService'; @@ -17,7 +16,7 @@ const collectionInDb = (collection = 'translationsV2') => testingDB.mongodb?.collection(collection)!; const createService = () => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); return new CreateTranslationsService( DefaultTranslationsDataSource(transactionManager), new ValidateTranslationsService( diff --git a/app/api/i18n.v2/services/specs/DeleteTranslationsService.spec.ts b/app/api/i18n.v2/services/specs/DeleteTranslationsService.spec.ts index 8bc84dbbd9..ed1d60aa4a 100644 --- a/app/api/i18n.v2/services/specs/DeleteTranslationsService.spec.ts +++ b/app/api/i18n.v2/services/specs/DeleteTranslationsService.spec.ts @@ -1,16 +1,15 @@ -import { getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; import { DefaultTranslationsDataSource } from 'api/i18n.v2/database/data_source_defaults'; import { getFixturesFactory } from 'api/utils/fixturesFactory'; import { testingEnvironment } from 'api/utils/testingEnvironment'; import testingDB, { DBFixture } from 'api/utils/testing_db'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { DeleteTranslationsService } from '../DeleteTranslationsService'; const collectionInDb = (collection = 'translationsV2') => testingDB.mongodb?.collection(collection)!; const createService = () => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); return new DeleteTranslationsService( DefaultTranslationsDataSource(transactionManager), transactionManager diff --git a/app/api/i18n.v2/services/specs/GetTranslationsService.spec.ts b/app/api/i18n.v2/services/specs/GetTranslationsService.spec.ts index 5fb3e28216..610c980129 100644 --- a/app/api/i18n.v2/services/specs/GetTranslationsService.spec.ts +++ b/app/api/i18n.v2/services/specs/GetTranslationsService.spec.ts @@ -1,13 +1,12 @@ -import { getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; import { DefaultTranslationsDataSource } from 'api/i18n.v2/database/data_source_defaults'; import { getFixturesFactory } from 'api/utils/fixturesFactory'; import { testingEnvironment } from 'api/utils/testingEnvironment'; import { DBFixture } from 'api/utils/testing_db'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { GetTranslationsService } from '../GetTranslationsService'; const createService = () => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); return new GetTranslationsService(DefaultTranslationsDataSource(transactionManager)); }; diff --git a/app/api/i18n.v2/services/specs/UpsertTranslationsService.spec.ts b/app/api/i18n.v2/services/specs/UpsertTranslationsService.spec.ts index 2647552be5..8dbeb6f627 100644 --- a/app/api/i18n.v2/services/specs/UpsertTranslationsService.spec.ts +++ b/app/api/i18n.v2/services/specs/UpsertTranslationsService.spec.ts @@ -1,11 +1,10 @@ -import { getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; import { DefaultTranslationsDataSource } from 'api/i18n.v2/database/data_source_defaults'; import { LanguageDoesNotExist } from 'api/i18n.v2/errors/translationErrors'; import { DefaultSettingsDataSource } from 'api/settings.v2/database/data_source_defaults'; import { getFixturesFactory } from 'api/utils/fixturesFactory'; import { testingEnvironment } from 'api/utils/testingEnvironment'; import testingDB, { DBFixture } from 'api/utils/testing_db'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { CreateTranslationsData } from '../CreateTranslationsService'; import { UpsertTranslationsService } from '../UpsertTranslationsService'; import { ValidateTranslationsService } from '../ValidateTranslationsService'; @@ -14,7 +13,7 @@ const collectionInDb = (collection = 'translationsV2') => testingDB.mongodb?.collection(collection)!; const createService = () => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); return new UpsertTranslationsService( DefaultTranslationsDataSource(transactionManager), DefaultSettingsDataSource(transactionManager), diff --git a/app/api/i18n/v2_support.ts b/app/api/i18n/v2_support.ts index b91314b61c..a2c4161d2b 100644 --- a/app/api/i18n/v2_support.ts +++ b/app/api/i18n/v2_support.ts @@ -1,7 +1,6 @@ import { ResultSet } from 'api/common.v2/contracts/ResultSet'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; -import { getClient, getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; +import { getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { MongoTranslationsSyncDataSource } from 'api/i18n.v2/database/MongoTranslationsSyncDataSource'; import { DefaultTranslationsDataSource } from 'api/i18n.v2/database/data_source_defaults'; import { Translation } from 'api/i18n.v2/model/Translation'; @@ -45,7 +44,7 @@ export const resultsToV1TranslationType = async ( tranlationsResult: ResultSet, onlyLanguage?: LanguageISO6391 ) => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); const settings = DefaultSettingsDataSource(transactionManager); let languageKeys = await settings.getLanguageKeys(); if (onlyLanguage) { @@ -100,7 +99,7 @@ export const resultsToV1TranslationType = async ( }; export const createTranslationsV2 = async (translation: TranslationType) => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); await new CreateTranslationsService( DefaultTranslationsDataSource(transactionManager), new ValidateTranslationsService( @@ -112,7 +111,7 @@ export const createTranslationsV2 = async (translation: TranslationType) => { }; export const upsertTranslationsV2 = async (translations: TranslationType[]) => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); await new UpsertTranslationsService( DefaultTranslationsDataSource(transactionManager), DefaultSettingsDataSource(transactionManager), @@ -130,7 +129,7 @@ export const upsertTranslationsV2 = async (translations: TranslationType[]) => { }; export const deleteTranslationsByContextIdV2 = async (contextId: string) => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); await new DeleteTranslationsService( DefaultTranslationsDataSource(transactionManager), transactionManager @@ -138,7 +137,7 @@ export const deleteTranslationsByContextIdV2 = async (contextId: string) => { }; export const deleteTranslationsByLanguageV2 = async (language: LanguageISO6391) => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); return new DeleteTranslationsService( DefaultTranslationsDataSource(transactionManager), transactionManager @@ -148,23 +147,21 @@ export const deleteTranslationsByLanguageV2 = async (language: LanguageISO6391) export const getTranslationsV2ByContext = async (context: string) => resultsToV1TranslationType( new GetTranslationsService( - DefaultTranslationsDataSource(new MongoTransactionManager(getClient())) + DefaultTranslationsDataSource(DefaultTransactionManager()) ).getByContext(context) ); export const getTranslationsV2ByLanguage = async (language: LanguageISO6391) => resultsToV1TranslationType( new GetTranslationsService( - DefaultTranslationsDataSource(new MongoTransactionManager(getClient())) + DefaultTranslationsDataSource(DefaultTransactionManager()) ).getByLanguage(language), language ); export const getTranslationsV2 = async () => resultsToV1TranslationType( - new GetTranslationsService( - DefaultTranslationsDataSource(new MongoTransactionManager(getClient())) - ).getAll() + new GetTranslationsService(DefaultTranslationsDataSource(DefaultTransactionManager())).getAll() ); export const updateContextV2 = async ( @@ -173,7 +170,7 @@ export const updateContextV2 = async ( keysToDelete: string[], valueChanges: IndexedContextValues ) => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); await new UpsertTranslationsService( DefaultTranslationsDataSource(transactionManager), DefaultSettingsDataSource(transactionManager), diff --git a/app/api/log.v2/contracts/Logger.ts b/app/api/log.v2/contracts/Logger.ts index c4d57c53cf..86c5a56c7e 100644 --- a/app/api/log.v2/contracts/Logger.ts +++ b/app/api/log.v2/contracts/Logger.ts @@ -1,7 +1,7 @@ export interface Logger { - debug(message: string | string[], asJSON?: boolean): void; - info(message: string | string[], asJSON?: boolean): void; - warning(message: string | string[], asJSON?: boolean): void; - error(message: string | string[], asJSON?: boolean): void; - critical(message: string | string[], asJSON?: boolean): void; + debug(message: string | string[]): void; + info(message: string | string[]): void; + warning(message: string | string[]): void; + error(message: string | string[]): void; + critical(message: string | string[]): void; } diff --git a/app/api/log.v2/infrastructure/LogEntry.ts b/app/api/log.v2/infrastructure/LogEntry.ts new file mode 100644 index 0000000000..1cca1c8076 --- /dev/null +++ b/app/api/log.v2/infrastructure/LogEntry.ts @@ -0,0 +1,23 @@ +import { Tenant } from 'api/tenants/tenantContext'; +import { LogLevel } from './LogLevels'; + +export class LogEntry { + message: string; + + timestamp: number; + + level: LogLevel; + + tenant: Tenant; + + constructor(message: string, timestamp: number, level: LogLevel, tenant: Tenant) { + this.message = message; + this.timestamp = timestamp; + this.level = level; + this.tenant = tenant; + } + + timeToString(): string { + return new Date(this.timestamp).toISOString(); + } +} diff --git a/app/api/log.v2/infrastructure/LogLevels.ts b/app/api/log.v2/infrastructure/LogLevels.ts new file mode 100644 index 0000000000..d58b73a9ce --- /dev/null +++ b/app/api/log.v2/infrastructure/LogLevels.ts @@ -0,0 +1,18 @@ +export class LogLevel { + name: string; + + severity: number; + + constructor(name: string, severity: number) { + this.name = name; + this.severity = severity; + } +} + +export const LogLevels: Record = { + DEBUG: new LogLevel('DEBUG', 10), + INFO: new LogLevel('INFO', 20), + WARNING: new LogLevel('WARNING', 30), + ERROR: new LogLevel('ERROR', 40), + CRITICAL: new LogLevel('CRITICAL', 50), +}; diff --git a/app/api/log.v2/infrastructure/LogWriter.ts b/app/api/log.v2/infrastructure/LogWriter.ts new file mode 100644 index 0000000000..6dbb43da9c --- /dev/null +++ b/app/api/log.v2/infrastructure/LogWriter.ts @@ -0,0 +1,5 @@ +import { LogEntry } from './LogEntry'; + +export interface LogWriter { + (log: LogEntry): void; +} diff --git a/app/api/log.v2/infrastructure/StandardLogger.ts b/app/api/log.v2/infrastructure/StandardLogger.ts index 3bca5971b4..b63aab2a99 100644 --- a/app/api/log.v2/infrastructure/StandardLogger.ts +++ b/app/api/log.v2/infrastructure/StandardLogger.ts @@ -1,99 +1,49 @@ -/* eslint-disable max-classes-per-file */ import { getTenant } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { Tenant } from 'api/tenants/tenantContext'; import { Logger } from '../contracts/Logger'; - -class LogLevel { - name: string; - - severity: number; - - constructor(name: string, severity: number) { - this.name = name; - this.severity = severity; - } -} - -const LogLevels: Record = { - DEBUG: new LogLevel('DEBUG', 10), - INFO: new LogLevel('INFO', 20), - WARNING: new LogLevel('WARNING', 30), - ERROR: new LogLevel('ERROR', 40), - CRITICAL: new LogLevel('CRITICAL', 50), -}; - -class LogEntry { - message: string; - - timestamp: number; - - level: LogLevel; - - tenant: Tenant; - - constructor(message: string, timestamp: number, level: LogLevel, tenant: Tenant) { - this.message = message; - this.timestamp = timestamp; - this.level = level; - this.tenant = tenant; - } - - private timeToString(): string { - return new Date(this.timestamp).toISOString(); - } - - toString(): string { - return `${this.timeToString()} - [${this.level.name}] - [${this.tenant.name}]:${this.message}`; - } - - toJSONString(): string { - return JSON.stringify({ - time: this.timeToString(), - level: this.level.name, - tenant: this.tenant.name, - message: this.message, - }); - } -} +import { LogLevel, LogLevels } from './LogLevels'; +import { LogEntry } from './LogEntry'; +import { LogWriter } from './LogWriter'; +import { StandardJSONWriter } from './writers/StandardJSONWriter'; class StandardLogger implements Logger { + private write: LogWriter; + private tenant: Tenant; - constructor(tenant: Tenant) { + constructor(writer: LogWriter, tenant: Tenant) { + this.write = writer; this.tenant = tenant; } - private log(level: LogLevel, _message: string | string[], asJSON: boolean = false): void { + private log(level: LogLevel, _message: string | string[]): void { const message = Array.isArray(_message) ? _message.join('\n') : _message; const entry = new LogEntry(message, Date.now(), level, this.tenant); - if (asJSON) { - process.stdout.write(`${entry.toJSONString()}\n`); - } else { - process.stdout.write(`${entry.toString()}\n`); - } + + this.write(entry); } - debug(message: string | string[], asJSON: boolean = false): void { - this.log(LogLevels.DEBUG, message, asJSON); + debug(message: string | string[]): void { + this.log(LogLevels.DEBUG, message); } - info(message: string | string[], asJSON: boolean = false): void { - this.log(LogLevels.INFO, message, asJSON); + info(message: string | string[]): void { + this.log(LogLevels.INFO, message); } - warning(message: string | string[], asJSON: boolean = false): void { - this.log(LogLevels.WARNING, message, asJSON); + warning(message: string | string[]): void { + this.log(LogLevels.WARNING, message); } - error(message: string | string[], asJSON: boolean = false): void { - this.log(LogLevels.ERROR, message, asJSON); + error(message: string | string[]): void { + this.log(LogLevels.ERROR, message); } - critical(message: string | string[], asJSON: boolean = false): void { - this.log(LogLevels.CRITICAL, message, asJSON); + critical(message: string | string[]): void { + this.log(LogLevels.CRITICAL, message); } } -const DefaultLogger = () => new StandardLogger(getTenant()); +const DefaultLogger = () => new StandardLogger(StandardJSONWriter, getTenant()); export { StandardLogger, DefaultLogger }; diff --git a/app/api/log.v2/infrastructure/specs/Logger.spec.ts b/app/api/log.v2/infrastructure/specs/Logger.spec.ts index ede6c48ea4..7bedc2d496 100644 --- a/app/api/log.v2/infrastructure/specs/Logger.spec.ts +++ b/app/api/log.v2/infrastructure/specs/Logger.spec.ts @@ -1,5 +1,7 @@ import { Tenant } from 'api/tenants/tenantContext'; import { StandardLogger } from '../StandardLogger'; +import { StandardJSONWriter } from '../writers/StandardJSONWriter'; +import { StandardWriter } from '../writers/StandardWriter'; const tenant: Tenant = { name: 'testTenant', @@ -11,7 +13,8 @@ const tenant: Tenant = { activityLogs: 'testTenant/activityLogs', }; -const logging = new StandardLogger(tenant); +const jsonLogger = new StandardLogger(StandardJSONWriter, tenant); +const standardLogger = new StandardLogger(StandardWriter, tenant); const mockedTimeStamp = Date.UTC(1999, 11, 31, 23, 59); const mockedDateString = '1999-12-31T23:59:00.000Z'; @@ -36,75 +39,75 @@ describe('Logger', () => { it.each([ { + logger: standardLogger, level: 'debug', message: 'debug message', - asJSON: false, expected: `${mockedDateString} - [DEBUG] - [testTenant]:debug message\n`, }, { + logger: jsonLogger, level: 'debug', message: 'debug message', - asJSON: true, expected: `{"time":"${mockedDateString}","level":"DEBUG","tenant":"testTenant","message":"debug message"}\n`, }, { + logger: standardLogger, level: 'info', message: 'info message', - asJSON: false, expected: `${mockedDateString} - [INFO] - [testTenant]:info message\n`, }, { + logger: jsonLogger, level: 'info', message: 'info message', - asJSON: true, expected: `{"time":"${mockedDateString}","level":"INFO","tenant":"testTenant","message":"info message"}\n`, }, { + logger: standardLogger, level: 'warning', message: 'warning message', - asJSON: false, expected: `${mockedDateString} - [WARNING] - [testTenant]:warning message\n`, }, { + logger: jsonLogger, level: 'warning', message: 'warning message', - asJSON: true, expected: `{"time":"${mockedDateString}","level":"WARNING","tenant":"testTenant","message":"warning message"}\n`, }, { + logger: standardLogger, level: 'error', message: 'error message', - asJSON: false, expected: `${mockedDateString} - [ERROR] - [testTenant]:error message\n`, }, { + logger: jsonLogger, level: 'error', message: 'error message', - asJSON: true, expected: `{"time":"${mockedDateString}","level":"ERROR","tenant":"testTenant","message":"error message"}\n`, }, { + logger: standardLogger, level: 'critical', message: 'critical message', - asJSON: false, expected: `${mockedDateString} - [CRITICAL] - [testTenant]:critical message\n`, }, { + logger: jsonLogger, level: 'critical', message: 'critical message', - asJSON: true, expected: `{"time":"${mockedDateString}","level":"CRITICAL","tenant":"testTenant","message":"critical message"}\n`, }, - ])('should log $level', ({ level, message, asJSON, expected }) => { + ])('should log $level', ({ logger, level, message, expected }) => { // @ts-ignore - logging[level](message, asJSON); + logger[level](message); expect(stdoutMock).toHaveBeenCalledWith(expected); }); it('should be able to log multiple lines together', () => { const message = ['multiple', 'line', 'message']; + standardLogger.debug(message); const expected = `${mockedDateString} - [DEBUG] - [testTenant]:multiple\nline\nmessage\n`; - logging.debug(message); expect(stdoutMock).toHaveBeenCalledWith(expected); }); }); diff --git a/app/api/log.v2/infrastructure/writers/StandardJSONWriter.ts b/app/api/log.v2/infrastructure/writers/StandardJSONWriter.ts new file mode 100644 index 0000000000..5fa8a7e3e5 --- /dev/null +++ b/app/api/log.v2/infrastructure/writers/StandardJSONWriter.ts @@ -0,0 +1,13 @@ +import { LogEntry } from '../LogEntry'; +import { LogWriter } from '../LogWriter'; + +export const StandardJSONWriter: LogWriter = (log: LogEntry) => { + process.stdout.write( + `${JSON.stringify({ + time: log.timeToString(), + level: log.level.name, + tenant: log.tenant.name, + message: log.message, + })}\n` + ); +}; diff --git a/app/api/log.v2/infrastructure/writers/StandardWriter.ts b/app/api/log.v2/infrastructure/writers/StandardWriter.ts new file mode 100644 index 0000000000..9546514184 --- /dev/null +++ b/app/api/log.v2/infrastructure/writers/StandardWriter.ts @@ -0,0 +1,8 @@ +import { LogEntry } from '../LogEntry'; +import { LogWriter } from '../LogWriter'; + +export const StandardWriter: LogWriter = (log: LogEntry) => { + process.stdout.write( + `${`${log.timeToString()} - [${log.level.name}] - [${log.tenant.name}]:${log.message}`}\n` + ); +}; diff --git a/app/api/migrations/migrations/146-remove_obsolete_mongo_index/index.js b/app/api/migrations/migrations/147-remove_obsolete_mongo_index/index.js similarity index 98% rename from app/api/migrations/migrations/146-remove_obsolete_mongo_index/index.js rename to app/api/migrations/migrations/147-remove_obsolete_mongo_index/index.js index 4cde56b82a..69d109e5b2 100644 --- a/app/api/migrations/migrations/146-remove_obsolete_mongo_index/index.js +++ b/app/api/migrations/migrations/147-remove_obsolete_mongo_index/index.js @@ -11,7 +11,7 @@ const handleCollection = async (collection, indexNames) => { }; export default { - delta: 146, + delta: 147, name: 'remove_obsolete_mongo_index', diff --git a/app/api/migrations/migrations/146-remove_obsolete_mongo_index/specs/146-remove_obsolete_mongo_index.spec.js b/app/api/migrations/migrations/147-remove_obsolete_mongo_index/specs/147-remove_obsolete_mongo_index.spec.js similarity index 98% rename from app/api/migrations/migrations/146-remove_obsolete_mongo_index/specs/146-remove_obsolete_mongo_index.spec.js rename to app/api/migrations/migrations/147-remove_obsolete_mongo_index/specs/147-remove_obsolete_mongo_index.spec.js index 6ff488671c..aa07cb03d3 100644 --- a/app/api/migrations/migrations/146-remove_obsolete_mongo_index/specs/146-remove_obsolete_mongo_index.spec.js +++ b/app/api/migrations/migrations/147-remove_obsolete_mongo_index/specs/147-remove_obsolete_mongo_index.spec.js @@ -33,7 +33,7 @@ describe('migration remove_obsolete_mongo_index', () => { }); it('should have a delta number', () => { - expect(migration.delta).toBe(146); + expect(migration.delta).toBe(147); }); it('should remove the targeted indices', async () => { diff --git a/app/api/migrations/migrations/146-remove_obsolete_mongo_index/specs/fixtures.js b/app/api/migrations/migrations/147-remove_obsolete_mongo_index/specs/fixtures.js similarity index 100% rename from app/api/migrations/migrations/146-remove_obsolete_mongo_index/specs/fixtures.js rename to app/api/migrations/migrations/147-remove_obsolete_mongo_index/specs/fixtures.js diff --git a/app/api/migrations/migrations/147-update_translations/index.js b/app/api/migrations/migrations/147-update_translations/index.js deleted file mode 100644 index 98b3dcded8..0000000000 --- a/app/api/migrations/migrations/147-update_translations/index.js +++ /dev/null @@ -1,108 +0,0 @@ -const newKeys = [ - { key: 'Suggestion accepted.' }, - { key: 'Showing' }, - { key: 'Accept suggestion' }, - { key: 'Stats & Filters' }, - { key: 'Labeled' }, - { key: 'Non-labeled' }, - { key: 'Pending' }, - { key: 'Clear all' }, - { key: 'Apply' }, - { key: 'Current value:' }, - { key: 'Suggestion:' }, - { key: 'Current Value/Suggestion' }, - { key: 'No context' }, -]; - -const deletedKeys = [ - { key: 'Reviewing' }, - { key: 'Confirm suggestion acceptance' }, - { key: 'Apply to all languages' }, - { key: 'Back to dashboard' }, - { key: 'Match / Label' }, - { key: 'Mismatch / Label' }, - { key: 'Match / Value' }, - { key: 'Mismatch / Value' }, - { key: 'Empty / Label' }, - { key: 'Empty / Value' }, - { key: 'State Legend' }, - { key: 'labelMatchDesc' }, - { key: 'labelMismatchDesc' }, - { key: 'labelEmptyDesc' }, - { key: 'valueMatchDesc' }, - { key: 'valueMismatchDesc' }, - { key: 'valueEmptyDesc' }, - { key: 'obsoleteDesc' }, - { key: 'emptyDesc' }, - { key: 'This will update the entity across all languages' }, - { key: 'Mismatch / Empty' }, - { key: 'Empty / Empty' }, - { key: 'emptyMismatchDesc' }, - { key: 'Non-matching' }, - { key: 'Empty / Obsolete' }, - { key: 'This will cancel the finding suggestion process' }, - { key: 'Add properties' }, - { key: 'Show Filters' }, -]; -const updateTranslation = (currentTranslation, keysToUpdate, loc) => { - const translation = { ...currentTranslation }; - const newTranslation = keysToUpdate.find(row => row.key === currentTranslation.key); - if (newTranslation) { - translation.key = newTranslation.newKey; - if (loc === 'en' || currentTranslation.value === newTranslation.oldValue) { - translation.value = newTranslation.newValue; - } - } - return translation; -}; - -export default { - delta: 147, - - reindex: false, - - name: 'update_translations', - - description: 'Updates some translations for new User/Groups UI in settings', - - async up(db) { - const keysToInsert = newKeys; - const keysToDelete = deletedKeys; - const translations = await db.collection('translations').find().toArray(); - const locToSystemContext = {}; - translations.forEach(tr => { - locToSystemContext[tr.locale] = tr.contexts.find(c => c.id === 'System'); - }); - - const alreadyInDB = []; - Object.entries(locToSystemContext).forEach(([loc, context]) => { - const contextValues = context.values.reduce((newValues, currentTranslation) => { - const deleted = keysToDelete.find( - deletedTranslation => deletedTranslation.key === currentTranslation.key - ); - if (!deleted) { - const translation = updateTranslation(currentTranslation, [], loc); - newValues.push(translation); - } - keysToInsert.forEach(newEntry => { - if (newEntry.key === currentTranslation.key) { - alreadyInDB.push(currentTranslation.key); - } - }); - return newValues; - }, []); - keysToInsert - .filter(k => !alreadyInDB.includes(k.key)) - .forEach(newEntry => { - contextValues.push({ key: newEntry.key, value: newEntry.key }); - }); - context.values = contextValues; - }); - - await Promise.all( - translations.map(tr => db.collection('translations').replaceOne({ _id: tr._id }, tr)) - ); - - process.stdout.write(`${this.name}...\r\n`); - }, -}; diff --git a/app/api/migrations/migrations/147-update_translations/specs/147-update_translations.spec.js b/app/api/migrations/migrations/147-update_translations/specs/147-update_translations.spec.js deleted file mode 100644 index 8c347dea29..0000000000 --- a/app/api/migrations/migrations/147-update_translations/specs/147-update_translations.spec.js +++ /dev/null @@ -1,75 +0,0 @@ -import testingDB from 'api/utils/testing_db'; -import migration from '../index.js'; -import fixtures, { templateContext } from './fixtures.js'; - -describe('migration update translations of settings new Users/Groups UI', () => { - beforeEach(async () => { - jest.spyOn(process.stdout, 'write').mockImplementation(() => {}); - await testingDB.setupFixturesAndContext(fixtures); - }); - - afterAll(async () => { - await testingDB.disconnect(); - }); - - it('should have a delta number', () => { - expect(migration.delta).toBe(147); - }); - - it('should update the keys that have changed', async () => { - await migration.up(testingDB.mongodb); - const allTranslations = await testingDB.mongodb.collection('translations').find().toArray(); - - const uwaziUI = allTranslations.filter(tr => - tr.contexts.filter(ctx => ctx.type === 'Uwazi UI') - ); - - const previousSystemValues = { - key: 'existing-key-in-system', - value: 'existing-key-in-system', - }; - - const addedKeys = [ - expect.objectContaining({ - key: 'Suggestion accepted.', - value: 'Suggestion accepted.', - }), - expect.objectContaining({ - key: 'Accept suggestion', - value: 'Accept suggestion', - }), - expect.objectContaining({ - key: 'Showing', - value: 'Showing', - }), - expect.objectContaining({ - key: 'Stats & Filters', - value: 'Stats & Filters', - }), - ]; - const defaultContextContent = expect.objectContaining({ - type: 'Uwazi UI', - values: expect.arrayContaining([previousSystemValues, ...addedKeys]), - }); - expect(uwaziUI).toMatchObject([ - expect.objectContaining({ - locale: 'en', - contexts: [defaultContextContent, templateContext], - }), - expect.objectContaining({ - locale: 'es', - contexts: [ - expect.objectContaining({ - type: 'Uwazi UI', - values: expect.arrayContaining([previousSystemValues, ...addedKeys]), - }), - templateContext, - ], - }), - expect.objectContaining({ - locale: 'pt', - contexts: [defaultContextContent, templateContext], - }), - ]); - }); -}); diff --git a/app/api/migrations/migrations/147-update_translations/specs/fixtures.js b/app/api/migrations/migrations/147-update_translations/specs/fixtures.js deleted file mode 100644 index f9a29257a5..0000000000 --- a/app/api/migrations/migrations/147-update_translations/specs/fixtures.js +++ /dev/null @@ -1,115 +0,0 @@ -import db from 'api/utils/testing_db'; - -const templateContext = { - id: db.id(), - label: 'default template', - type: 'Entity', - values: [ - { - key: 'default template', - value: 'default template', - }, - { - key: 'Title', - value: 'Title', - }, - ], -}; - -const fixturesDB = { - translations: [ - { - _id: db.id(), - locale: 'en', - contexts: [ - { - _id: db.id(), - type: 'Uwazi UI', - label: 'User Interface', - id: 'System', - values: [ - { - key: 'existing-key-in-system', - value: 'existing-key-in-system', - }, - { - _id: db.id(), - key: 'Can not delete template:', - value: 'Can not delete template: changed', - }, - { - _id: db.id(), - key: '- Site page:', - value: '- Site page:', - }, - { _id: db.id(), key: 'Document OCR trigger', value: 'Document OCR trigger' }, - ], - }, - templateContext, - ], - }, - { - _id: db.id(), - locale: 'es', - contexts: [ - { - _id: db.id(), - type: 'Uwazi UI', - label: 'User Interface', - id: 'System', - values: [ - { - key: 'existing-key-in-system', - value: 'existing-key-in-system', - }, - { - _id: db.id(), - key: 'Confirm delete relationship type:', - value: 'Confirmar eliminación de tipo de relación:', - }, - { - _id: db.id(), - key: '- Site page:', - value: '- Sito:', - }, - { _id: db.id(), key: 'Document OCR trigger', value: 'Document OCR trigger' }, - ], - }, - templateContext, - ], - }, - { - _id: db.id(), - locale: 'pt', - contexts: [ - { - _id: db.id(), - type: 'Uwazi UI', - label: 'User Interface', - id: 'System', - values: [ - { - key: 'existing-key-in-system', - value: 'existing-key-in-system', - }, - { - _id: db.id(), - key: 'Can not delete template:', - value: 'Can not delete template:', - }, - { - _id: db.id(), - key: '- Site page:', - value: '- Site page:', - }, - { _id: db.id(), key: 'Document OCR trigger', value: 'Document OCR trigger' }, - ], - }, - templateContext, - ], - }, - ], -}; - -export { templateContext }; -export default fixturesDB; diff --git a/app/api/migrations/migrations/148-update_translations/index.js b/app/api/migrations/migrations/148-update_translations/index.js new file mode 100644 index 0000000000..573009443e --- /dev/null +++ b/app/api/migrations/migrations/148-update_translations/index.js @@ -0,0 +1,80 @@ +const newKeys = [ + { key: 'Suggestion accepted.' }, + { key: 'Showing' }, + { key: 'Accept suggestion' }, + { key: 'Stats & Filters' }, + { key: 'Labeled' }, + { key: 'Non-labeled' }, + { key: 'Pending' }, + { key: 'Clear all' }, + { key: 'Apply' }, + { key: 'Current value:' }, + { key: 'Suggestion:' }, + { key: 'Current Value/Suggestion' }, + { key: 'No context' }, +]; + +const deletedKeys = [ + { key: 'Reviewing' }, + { key: 'Confirm suggestion acceptance' }, + { key: 'Apply to all languages' }, + { key: 'Back to dashboard' }, + { key: 'Match / Label' }, + { key: 'Mismatch / Label' }, + { key: 'Match / Value' }, + { key: 'Mismatch / Value' }, + { key: 'Empty / Label' }, + { key: 'Empty / Value' }, + { key: 'State Legend' }, + { key: 'labelMatchDesc' }, + { key: 'labelMismatchDesc' }, + { key: 'labelEmptyDesc' }, + { key: 'valueMatchDesc' }, + { key: 'valueMismatchDesc' }, + { key: 'valueEmptyDesc' }, + { key: 'obsoleteDesc' }, + { key: 'emptyDesc' }, + { key: 'This will update the entity across all languages' }, + { key: 'Mismatch / Empty' }, + { key: 'Empty / Empty' }, + { key: 'emptyMismatchDesc' }, + { key: 'Non-matching' }, + { key: 'Empty / Obsolete' }, + { key: 'This will cancel the finding suggestion process' }, + { key: 'Add properties' }, + { key: 'Show Filters' }, +]; + +export default { + delta: 148, + + reindex: false, + + name: 'update_translations', + + description: 'Updates some translations for new User/Groups UI in settings', + + async up(db) { + const settings = await db.collection('settings').findOne(); + const languages = settings.languages.map(l => l.key); + await db + .collection('translationsV2') + .deleteMany({ key: { $in: deletedKeys.map(k => k.key) }, 'context.id': 'System' }); + + const insertMany = languages.map(l => + db.collection('translationsV2').insertMany( + newKeys.map(k => ({ + key: k.key, + value: k.key, + language: l, + context: { id: 'System', type: 'Uwazi UI', label: 'User Interface' }, + })) + ) + ); + await Promise.all(insertMany); + + process.stdout.write(`${this.name}...\r\n`); + }, +}; + +export { newKeys, deletedKeys }; diff --git a/app/api/migrations/migrations/148-update_translations/specs/148-update_translations.spec.js b/app/api/migrations/migrations/148-update_translations/specs/148-update_translations.spec.js new file mode 100644 index 0000000000..c9a9cd8393 --- /dev/null +++ b/app/api/migrations/migrations/148-update_translations/specs/148-update_translations.spec.js @@ -0,0 +1,46 @@ +import testingDB from 'api/utils/testing_db'; +import migration, { newKeys, deletedKeys } from '../index.js'; +import fixtures from './fixtures.js'; + +describe('migration update translations of settings new Users/Groups UI', () => { + beforeAll(async () => { + jest.spyOn(process.stdout, 'write').mockImplementation(() => {}); + await testingDB.setupFixturesAndContext(fixtures); + await migration.up(testingDB.mongodb); + }); + + afterAll(async () => { + await testingDB.disconnect(); + }); + + it('should have a delta number', () => { + expect(migration.delta).toBe(148); + }); + + it('should delete old translations', async () => { + const translations = await testingDB.mongodb + .collection('translationsV2') + .find({ key: { $in: deletedKeys.map(k => k.key) } }) + .toArray(); + + expect(translations).toEqual([]); + }); + + it('should NOT delete other translations', async () => { + const translations = await testingDB.mongodb + .collection('translationsV2') + .find({ key: 'Im cool' }) + .toArray(); + + expect(translations.length).toBe(2); + }); + + it('should add new translations per language', async () => { + const translations = await testingDB.mongodb + .collection('translationsV2') + .find({ key: { $in: newKeys.map(k => k.key) } }) + .toArray(); + + expect(translations.length).toBe(newKeys.length * fixtures.settings[0].languages.length); + }); +}); diff --git a/app/api/migrations/migrations/148-update_translations/specs/fixtures.js b/app/api/migrations/migrations/148-update_translations/specs/fixtures.js new file mode 100644 index 0000000000..488d6db7b5 --- /dev/null +++ b/app/api/migrations/migrations/148-update_translations/specs/fixtures.js @@ -0,0 +1,51 @@ +import db from 'api/utils/testing_db'; + +const fixturesDB = { + settings: [{ _id: db.id(), languages: [{ key: 'en' }, { key: 'es' }] }], + translationsV2: [ + { + _id: db.id(), + language: 'en', + context: { id: 'System', label: 'User Interface', type: 'Uwazi UI' }, + key: 'Match / Label', + value: 'Match / Label', + }, + { + _id: db.id(), + language: 'en', + context: { id: 'System', label: 'User Interface', type: 'Uwazi UI' }, + key: 'Reviewing', + value: 'Reviewing', + }, + { + _id: db.id(), + language: 'es', + context: { id: 'System', label: 'User Interface', type: 'Uwazi UI' }, + key: 'Match / Label', + value: 'Match / Label', + }, + { + _id: db.id(), + language: 'es', + context: { id: 'System', label: 'User Interface', type: 'Uwazi UI' }, + key: 'Reviewing', + value: 'Reviewing', + }, + { + _id: db.id(), + language: 'en', + context: { id: 'System', label: 'User Interface', type: 'Uwazi UI' }, + key: 'Im cool', + value: 'Im cool', + }, + { + _id: db.id(), + language: 'es', + context: { id: 'System', label: 'User Interface', type: 'Uwazi UI' }, + key: 'Im cool', + value: 'Im cool', + }, + ], +}; + +export default fixturesDB; diff --git a/app/api/queue.v2/configuration/factories.ts b/app/api/queue.v2/configuration/factories.ts index 23dfa58158..b356f33642 100644 --- a/app/api/queue.v2/configuration/factories.ts +++ b/app/api/queue.v2/configuration/factories.ts @@ -5,6 +5,7 @@ import { getSharedClient, getSharedConnection, } from 'api/common.v2/database/getConnectionForCurrentTenant'; +import { DefaultLogger } from 'api/log.v2/infrastructure/StandardLogger'; import { JobsRouter } from '../infrastructure/JobsRouter'; import { MongoQueueAdapter } from '../infrastructure/MongoQueueAdapter'; import { NamespacedDispatcher } from '../infrastructure/NamespacedDispatcher'; @@ -12,12 +13,15 @@ import { NamespacedDispatcher } from '../infrastructure/NamespacedDispatcher'; export function DefaultQueueAdapter() { return new MongoQueueAdapter( getSharedConnection(), - new MongoTransactionManager(getSharedClient()) + new MongoTransactionManager(getSharedClient(), DefaultLogger()) ); } export function DefaultTestingQueueAdapter() { - return new MongoQueueAdapter(getConnection(), new MongoTransactionManager(getClient())); + return new MongoQueueAdapter( + getConnection(), + new MongoTransactionManager(getClient(), DefaultLogger()) + ); } export async function DefaultDispatcher(tenant: string) { diff --git a/app/api/relationships.v2/database/specs/MongoRelationshipsDataSource.spec.ts b/app/api/relationships.v2/database/specs/MongoRelationshipsDataSource.spec.ts index ff497c5713..c7d6e1fe16 100644 --- a/app/api/relationships.v2/database/specs/MongoRelationshipsDataSource.spec.ts +++ b/app/api/relationships.v2/database/specs/MongoRelationshipsDataSource.spec.ts @@ -1,10 +1,9 @@ -import { getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; import { MatchQueryNode } from 'api/relationships.v2/model/MatchQueryNode'; import { TraversalQueryNode } from 'api/relationships.v2/model/TraversalQueryNode'; import { getFixturesFactory } from 'api/utils/fixturesFactory'; import { testingEnvironment } from 'api/utils/testingEnvironment'; import testingDB from 'api/utils/testing_db'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { MongoRelationshipsDataSource } from '../MongoRelationshipsDataSource'; const factory = getFixturesFactory(); @@ -88,10 +87,7 @@ let ds: MongoRelationshipsDataSource; beforeEach(async () => { await testingEnvironment.setUp(fixtures); - ds = new MongoRelationshipsDataSource( - testingDB.mongodb!, - new MongoTransactionManager(getClient()) - ); + ds = new MongoRelationshipsDataSource(testingDB.mongodb!, DefaultTransactionManager()); }); afterAll(async () => { diff --git a/app/api/relationships.v2/routes/routes.ts b/app/api/relationships.v2/routes/routes.ts index 247d34374d..3c0da160e3 100644 --- a/app/api/relationships.v2/routes/routes.ts +++ b/app/api/relationships.v2/routes/routes.ts @@ -4,14 +4,13 @@ import { Application, NextFunction, Request, Response } from 'express'; import { needsAuthorization } from 'api/auth'; import { DefaultSettingsDataSource } from 'api/settings.v2/database/data_source_defaults'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; -import { getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { parseQuery } from 'api/utils'; import { GetMigrationHubRecordsResponse } from 'shared/types/api.v2/migrationHubRecords.get'; import { MigrationResponse } from 'shared/types/api.v2/relationships.migrate'; import { TestOneHubResponse } from 'shared/types/api.v2/relationships.testOneHub'; import { CreateRelationshipMigRationFieldResponse } from 'shared/types/api.v2/relationshipMigrationField.create'; import { GetRelationshipMigrationFieldsResponse } from 'shared/types/api.v2/relationshipMigrationField.get'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { CreateRelationshipMigrationFieldService, CreateRelationshipService, @@ -34,9 +33,7 @@ import { validateGetMigrationHubRecordsRequest } from './validators/getMigration const featureRequired = async (_req: Request, res: Response, next: NextFunction) => { if ( - !(await DefaultSettingsDataSource( - new MongoTransactionManager(getClient()) - ).readNewRelationshipsAllowed()) + !(await DefaultSettingsDataSource(DefaultTransactionManager()).readNewRelationshipsAllowed()) ) { return res.sendStatus(404); } diff --git a/app/api/relationships.v2/services/service_factories.ts b/app/api/relationships.v2/services/service_factories.ts index 81299d0a1f..58d3368ed8 100644 --- a/app/api/relationships.v2/services/service_factories.ts +++ b/app/api/relationships.v2/services/service_factories.ts @@ -17,7 +17,6 @@ import { User } from 'api/users.v2/model/User'; import { Request } from 'express'; import { UserRole } from 'shared/types/userSchema'; -import { getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { tenants } from 'api/tenants'; import { MongoIdHandler } from 'api/common.v2/database/MongoIdGenerator'; import { DefaultDispatcher } from 'api/queue.v2/configuration/factories'; @@ -70,7 +69,7 @@ const createUpdateStrategy = async ( strategyKey: string | undefined, updater: GenericEntityRelationshipsUpdateService ) => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); switch (strategyKey) { case QueuedRelationshipPropertyUpdateStrategy.name: diff --git a/app/api/relationships.v2/services/specs/CreateRelationshipService.spec.ts b/app/api/relationships.v2/services/specs/CreateRelationshipService.spec.ts index 2517af54e6..ed4a66739e 100644 --- a/app/api/relationships.v2/services/specs/CreateRelationshipService.spec.ts +++ b/app/api/relationships.v2/services/specs/CreateRelationshipService.spec.ts @@ -1,7 +1,6 @@ import { AuthorizationService } from 'api/authorization.v2/services/AuthorizationService'; -import { getConnection, getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; +import { getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { MongoIdHandler } from 'api/common.v2/database/MongoIdGenerator'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; import { partialImplementation } from 'api/common.v2/testing/partialImplementation'; import { MongoEntitiesDataSource } from 'api/entities.v2/database/MongoEntitiesDataSource'; import { MissingEntityError } from 'api/entities.v2/errors/entityErrors'; @@ -15,6 +14,7 @@ import { getFixturesFactory } from 'api/utils/fixturesFactory'; import { testingEnvironment } from 'api/utils/testingEnvironment'; import testingDB, { DBFixture } from 'api/utils/testing_db'; import { ObjectId } from 'mongodb'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { CreateRelationshipService } from '../CreateRelationshipService'; import { DenormalizationService } from '../DenormalizationService'; @@ -39,7 +39,7 @@ const denormalizationServiceMock = partialImplementation const createService = () => { const connection = getConnection(); - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); const SettingsDataSource = new MongoSettingsDataSource(connection, transactionManager); validateAccessMock.mockReset(); diff --git a/app/api/relationships.v2/services/specs/DeleteRelationshipService.spec.ts b/app/api/relationships.v2/services/specs/DeleteRelationshipService.spec.ts index 7976618b32..25d7acdf3b 100644 --- a/app/api/relationships.v2/services/specs/DeleteRelationshipService.spec.ts +++ b/app/api/relationships.v2/services/specs/DeleteRelationshipService.spec.ts @@ -1,14 +1,14 @@ import { MongoPermissionsDataSource } from 'api/authorization.v2/database/MongoPermissionsDataSource'; import { AuthorizationService } from 'api/authorization.v2/services/AuthorizationService'; -import { getConnection, getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; +import { getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { MongoIdHandler } from 'api/common.v2/database/MongoIdGenerator'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; import { partialImplementation } from 'api/common.v2/testing/partialImplementation'; import { User } from 'api/users.v2/model/User'; import { getFixturesFactory } from 'api/utils/fixturesFactory'; import { testingEnvironment } from 'api/utils/testingEnvironment'; import testingDB from 'api/utils/testing_db'; import { ObjectId } from 'mongodb'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { MongoRelationshipsDataSource } from '../../database/MongoRelationshipsDataSource'; import { MissingRelationshipError } from '../../errors/relationshipErrors'; import { DeleteRelationshipService } from '../DeleteRelationshipService'; @@ -81,7 +81,7 @@ describe('delete()', () => { }); const connection = getConnection(); - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); const service = new DeleteRelationshipService( new MongoRelationshipsDataSource(connection, transactionManager), transactionManager, @@ -111,7 +111,7 @@ describe('delete()', () => { }); const connection = getConnection(); - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); const service = new DeleteRelationshipService( new MongoRelationshipsDataSource(connection, transactionManager), transactionManager, @@ -157,7 +157,7 @@ describe('delete()', () => { }); const connection = getConnection(); - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); const service = new DeleteRelationshipService( new MongoRelationshipsDataSource(connection, transactionManager), transactionManager, diff --git a/app/api/relationships.v2/services/specs/DenormalizationService.spec.ts b/app/api/relationships.v2/services/specs/DenormalizationService.spec.ts index 7c93a0d0c4..54f4148983 100644 --- a/app/api/relationships.v2/services/specs/DenormalizationService.spec.ts +++ b/app/api/relationships.v2/services/specs/DenormalizationService.spec.ts @@ -1,5 +1,4 @@ -import { getClient, getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; +import { getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { MongoEntitiesDataSource } from 'api/entities.v2/database/MongoEntitiesDataSource'; import { MongoRelationshipsDataSource } from 'api/relationships.v2/database/MongoRelationshipsDataSource'; import { MongoSettingsDataSource } from 'api/settings.v2/database/MongoSettingsDataSource'; @@ -9,6 +8,7 @@ import { testingEnvironment } from 'api/utils/testingEnvironment'; import testingDB, { DBFixture } from 'api/utils/testing_db'; import { Db } from 'mongodb'; import { partialImplementation } from 'api/common.v2/testing/partialImplementation'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { DenormalizationService } from '../DenormalizationService'; import { RelationshipPropertyUpdateStrategy } from '../propertyUpdateStrategies/RelationshipPropertyUpdateStrategy'; @@ -319,7 +319,7 @@ beforeEach(async () => { updateByTemplateMock = jest.fn(); db = getConnection(); - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); triggerCommit = async () => transactionManager.executeOnCommitHandlers(undefined); const relationshipsDataSource = new MongoRelationshipsDataSource(db, transactionManager); const templatesDataSource = new MongoTemplatesDataSource(db, transactionManager); diff --git a/app/api/relationships.v2/services/specs/GetRelationshipService.spec.ts b/app/api/relationships.v2/services/specs/GetRelationshipService.spec.ts index d6de993c22..863b1256e9 100644 --- a/app/api/relationships.v2/services/specs/GetRelationshipService.spec.ts +++ b/app/api/relationships.v2/services/specs/GetRelationshipService.spec.ts @@ -1,7 +1,6 @@ import { MongoPermissionsDataSource } from 'api/authorization.v2/database/MongoPermissionsDataSource'; import { AuthorizationService } from 'api/authorization.v2/services/AuthorizationService'; -import { getClient, getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; +import { getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { MongoEntitiesDataSource } from 'api/entities.v2/database/MongoEntitiesDataSource'; import { MongoRelationshipsDataSource } from 'api/relationships.v2/database/MongoRelationshipsDataSource'; import { MongoRelationshipTypesDataSource } from 'api/relationshiptypes.v2/database/MongoRelationshipTypesDataSource'; @@ -11,6 +10,7 @@ import { User } from 'api/users.v2/model/User'; import { getFixturesFactory } from 'api/utils/fixturesFactory'; import { testingEnvironment } from 'api/utils/testingEnvironment'; import { DBFixture } from 'api/utils/testing_db'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { GetRelationshipService } from '../GetRelationshipService'; const fixtureFactory = getFixturesFactory(); @@ -49,7 +49,7 @@ const fixtures: DBFixture = { const createService = (_user?: User) => { const user = _user || new User(fixtureFactory.id('user').toString(), 'admin', []); const connection = getConnection(); - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); const relationshipsDS = new MongoRelationshipsDataSource(connection, transactionManager); const relationshipTypesDS = new MongoRelationshipTypesDataSource(connection, transactionManager); const templatesDS = new MongoTemplatesDataSource(connection, transactionManager); diff --git a/app/api/relationtypes/v2_support.ts b/app/api/relationtypes/v2_support.ts index dde216e096..70c82d6f3e 100644 --- a/app/api/relationtypes/v2_support.ts +++ b/app/api/relationtypes/v2_support.ts @@ -1,12 +1,11 @@ import { ObjectId } from 'mongodb'; import { DefaultSettingsDataSource } from 'api/settings.v2/database/data_source_defaults'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; -import { getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { DefaultRelationshipDataSource } from 'api/relationships.v2/database/data_source_defaults'; import { CreateTemplateService } from 'api/templates.v2/services/service_factories'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; const getNewRelationshipCount = async (id: ObjectId) => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); const newRelationshipsAllowed = await DefaultSettingsDataSource(transactionManager).readNewRelationshipsAllowed(); const relationshipsDataSource = DefaultRelationshipDataSource(transactionManager); @@ -15,7 +14,7 @@ const getNewRelationshipCount = async (id: ObjectId) => { }; const relationTypeIsUsedInQueries = async (id: ObjectId): Promise => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); const newRelationshipsAllowed = await DefaultSettingsDataSource(transactionManager).readNewRelationshipsAllowed(); if (!newRelationshipsAllowed) return false; diff --git a/app/api/search/entitiesIndex.js b/app/api/search/entitiesIndex.js index a4816e9818..e456622567 100644 --- a/app/api/search/entitiesIndex.js +++ b/app/api/search/entitiesIndex.js @@ -6,9 +6,9 @@ import { entityDefaultDocument } from 'shared/entityDefaultDocument'; import PromisePool from '@supercharge/promise-pool'; import { ElasticEntityMapper } from 'api/entities.v2/database/ElasticEntityMapper'; import { MongoTemplatesDataSource } from 'api/templates.v2/database/MongoTemplatesDataSource'; -import { getClient, getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; +import { getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { MongoSettingsDataSource } from 'api/settings.v2/database/MongoSettingsDataSource'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import elasticMapping from '../../../database/elastic_mapping/elastic_mapping'; import elasticMapFactory from '../../../database/elastic_mapping/elasticMapFactory'; import { elastic } from './elastic'; @@ -17,18 +17,14 @@ export class IndexError extends Error {} const preprocessEntitiesToIndex = async entitiesToIndex => { const db = getConnection(); - const client = getClient(); - const transactionManager = new MongoTransactionManager(client); + const transactionManager = DefaultTransactionManager(); const settingsDataSource = new MongoSettingsDataSource(db, transactionManager); if (!(await settingsDataSource.readNewRelationshipsAllowed())) { return entitiesToIndex; } - const templateDS = new MongoTemplatesDataSource( - getConnection(), - new MongoTransactionManager(getClient()) - ); + const templateDS = new MongoTemplatesDataSource(getConnection(), DefaultTransactionManager()); const transformer = new ElasticEntityMapper(templateDS); return Promise.all(entitiesToIndex.map(e => transformer.toElastic(e))); }; diff --git a/app/api/search/v2_support.ts b/app/api/search/v2_support.ts index 346e657ffc..929df5979d 100644 --- a/app/api/search/v2_support.ts +++ b/app/api/search/v2_support.ts @@ -1,6 +1,5 @@ -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; -import { getClient, getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; +import { getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { DefaultEntitiesDataSource } from 'api/entities.v2/database/data_source_defaults'; import { MongoSettingsDataSource } from 'api/settings.v2/database/MongoSettingsDataSource'; import { propertyTypes } from 'shared/propertyTypes'; @@ -8,9 +7,8 @@ import { PropertySchema } from 'shared/types/commonTypes'; async function checkFeatureEnabled() { const db = getConnection(); - const client = getClient(); - const transactionManager = new MongoTransactionManager(client); + const transactionManager = DefaultTransactionManager(); const settingsDataSource = new MongoSettingsDataSource(db, transactionManager); return settingsDataSource.readNewRelationshipsAllowed(); diff --git a/app/api/sync/specs/syncWorker.spec.ts b/app/api/sync/specs/syncWorker.spec.ts index 29a029b6e4..587b9c65aa 100644 --- a/app/api/sync/specs/syncWorker.spec.ts +++ b/app/api/sync/specs/syncWorker.spec.ts @@ -20,8 +20,6 @@ import db from 'api/utils/testing_db'; import { advancedSort } from 'app/utils/advancedSort'; import bodyParser from 'body-parser'; import express, { NextFunction, Request, RequestHandler, Response } from 'express'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; -import { getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { DefaultTranslationsDataSource } from 'api/i18n.v2/database/data_source_defaults'; import { CreateTranslationsService } from 'api/i18n.v2/services/CreateTranslationsService'; import { ValidateTranslationsService } from 'api/i18n.v2/services/ValidateTranslationsService'; @@ -32,6 +30,7 @@ import { Server } from 'http'; import 'isomorphic-fetch'; import _ from 'lodash'; import { FetchResponseError } from 'shared/JSONRequest'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { syncWorker } from '../syncWorker'; import { host1Fixtures, @@ -336,7 +335,7 @@ describe('syncWorker', () => { it('should syncronize translations v2 that match configured properties', async () => { await tenants.run(async () => { - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); await new CreateTranslationsService( DefaultTranslationsDataSource(transactionManager), new ValidateTranslationsService( diff --git a/app/api/templates.v2/database/specs/MongoTemplatesDataSource.spec.ts b/app/api/templates.v2/database/specs/MongoTemplatesDataSource.spec.ts index c63addc2a5..dd9623f143 100644 --- a/app/api/templates.v2/database/specs/MongoTemplatesDataSource.spec.ts +++ b/app/api/templates.v2/database/specs/MongoTemplatesDataSource.spec.ts @@ -1,10 +1,10 @@ -import { getClient, getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; +import { getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { TraversalQueryNode } from 'api/relationships.v2/model/TraversalQueryNode'; import { Property } from 'api/templates.v2/model/Property'; import { RelationshipProperty } from 'api/templates.v2/model/RelationshipProperty'; import { getFixturesFactory } from 'api/utils/fixturesFactory'; import { testingEnvironment } from 'api/utils/testingEnvironment'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { MongoTemplatesDataSource } from '../MongoTemplatesDataSource'; import { mapPropertyQuery } from '../QueryMapper'; @@ -71,10 +71,7 @@ afterAll(async () => { describe('getAllProperties()', () => { it('should return all the properties properly typed', async () => { - const dataSource = new MongoTemplatesDataSource( - getConnection(), - new MongoTransactionManager(getClient()) - ); + const dataSource = new MongoTemplatesDataSource(getConnection(), DefaultTransactionManager()); const result = await dataSource.getAllProperties().all(); expect(result.length).toBe(4); expect(result[0]).toBeInstanceOf(RelationshipProperty); @@ -104,10 +101,7 @@ describe('getAllProperties()', () => { describe('when requesting the relationship properties configured in the system', () => { it('should return all the relationship properties', async () => { - const dataSource = new MongoTemplatesDataSource( - getConnection(), - new MongoTransactionManager(getClient()) - ); + const dataSource = new MongoTemplatesDataSource(getConnection(), DefaultTransactionManager()); const result = await dataSource.getAllRelationshipProperties().all(); expect(result.length).toBe(3); result.forEach(property => { @@ -139,7 +133,7 @@ describe('when requesting a property by name', () => { const props: { [name: string]: Property } = {}; beforeAll(async () => { - tds = new MongoTemplatesDataSource(getConnection(), new MongoTransactionManager(getClient())); + tds = new MongoTemplatesDataSource(getConnection(), DefaultTransactionManager()); props.newRelationship = await tds.getPropertyByName('relationshipProp2'); props.text = await tds.getPropertyByName('textprop'); }); @@ -173,10 +167,7 @@ describe('when requesting a property by name', () => { describe('getByIds()', () => { it('should return the templates', async () => { - const dataSource = new MongoTemplatesDataSource( - getConnection(), - new MongoTransactionManager(getClient()) - ); + const dataSource = new MongoTemplatesDataSource(getConnection(), DefaultTransactionManager()); const result = await dataSource .getByIds([factory.id('template1').toString(), factory.id('template2').toString()]) .all(); @@ -195,10 +186,7 @@ describe('getByIds()', () => { describe('getById()', () => { it('should return the template', async () => { - const dataSource = new MongoTemplatesDataSource( - getConnection(), - new MongoTransactionManager(getClient()) - ); + const dataSource = new MongoTemplatesDataSource(getConnection(), DefaultTransactionManager()); const result = await dataSource.getById(factory.id('template1').toString()); expect(result).toMatchObject({ id: factory.id('template1').toString(), diff --git a/app/api/templates.v2/services/specs/CreateTemplateService.spec.ts b/app/api/templates.v2/services/specs/CreateTemplateService.spec.ts index b3e783dabb..90af1364ec 100644 --- a/app/api/templates.v2/services/specs/CreateTemplateService.spec.ts +++ b/app/api/templates.v2/services/specs/CreateTemplateService.spec.ts @@ -1,5 +1,4 @@ -import { getClient, getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; +import { getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { ValidationError } from 'api/common.v2/validation/ValidationError'; import { MongoRelationshipTypesDataSource } from 'api/relationshiptypes.v2/database/MongoRelationshipTypesDataSource'; import { MongoTemplatesDataSource } from 'api/templates.v2/database/MongoTemplatesDataSource'; @@ -11,6 +10,7 @@ import { DenormalizationService } from 'api/relationships.v2/services/Denormaliz import { MongoRelationshipsDataSource } from 'api/relationships.v2/database/MongoRelationshipsDataSource'; import { OnlineRelationshipPropertyUpdateStrategy } from 'api/relationships.v2/services/propertyUpdateStrategies/OnlineRelationshipPropertyUpdateStrategy'; import { EntityRelationshipsUpdateService } from 'api/entities.v2/services/EntityRelationshipsUpdateService'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { CreateTemplateService } from '../CreateTemplateService'; const fixturesFactory = getFixturesFactory(); @@ -43,7 +43,7 @@ afterAll(async () => { function setUpService() { const connection = getConnection(); - const transactionManager = new MongoTransactionManager(getClient()); + const transactionManager = DefaultTransactionManager(); const templatesDS = new MongoTemplatesDataSource(connection, transactionManager); const relTypeDS = new MongoRelationshipTypesDataSource(connection, transactionManager); const settingsDS = new MongoSettingsDataSource(connection, transactionManager); @@ -64,7 +64,7 @@ function setUpService() { new OnlineRelationshipPropertyUpdateStrategy( async () => {}, new EntityRelationshipsUpdateService(entityDS, templatesDS, relationshipsDS), - new MongoTransactionManager(getClient()), + DefaultTransactionManager(), entityDS ) ); diff --git a/app/api/templates/v2_support.ts b/app/api/templates/v2_support.ts index 911a65eeee..fd0be085bd 100644 --- a/app/api/templates/v2_support.ts +++ b/app/api/templates/v2_support.ts @@ -1,16 +1,14 @@ -import { getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; import { WithId } from 'api/odm'; import { DefaultSettingsDataSource } from 'api/settings.v2/database/data_source_defaults'; import { validateCreateNewRelationshipProperty } from 'api/templates.v2/routes/validators/createNewRelationshipProperty'; import { CreateTemplateService } from 'api/templates.v2/services/service_factories'; import { ensure } from 'shared/tsUtils'; import { TemplateSchema } from 'shared/types/templateType'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import templates from './templates'; const processNewRelationshipProperties = async (template: TemplateSchema) => { - const client = getClient(); - const transactionManager = new MongoTransactionManager(client); + const transactionManager = DefaultTransactionManager(); if (!(await DefaultSettingsDataSource(transactionManager).readNewRelationshipsAllowed())) { return template; } @@ -42,8 +40,7 @@ const processNewRelationshipPropertiesOnUpdate = async ( _oldTemplate: TemplateSchema, _newTemplate: TemplateSchema ) => { - const client = getClient(); - const transactionManager = new MongoTransactionManager(client); + const transactionManager = DefaultTransactionManager(); if (!(await DefaultSettingsDataSource(transactionManager).readNewRelationshipsAllowed())) { return _newTemplate; } @@ -55,8 +52,7 @@ const processNewRelationshipPropertiesOnUpdate = async ( }; const processNewRelationshipPropertiesOnDelete = async (templateId: TemplateSchema['_id']) => { - const client = getClient(); - const transactionManager = new MongoTransactionManager(client); + const transactionManager = DefaultTransactionManager(); if (!(await DefaultSettingsDataSource(transactionManager).readNewRelationshipsAllowed())) { return; } diff --git a/app/react/Entities/components/EntityViewer.js b/app/react/Entities/components/EntityViewer.js index e7fe1606df..dead05089e 100644 --- a/app/react/Entities/components/EntityViewer.js +++ b/app/react/Entities/components/EntityViewer.js @@ -342,7 +342,7 @@ class EntityViewer extends Component {
>; - originalEntity: EntitySchema; + originalEntity: ClientEntitySchema; formModel: string; }; type CopyFromEntityState = { - selectedEntity: EntitySchema; + selectedEntity: ClientEntitySchema; propsToCopy: Array; lastSearch?: string; }; class CopyFromEntity extends Component { + templates: TemplateSchema[]; + constructor(props: CopyFromEntityProps) { super(props); this.state = { propsToCopy: [], selectedEntity: {}, lastSearch: undefined }; + this.templates = this.props.templates.toJS(); this.onSelect = this.onSelect.bind(this); this.cancel = this.cancel.bind(this); this.copy = this.copy.bind(this); @@ -38,13 +41,12 @@ class CopyFromEntity extends Component this.onFinishedSearch = this.onFinishedSearch.bind(this); } - onSelect(selectedEntity: EntitySchema) { + onSelect(selectedEntity: ClientEntitySchema) { const copyFromTemplateId = selectedEntity.template; - const templates = this.props.templates.toJS(); const originalTemplate = this.props.originalEntity.template; const propsToCopy = comonProperties - .comonProperties(templates, [originalTemplate, copyFromTemplateId], ['generatedid']) + .comonProperties(this.templates, [originalTemplate, copyFromTemplateId], ['generatedid']) .map(p => p.name); this.setState({ selectedEntity, propsToCopy }); @@ -60,20 +62,33 @@ class CopyFromEntity extends Component return; } + const entityTemplate = this.templates.find( + template => template._id === this.props.originalEntity.template + ); + + const originalEntity: ClientEntitySchema = wrapEntityMetadata( + this.props.originalEntity, + entityTemplate + ); + const updatedEntity = this.state.propsToCopy.reduce( - (entity: EntitySchema, propName: string) => { + (entity: ClientEntitySchema, propName: string) => { if (!entity.metadata) { - entity.metadata = {}; + return { ...entity, metadata: {} }; } - entity.metadata[propName] = this.state.selectedEntity.metadata![propName]; - return entity; + const updatedMetadata = this.state.selectedEntity.metadata![propName]; + + return { + ...entity, + metadata: { ...entity.metadata, [propName]: updatedMetadata }, + }; }, - { ...this.props.originalEntity, metadata: { ...this.props.originalEntity.metadata } } + { ...originalEntity } ); actions - .loadFetchedInReduxForm(this.props.formModel, updatedEntity, this.props.templates.toJS()) + .loadFetchedInReduxForm(this.props.formModel, updatedEntity, this.templates) .forEach(action => store?.dispatch(action)); this.props.onSelect([]); diff --git a/app/react/Metadata/components/specs/CopyFromEntity.spec.tsx b/app/react/Metadata/components/specs/CopyFromEntity.spec.tsx index 4ad6aa8155..36ebf357ac 100644 --- a/app/react/Metadata/components/specs/CopyFromEntity.spec.tsx +++ b/app/react/Metadata/components/specs/CopyFromEntity.spec.tsx @@ -95,7 +95,15 @@ describe('CopyFromEntity', () => { silent: true, type: 'rrf/change', value: { - metadata: { one: 'number one', two: 'number two', id: 'ABC123' }, + metadata: { + id: { + value: 'ABC123', + }, + one: { + value: 'number one', + }, + two: 'number two', + }, template: 'template_1', title: 'I want to be like you', }, diff --git a/app/react/V2/Routes/Settings/IX/IXSuggestions.tsx b/app/react/V2/Routes/Settings/IX/IXSuggestions.tsx index cca18144da..38d0e7fe8d 100644 --- a/app/react/V2/Routes/Settings/IX/IXSuggestions.tsx +++ b/app/react/V2/Routes/Settings/IX/IXSuggestions.tsx @@ -1,6 +1,6 @@ /* eslint-disable max-lines */ /* eslint-disable max-statements */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { IncomingHttpHeaders } from 'http'; import { LoaderFunction, @@ -28,6 +28,7 @@ import { SuggestionsTitle } from './components/SuggestionsTitle'; import { FiltersSidepanel } from './components/FiltersSidepanel'; import { suggestionsTableColumnsBuilder } from './components/TableElements'; import { PDFSidepanel } from './components/PDFSidepanel'; +import { updateSuggestions, updateSuggestionsByEntity } from './components/helpers'; const SUGGESTIONS_PER_PAGE = 100; const SORTABLE_PROPERTIES = ['entityTitle', 'segment', 'currentValue']; @@ -60,6 +61,9 @@ const IXSuggestions = () => { currentStatus: ixStatus; }; + const [currentSuggestions, setCurrentSuggestions] = useState(suggestions); + useMemo(() => setCurrentSuggestions(suggestions), [suggestions]); + const location = useLocation(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); @@ -126,7 +130,7 @@ const IXSuggestions = () => { entityId: acceptedSuggestion.entityId, })) ); - revalidate(); + setCurrentSuggestions(updateSuggestions(currentSuggestions, acceptedSuggestions)); setNotifications({ type: 'success', text: Suggestion accepted., @@ -167,7 +171,7 @@ const IXSuggestions = () => { Showing   - {from}-{from + suggestions.length - 1} + {from}-{from + currentSuggestions.length - 1}   of @@ -201,7 +205,7 @@ const IXSuggestions = () => { - data={suggestions} + data={currentSuggestions} columns={suggestionsTableColumnsBuilder( filteredTemplates(), acceptSuggestions, @@ -310,6 +314,9 @@ const IXSuggestions = () => { showSidepanel={sidepanel === 'pdf'} setShowSidepanel={closeSidepanel} suggestion={sidepanelSuggestion} + onEntitySave={updatedEntity => + setCurrentSuggestions(updateSuggestionsByEntity(currentSuggestions, updatedEntity)) + } />
); diff --git a/app/react/V2/Routes/Settings/IX/components/PDFSidepanel.tsx b/app/react/V2/Routes/Settings/IX/components/PDFSidepanel.tsx index b475946d09..d7efacdb89 100644 --- a/app/react/V2/Routes/Settings/IX/components/PDFSidepanel.tsx +++ b/app/react/V2/Routes/Settings/IX/components/PDFSidepanel.tsx @@ -2,7 +2,7 @@ /* eslint-disable react/jsx-props-no-spreading */ /* eslint-disable max-statements */ import React, { useEffect, useRef, useState } from 'react'; -import { useLoaderData, useRevalidator } from 'react-router-dom'; +import { useLoaderData } from 'react-router-dom'; import { useForm } from 'react-hook-form'; import { useSetRecoilState } from 'recoil'; import { TextSelection } from 'react-text-selection-handler/dist/TextSelection'; @@ -26,6 +26,7 @@ interface PDFSidepanelProps { showSidepanel: boolean; setShowSidepanel: React.Dispatch>; suggestion: EntitySuggestionType | undefined; + onEntitySave: (entity: ClientEntitySchema) => any; } enum HighlightColors { @@ -144,13 +145,16 @@ const coerceValue = async ( return undefined; }; -const PDFSidepanel = ({ showSidepanel, setShowSidepanel, suggestion }: PDFSidepanelProps) => { +const PDFSidepanel = ({ + showSidepanel, + setShowSidepanel, + suggestion, + onEntitySave, +}: PDFSidepanelProps) => { const { templates } = useLoaderData() as { templates: ClientTemplateSchema[]; }; - const revalidator = useRevalidator(); - const pdfContainerRef = useRef(null); const [pdf, setPdf] = useState(); const [pdfContainerHeight, setPdfContainerHeight] = useState(0); @@ -241,10 +245,17 @@ const PDFSidepanel = ({ showSidepanel, setShowSidepanel, suggestion }: PDFSidepa (savedEntity as FetchResponseError)?.json.prettyMessage; setNotifications({ type: 'error', text: 'An error occurred', details }); - revalidator.revalidate(); } else if (savedFile || savedEntity) { + if (savedFile) { + setPdf(savedFile); + } + + if (savedEntity) { + setEntity(savedEntity); + onEntitySave(savedEntity); + } + setNotifications({ type: 'success', text: 'Saved successfully.' }); - revalidator.revalidate(); } setShowSidepanel(false); diff --git a/app/react/V2/Routes/Settings/IX/components/helpers.ts b/app/react/V2/Routes/Settings/IX/components/helpers.ts new file mode 100644 index 0000000000..059eb72c04 --- /dev/null +++ b/app/react/V2/Routes/Settings/IX/components/helpers.ts @@ -0,0 +1,95 @@ +import { ClientEntitySchema } from 'app/istore'; +import { EntitySuggestionType } from 'shared/types/suggestionType'; + +// eslint-disable-next-line max-statements +const updateSuggestionsByEntity = ( + currentSuggestions: EntitySuggestionType[], + updatedEntity?: ClientEntitySchema +): EntitySuggestionType[] => { + if (!updatedEntity) { + return currentSuggestions; + } + + const suggestionToUpdate = currentSuggestions.find( + currentSuggestion => currentSuggestion.entityId === updatedEntity._id + ); + + const propertyToUpdate = suggestionToUpdate?.propertyName; + + if (!suggestionToUpdate || !propertyToUpdate) { + return currentSuggestions; + } + + if (propertyToUpdate === 'title' && updatedEntity.title) { + const newTitle = updatedEntity.title; + suggestionToUpdate.currentValue = newTitle; + suggestionToUpdate.entityTitle = newTitle; + suggestionToUpdate.state.match = suggestionToUpdate.suggestedValue === newTitle; + } + + if ( + propertyToUpdate !== 'title' && + updatedEntity.metadata && + updatedEntity.metadata[propertyToUpdate]?.length + ) { + const newValue = updatedEntity.metadata[propertyToUpdate]![0].value; + suggestionToUpdate.currentValue = newValue; + suggestionToUpdate.state.match = suggestionToUpdate.suggestedValue === newValue; + } + + if ( + propertyToUpdate !== 'title' && + (!updatedEntity.metadata || !updatedEntity.metadata[propertyToUpdate]?.length) + ) { + suggestionToUpdate.currentValue = ''; + suggestionToUpdate.state.match = suggestionToUpdate.suggestedValue === ''; + } + + const suggestions = currentSuggestions.map(currentSuggestion => { + if (currentSuggestion._id === suggestionToUpdate._id) { + return suggestionToUpdate; + } + return currentSuggestion; + }); + + return suggestions; +}; + +const updateSuggestions = ( + currentSuggestions: EntitySuggestionType[], + suggestionsToAccept: EntitySuggestionType[] | [] +): EntitySuggestionType[] => { + if (!suggestionsToAccept.length) { + return currentSuggestions; + } + + const acceptedSuggestions = suggestionsToAccept.map(suggestionToAccept => { + const updated = { ...suggestionToAccept }; + updated.state.match = true; + updated.currentValue = suggestionToAccept.suggestedValue; + + if ( + suggestionToAccept.propertyName === 'title' && + typeof suggestionToAccept.suggestedValue === 'string' + ) { + updated.entityTitle = suggestionToAccept.suggestedValue; + } + + return updated; + }); + + const merged = [ + ...currentSuggestions + .concat(acceptedSuggestions) + .reduce( + (map, suggestion) => + map.set(suggestion._id, Object.assign(map.get(suggestion._id) || {}, suggestion)), + new Map() + ) + .values(), + ]; + + return merged; +}; + +export { updateSuggestions, updateSuggestionsByEntity }; diff --git a/app/react/V2/Routes/Settings/IX/components/specs/fixtures.ts b/app/react/V2/Routes/Settings/IX/components/specs/fixtures.ts new file mode 100644 index 0000000000..d8734ae188 --- /dev/null +++ b/app/react/V2/Routes/Settings/IX/components/specs/fixtures.ts @@ -0,0 +1,164 @@ +const suggestion1 = { + _id: '1', + language: 'es', + propertyName: 'title', + error: '', + segment: 'suggested value', + suggestedValue: 'suggested value', + state: { + match: false, + labeled: true, + withValue: true, + withSuggestion: true, + hasContext: true, + obsolete: false, + processing: false, + error: false, + }, + selectionRectangles: [ + { + left: 0, + top: 0, + width: 250, + height: 250, + page: '1', + }, + ], + currentValue: 'Entity 1', + entityTitle: 'Entity 1', + entityId: 'entity1', + extractorId: '1', + entityTemplateId: '1', + sharedId: '1', + fileId: '1', + date: 1, +}; + +const suggestion2 = { + _id: '2', + language: 'es', + propertyName: 'title', + error: '', + segment: 'Entity 2', + suggestedValue: 'Entity 2', + state: { + match: true, + labeled: true, + withValue: true, + withSuggestion: true, + hasContext: true, + obsolete: false, + processing: false, + error: false, + }, + selectionRectangles: [ + { + left: 0, + top: 0, + width: 250, + height: 250, + page: '1', + }, + ], + currentValue: 'Entity 2', + entityTitle: 'Entity 2', + entityId: 'entity2', + extractorId: '1', + entityTemplateId: '1', + sharedId: '2', + fileId: '2', + date: 1, +}; + +const suggestion3 = { + _id: '3', + language: 'es', + propertyName: 'document_date', + segment: 'Some value that contains a date', + suggestedValue: 100, + state: { + match: true, + labeled: true, + withValue: true, + withSuggestion: true, + hasContext: true, + obsolete: false, + processing: false, + error: false, + }, + selectionRectangles: [ + { + left: 0, + top: 0, + width: 250, + height: 250, + page: '1', + }, + ], + currentValue: 100, + entityTitle: 'Entity 2', + entityId: 'entity2', + extractorId: '2', + entityTemplateId: '2', + sharedId: '2', + fileId: '3', + date: 1, +}; + +const suggestion4 = { + _id: '4', + language: 'es', + propertyName: 'document_date', + segment: 'Some value that contains a date', + suggestedValue: 500, + state: { + match: false, + labeled: true, + withValue: true, + withSuggestion: true, + hasContext: true, + obsolete: false, + processing: false, + error: false, + }, + selectionRectangles: [ + { + left: 0, + top: 0, + width: 250, + height: 250, + page: '1', + }, + ], + currentValue: 100, + entityTitle: 'Entity 5', + entityId: 'entity5', + extractorId: '2', + entityTemplateId: '2', + sharedId: '5', + fileId: '5', + date: 1, +}; + +const entity1 = { + _id: 'entity1', + language: 'es', + sharedId: '1', + title: 'Entity 1', +}; + +const entity2 = { + _id: 'entity2', + language: 'es', + sharedId: '2', + title: 'Entity 2', + metadata: { + document_date: [ + { + value: 100, + }, + ], + }, +}; + +export { suggestion1, suggestion2, suggestion3, suggestion4, entity1, entity2 }; diff --git a/app/react/V2/Routes/Settings/IX/components/specs/updateSuggestions.spec.ts b/app/react/V2/Routes/Settings/IX/components/specs/updateSuggestions.spec.ts new file mode 100644 index 0000000000..dec0269433 --- /dev/null +++ b/app/react/V2/Routes/Settings/IX/components/specs/updateSuggestions.spec.ts @@ -0,0 +1,35 @@ +import { updateSuggestions } from '../helpers'; +import { suggestion1, suggestion2, suggestion3, suggestion4 } from './fixtures'; + +describe('updateSuggestions', () => { + beforeAll(() => { + jest.clearAllMocks(); + jest.resetModules(); + }); + + it('should update existing suggestions making them accepted', () => { + const accepted = updateSuggestions([suggestion1, suggestion2], [suggestion1]); + expect(accepted).toEqual([ + { + ...suggestion1, + state: { ...suggestion1.state, match: true }, + currentValue: 'suggested value', + entityTitle: 'suggested value', + }, + suggestion2, + ]); + }); + + it('should work with properties other than title', () => { + const accepted = updateSuggestions([suggestion3, suggestion4], [suggestion4]); + expect(accepted).toEqual([ + suggestion3, + { + ...suggestion4, + state: { ...suggestion4.state, match: true }, + currentValue: 500, + entityTitle: 'Entity 5', + }, + ]); + }); +}); diff --git a/app/react/V2/Routes/Settings/IX/components/specs/updateSuggestionsByEntity.spec.ts b/app/react/V2/Routes/Settings/IX/components/specs/updateSuggestionsByEntity.spec.ts new file mode 100644 index 0000000000..d23dcb9ebb --- /dev/null +++ b/app/react/V2/Routes/Settings/IX/components/specs/updateSuggestionsByEntity.spec.ts @@ -0,0 +1,63 @@ +import { updateSuggestionsByEntity } from '../helpers'; +import { suggestion1, suggestion2, suggestion3, entity1, entity2 } from './fixtures'; + +describe('updateSuggestionsByEntity', () => { + it('should update the suggestions current value', () => { + const result = updateSuggestionsByEntity([suggestion1, suggestion2], { + ...entity1, + title: 'New title for entity 1', + }); + + expect(result).toEqual([ + { + ...suggestion1, + currentValue: 'New title for entity 1', + entityTitle: 'New title for entity 1', + }, + suggestion2, + ]); + }); + + it('should update the match status', () => { + const result = updateSuggestionsByEntity([suggestion1, suggestion2], { + ...entity1, + title: 'suggested value', + }); + + expect(result).toEqual([ + { + ...suggestion1, + currentValue: 'suggested value', + entityTitle: 'suggested value', + state: { ...suggestion1.state, match: true }, + }, + suggestion2, + ]); + }); + + it('should work with metadata properties', () => { + const result = updateSuggestionsByEntity([suggestion1, suggestion3], { + ...entity2, + metadata: { + document_date: [ + { + value: 200, + }, + ], + }, + }); + + expect(result).toEqual([ + suggestion1, + { + ...suggestion3, + currentValue: 200, + suggestedValue: 100, + state: { + ...suggestion3.state, + match: false, + }, + }, + ]); + }); +}); diff --git a/cypress.config.ts b/cypress.config.ts index e1ec1f61d1..2d1fb2dd38 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -6,6 +6,8 @@ const { initPlugin } = require('cypress-plugin-snapshots/plugin'); export default defineConfig({ viewportWidth: 1280, viewportHeight: 768, + defaultCommandTimeout: 10000, + requestTimeout: 12000, e2e: { baseUrl: 'http://localhost:3000', video: true, @@ -27,6 +29,24 @@ export default defineConfig({ } } }); + on('before:browser:launch', (browser, launchOptions) => { + if (browser.name === 'chrome' && browser.isHeadless) { + launchOptions.args.push('--window-size=1280,768'); + launchOptions.args.push('--force-device-scale-factor=1'); + } + + if (browser.name === 'electron' && browser.isHeadless) { + launchOptions.preferences.width = 1280; + launchOptions.preferences.height = 768; + } + + if (browser.name === 'firefox' && browser.isHeadless) { + launchOptions.args.push('--width=1280'); + launchOptions.args.push('--height=768'); + } + + return launchOptions; + }); }, }, component: { diff --git a/cypress/e2e/__image_snapshots__/Media metadata should allow add timelinks to an existing entity media property #0.png b/cypress/e2e/__image_snapshots__/Media metadata should allow add timelinks to an existing entity media property #0.png index c7a8222de2..26399d8d8b 100644 Binary files a/cypress/e2e/__image_snapshots__/Media metadata should allow add timelinks to an existing entity media property #0.png and b/cypress/e2e/__image_snapshots__/Media metadata should allow add timelinks to an existing entity media property #0.png differ diff --git a/cypress/e2e/__image_snapshots__/Media metadata should allow media selection on entity creation #0.png b/cypress/e2e/__image_snapshots__/Media metadata should allow media selection on entity creation #0.png index c7a8222de2..5848fdf625 100644 Binary files a/cypress/e2e/__image_snapshots__/Media metadata should allow media selection on entity creation #0.png and b/cypress/e2e/__image_snapshots__/Media metadata should allow media selection on entity creation #0.png differ diff --git a/cypress/e2e/__image_snapshots__/Media metadata should show an error for an invalid property and allow to replace it for a valid one #0.png b/cypress/e2e/__image_snapshots__/Media metadata should show an error for an invalid property and allow to replace it for a valid one #0.png index c7a8222de2..5848fdf625 100644 Binary files a/cypress/e2e/__image_snapshots__/Media metadata should show an error for an invalid property and allow to replace it for a valid one #0.png and b/cypress/e2e/__image_snapshots__/Media metadata should show an error for an invalid property and allow to replace it for a valid one #0.png differ diff --git a/cypress/e2e/copy-from.cy.ts b/cypress/e2e/copy-from.cy.ts new file mode 100644 index 0000000000..c2827f49ea --- /dev/null +++ b/cypress/e2e/copy-from.cy.ts @@ -0,0 +1,135 @@ +import { clearCookiesAndLogin } from './helpers/login'; + +describe('Copy from entity', () => { + before(() => { + const env = { DATABASE_NAME: 'uwazi_e2e', INDEX_NAME: 'uwazi_e2e' }; + cy.exec('yarn e2e-puppeteer-fixtures', { env }); + clearCookiesAndLogin(); + }); + + describe('Creating a new entity', () => { + it('should copy the metadata from an existing entity to create a new one', () => { + cy.contains('button', 'Create entity').click(); + cy.get('[name="library.sidepanel.metadata.title"]').type('New orden de la corte'); + cy.get('#metadataForm').find('select').select('Ordenes de la corte'); + cy.get('#metadataForm').find('.form-group.select').find('select').select('d3b1s0w3lzi'); + + cy.contains('button', 'Copy From').click(); + cy.get('div.copy-from').within(() => { + cy.get('input').type( + 'Artavia Murillo y otros. Resolución de la CorteIDH de 26 de febrero de 2016' + ); + cy.contains( + '.item-name', + 'Artavia Murillo y otros. Resolución de la CorteIDH de 26 de febrero de 2016' + ).click(); + cy.contains('button', 'Copy Highlighted').click(); + }); + cy.get('div.copy-from').should('not.exist'); + + cy.contains('button', 'Save').click(); + }); + + it('should view the new entity', () => { + cy.contains('Entity created').click(); + cy.contains('h2', 'New orden de la corte').click(); + cy.get('.side-panel.metadata-sidepanel.is-active').within(() => { + cy.contains('a', 'View').click(); + }); + cy.contains('h1', 'New orden de la corte'); + }); + + it('should check the data for the new entity', () => { + cy.get('.entity-metadata').within(() => { + cy.get('.metadata-name-mecanismo').within(() => { + cy.contains('Corte Interamericana de Derechos Humanos'); + }); + + cy.get('.metadata-name-fecha').within(() => { + cy.contains('Feb 26, 2016'); + }); + + cy.get('.metadata-name-pa_s').within(() => { + cy.contains('Costa Rica'); + }); + + cy.get('.metadata-name-firmantes').within(() => { + cy.contains('Alberto Pérez Pérez'); + cy.contains('Diego García-Sayán'); + cy.contains('Eduardo Ferrer Mac-Gregor Poisot'); + cy.contains('Eduardo Vio Grossi'); + cy.contains('Humberto Antonio Sierra Porto'); + cy.contains('Roberto de Figueiredo Caldas'); + }); + + cy.get('.metadata-name-tipo').within(() => { + cy.contains('Supervisión de cumplimiento de Sentencia'); + }); + + cy.get('.metadata-name-categor_a').within(() => { + cy.contains('Categoría 1'); + }); + }); + }); + }); + + describe('editing an existing entity', () => { + it('should edit an entity by using copy from', () => { + cy.contains('a', 'Library').click(); + cy.contains( + 'h2', + 'Artavia Murillo y otros. Resolución de la CorteIDH de 26 de febrero de 2016' + ).click(); + cy.contains('button', 'Edit').click(); + cy.contains('button', 'Copy From').click(); + + cy.get('div.copy-from').within(() => { + cy.get('input').type( + 'Apitz Barbera y otros. Resolución de la Presidenta de 18 de diciembre de 2009' + ); + cy.contains( + '.item-name', + 'Apitz Barbera y otros. Resolución de la Presidenta de 18 de diciembre de 2009' + ).click(); + cy.contains('button', 'Copy Highlighted').click(); + }); + cy.get('div.copy-from').should('not.exist'); + + cy.get('[name="library.sidepanel.metadata.title"]').clear(); + cy.get('[name="library.sidepanel.metadata.title"]').type('Edited orden de la corte'); + cy.get('#metadataForm') + .contains('.multiselectItem-name', 'Comisión Interamericana de Derechos Humanos') + .click(); + + cy.contains('button', 'Save').click(); + }); + + it('should view the edited entity', () => { + cy.contains('Entity updated').click(); + cy.contains('h2', 'Edited orden de la corte').click(); + cy.get('.side-panel.metadata-sidepanel.is-active').within(() => { + cy.contains('a', 'View').click(); + }); + cy.contains('h1', 'Edited orden de la corte'); + }); + + it('should check the data for the edited entity', () => { + cy.get('.metadata.tab-content-visible').within(() => { + cy.contains('h1', 'Edited orden de la corte'); + + cy.get('.metadata-name-mecanismo').within(() => { + cy.contains('Comisión Interamericana de Derechos Humanos'); + cy.contains('Corte Interamericana de Derechos Humanos'); + }); + + cy.get('.metadata-name-fecha').contains('Dec 1, 2018'); + + cy.get('.metadata-name-pa_s').contains('Venezuela'); + + cy.get('.metadata-name-firmantes').contains('Cecilia Medina Quiroga'); + + cy.get('.metadata-name-tipo').contains('Supervisión de cumplimiento de Sentencia'); + }); + }); + }); +}); diff --git a/cypress/e2e/media-metadata.cy.ts b/cypress/e2e/media-metadata.cy.ts index cf0be40365..f73ac0c693 100644 --- a/cypress/e2e/media-metadata.cy.ts +++ b/cypress/e2e/media-metadata.cy.ts @@ -30,7 +30,6 @@ describe('Media metadata', { defaultCommandTimeout: 5000 }, () => { const addVideo = (local: boolean = true) => { clickMediaAction('Video', 'Add file'); - if (local) { cy.get('.upload-button input[type=file]') .last() @@ -88,20 +87,19 @@ describe('Media metadata', { defaultCommandTimeout: 5000 }, () => { cy.wait('@saveEntity'); // waiting for video - cy.get('video', { timeout: 2000 }) - .then( - async $video => - new Promise(resolve => { - $video[0].removeAttribute('controls'); - const interval = setInterval(() => { - if ($video[0].readyState >= 3) { - clearInterval(interval); - resolve($video); - } - }, 10); - }) - ) - .should('be.visible'); + cy.get('aside video', { timeout: 5000 }).then( + async $video => + new Promise(resolve => { + $video[0].removeAttribute('controls'); + const interval = setInterval(() => { + const videoElement = $video[0] as HTMLVideoElement; + if (videoElement.readyState >= 3) { + clearInterval(interval); + resolve($video); + } + }, 10); + }) + ); }; it('should allow media selection on entity creation', () => { @@ -128,24 +126,6 @@ describe('Media metadata', { defaultCommandTimeout: 5000 }, () => { addVideo(); cy.addTimeLink(2000, 'Second one'); saveEntity(); - checkMediaSnapshots('.metadata-type-multimedia.metadata-name-video'); - }); - - it('should allow edit media created with timelinks', () => { - cy.contains('h2', 'Reporte audiovisual con lineas de tiempo').click(); - cy.contains('button', 'Edit').should('be.visible').click(); - cy.addTimeLink(4000, 'Second three', 1); - saveEntity(); - checkMediaSnapshots('.metadata-type-multimedia.metadata-name-video'); - }); - - it('should allow remove a timelink from a media property', () => { - cy.contains('h2', 'Reporte audiovisual con lineas de tiempo').click(); - cy.contains('button', 'Edit').should('be.visible').click(); - cy.get('.timelinks-form').scrollIntoView(); - cy.get('.delete-timestamp-btn').eq(1).click(); - saveEntity(); - checkMediaSnapshots('.metadata-type-multimedia.metadata-name-video'); }); it('should allow set an external link from a media property', () => { diff --git a/cypress/e2e/pages/__image_snapshots__/Public Form check created entities should check the first entity #0.png b/cypress/e2e/pages/__image_snapshots__/Public Form check created entities should check the first entity #0.png index f8d347b31e..07d8433913 100644 Binary files a/cypress/e2e/pages/__image_snapshots__/Public Form check created entities should check the first entity #0.png and b/cypress/e2e/pages/__image_snapshots__/Public Form check created entities should check the first entity #0.png differ diff --git a/cypress/e2e/pages/__image_snapshots__/Public Form public form with image and media files should fill the Video field #0.png b/cypress/e2e/pages/__image_snapshots__/Public Form public form with image and media files should fill the Video field #0.png index daec5c10fc..7c43e4181d 100644 Binary files a/cypress/e2e/pages/__image_snapshots__/Public Form public form with image and media files should fill the Video field #0.png and b/cypress/e2e/pages/__image_snapshots__/Public Form public form with image and media files should fill the Video field #0.png differ diff --git a/cypress/e2e/pages/public-form.cy.ts b/cypress/e2e/pages/public-form.cy.ts index ab02ad974d..47062dea08 100644 --- a/cypress/e2e/pages/public-form.cy.ts +++ b/cypress/e2e/pages/public-form.cy.ts @@ -123,10 +123,6 @@ describe('Public Form', () => { // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(1000); cy.addTimeLink(2000, 'Control point'); - cy.get('.form-group.media', { timeout: 2000 }).eq(0).scrollIntoView(); - cy.get('.form-group.media') - .eq(0) - .toMatchImageSnapshot({ disableTimersAndAnimations: true, threshold: 0.08 }); }); it('should fill the Imagen adicional field', () => { @@ -153,7 +149,7 @@ describe('Public Form', () => { it('should check the first entity', () => { cy.contains('h2', 'Test public submit entity').click(); - cy.get('aside.is-active').toMatchImageSnapshot(); + cy.contains('Test public submit entity'); }); it('should check the second entity with files', () => { diff --git a/cypress/e2e/settings/__image_snapshots__/Information Extraction PDF sidepanel should display the PDF sidepanel with the pdf and selection rectangle #0.png b/cypress/e2e/settings/__image_snapshots__/Information Extraction PDF sidepanel should display the PDF sidepanel with the pdf and selection rectangle #0.png index 302da530ef..f6435ec6d0 100644 Binary files a/cypress/e2e/settings/__image_snapshots__/Information Extraction PDF sidepanel should display the PDF sidepanel with the pdf and selection rectangle #0.png and b/cypress/e2e/settings/__image_snapshots__/Information Extraction PDF sidepanel should display the PDF sidepanel with the pdf and selection rectangle #0.png differ diff --git a/cypress/e2e/settings/__image_snapshots__/Information Extraction Suggestions review should display suggestions and be accessible #0.png b/cypress/e2e/settings/__image_snapshots__/Information Extraction Suggestions review should display suggestions and be accessible #0.png index fdcbade955..a17fd0b305 100644 Binary files a/cypress/e2e/settings/__image_snapshots__/Information Extraction Suggestions review should display suggestions and be accessible #0.png and b/cypress/e2e/settings/__image_snapshots__/Information Extraction Suggestions review should display suggestions and be accessible #0.png differ diff --git a/cypress/e2e/settings/__image_snapshots__/Settings mobile menu should enter the account settings #0.png b/cypress/e2e/settings/__image_snapshots__/Settings mobile menu should enter the account settings #0.png index 32ba83f317..5b7e55d105 100644 Binary files a/cypress/e2e/settings/__image_snapshots__/Settings mobile menu should enter the account settings #0.png and b/cypress/e2e/settings/__image_snapshots__/Settings mobile menu should enter the account settings #0.png differ diff --git a/cypress/e2e/settings/__image_snapshots__/Settings mobile menu should go back to the menu #0.png b/cypress/e2e/settings/__image_snapshots__/Settings mobile menu should go back to the menu #0.png index 54ce83913a..e2be7f4c87 100644 Binary files a/cypress/e2e/settings/__image_snapshots__/Settings mobile menu should go back to the menu #0.png and b/cypress/e2e/settings/__image_snapshots__/Settings mobile menu should go back to the menu #0.png differ diff --git a/cypress/e2e/settings/__image_snapshots__/Settings mobile menu should only show the menu #0.png b/cypress/e2e/settings/__image_snapshots__/Settings mobile menu should only show the menu #0.png index 54ce83913a..e2be7f4c87 100644 Binary files a/cypress/e2e/settings/__image_snapshots__/Settings mobile menu should only show the menu #0.png and b/cypress/e2e/settings/__image_snapshots__/Settings mobile menu should only show the menu #0.png differ diff --git a/cypress/e2e/settings/__image_snapshots__/Users accesibility check #0.png b/cypress/e2e/settings/__image_snapshots__/Users accesibility check #0.png index 401b5c231d..15c064da3d 100644 Binary files a/cypress/e2e/settings/__image_snapshots__/Users accesibility check #0.png and b/cypress/e2e/settings/__image_snapshots__/Users accesibility check #0.png differ diff --git a/cypress/e2e/settings/information-extraction.cy.ts b/cypress/e2e/settings/information-extraction.cy.ts index 2eb9c70e14..136322d255 100644 --- a/cypress/e2e/settings/information-extraction.cy.ts +++ b/cypress/e2e/settings/information-extraction.cy.ts @@ -221,15 +221,30 @@ describe('Information Extraction', () => { cy.contains('button', 'Find suggestions').click(); cy.wait('@trainSuggestions'); cy.contains('Training model...'); - cy.contains('Finding suggestions...'); + cy.contains('2023'); }); - it('should accept a single suggestion', () => { - cy.intercept('POST', 'api/suggestions/accept').as('accept'); - cy.contains('button', 'Accept', { timeout: 10000 }).eq(0).click(); - cy.wait('@accept'); - cy.checkA11y(); + it('should accept a single suggestion without affecting the order', () => { + cy.contains('Lorem Ipsum').parent().siblings().contains('button', 'Accept').click(); + + cy.contains('Suggestion accepted.'); cy.contains('button', 'Dismiss').click(); + + const titles = [ + 'Apitz Barbera y otros. Resolución de la Presidenta de 18 de diciembre de 2009', + 'Batman v Superman: Dawn of Justice', + '2023', + 'Spider-Man: Shattered Dimensions', + 'The Spectacular Spider-Man', + 'Uwazi Heroes Investigation', + ]; + + cy.get('tr > td:nth-child(2) > div').each((element, index) => { + const text = element.get(0).innerText; + expect(text).to.be.equal(titles[index]); + }); + + cy.checkA11y(); }); it('should use filters', () => { @@ -246,17 +261,16 @@ describe('Information Extraction', () => { describe('PDF sidepanel', () => { it('should display the PDF sidepanel with the pdf and selection rectangle', () => { cy.contains('button', 'Open PDF').click(); - cy.contains('h1', 'Spider-Man__Into_the_Spider-Verse.pdf'); + cy.contains('h1', 'SamplePDF.pdf'); cy.get('aside').within(() => { cy.get('input').should('have.value', '2023'); }); cy.get('div.highlight-rectangle').should('be.visible'); - cy.contains('span', 'Spider-Man: Into the Spider-Verse'); - cy.get('aside').toMatchImageSnapshot(); + cy.contains('span', 'Lorem Ipsum'); }); it('should not render pdf pages that are not visible', () => { - cy.get('[data-region-selector-id="3"]').within(() => { + cy.get('[data-region-selector-id="2"]').within(() => { cy.get('div').should('be.empty'); }); }); @@ -266,38 +280,66 @@ describe('Information Extraction', () => { cy.get('div.highlight-rectangle').should('have.length', 0); }); + it('should clear the filters', () => { + cy.contains('button', 'Cancel').click(); + cy.contains('button', 'Stats & Filters').click(); + cy.contains('button', 'Clear all').click(); + }); + it('should click to fill with a new text', () => { - //@ts-ignore - cy.contains('span[role="presentation"]', 'Spider-Man: Into the Spider-Verse').setSelection( - 'Spider-Man: Into the Spider-Verse' - ); + cy.contains('The Spectacular Spider-Man').parent().siblings().last().click(); + cy.get('aside').within(() => { + cy.get('input').clear(); + }); + cy.get('#pdf-container').scrollTo(0, 0); + cy.contains('button', 'Clear PDF selection').click(); + cy.contains('span[role="presentation"]', 'The Spectacular Spider-Man') + .eq(0) + //@ts-ignore + .setSelection('The Spectacular Spider-Man'); + cy.contains('button', 'Click to fill').click(); cy.get('div.highlight-rectangle').should('be.visible'); cy.get('aside').within(() => { - cy.get('input').should('have.value', 'Spider-Man: Into the Spider-Verse'); + cy.get('input').should('have.value', 'The Spectacular Spider-Man'); }); }); it('should manually edit the field and save', () => { cy.get('aside').within(() => { - cy.get('input').type(' edited'); + cy.get('input').clear(); + cy.get('input').type('A title'); cy.contains('button', 'Accept').click(); }); cy.contains('Saved successfully'); cy.contains('button', 'Dismiss').click(); - cy.contains('button', 'Stats & Filters').click(); - cy.contains('button', 'Clear all').click(); - cy.get('tbody').within(() => { - cy.get('tr:nth-child(3)').within(() => { - cy.get('td:nth-child(2)').should('contain', 'Spider-Man: Into the Spider-Verse edited'); - }); + cy.contains('A title'); + }); + + it('should check that the table updated and the ordering is not affected', () => { + const titles = [ + '2023', + 'Apitz Barbera y otros. Resolución de la Presidenta de 18 de diciembre de 2009', + 'Batman v Superman: Dawn of Justice', + 'Spider-Man: Shattered Dimensions', + 'A title', + 'Uwazi Heroes Investigation', + ]; + + cy.get('tr > td:nth-child(2) > div').each((element, index) => { + const text = element.get(0).innerText; + expect(text).to.be.equal(titles[index]); }); }); it('should open the pdf on the page of the selection', () => { cy.contains('a', 'Metadata Extraction').eq(0).click(); cy.contains('Fechas from relevant templates').siblings().last().click(); - cy.contains('Spider-Man: Into the Spider-Verse edited').parent().siblings().last().click(); + cy.contains('Apitz Barbera y otros. Resolución de la Presidenta de 18 de diciembre de 2009') + .parent() + .siblings() + .last() + .click(); cy.get('aside').within(() => { cy.get('input').should('have.value', '2018-12-01'); cy.contains('New York City teenager Miles Morales'); diff --git a/cypress/e2e/settings/languages.cy.ts b/cypress/e2e/settings/languages.cy.ts index cb69d52121..85d0c22065 100644 --- a/cypress/e2e/settings/languages.cy.ts +++ b/cypress/e2e/settings/languages.cy.ts @@ -30,8 +30,6 @@ describe('Languages', () => { it('should install new languages', () => { const BACKEND_LANGUAGE_INSTALL_DELAY = 25000; cy.intercept('POST', 'api/translations/languages').as('addLanguage'); - cy.get('[data-testid=modal] input[type=text]').type('Danish'); - cy.contains('button', 'Danish').click(); cy.clearAndType('[data-testid=modal] input[type=text]', 'Basque'); cy.contains('button', 'Basque').click(); @@ -40,7 +38,6 @@ describe('Languages', () => { cy.wait('@addLanguage'); cy.contains('Dismiss').click(); - cy.contains('Danish', { timeout: BACKEND_LANGUAGE_INSTALL_DELAY }); cy.contains('Basque', { timeout: BACKEND_LANGUAGE_INSTALL_DELAY }); cy.contains('Languages installed successfully').click(); }); diff --git a/cypress/e2e/settings/mobile-settings-menu.cy.ts b/cypress/e2e/settings/mobile-settings-menu.cy.ts index 62b6689266..2c4dcb0229 100644 --- a/cypress/e2e/settings/mobile-settings-menu.cy.ts +++ b/cypress/e2e/settings/mobile-settings-menu.cy.ts @@ -8,7 +8,7 @@ describe('Settings mobile menu', () => { }); beforeEach(() => { - cy.viewport(384, 720); + cy.viewport(384, 768); }); it('should login', () => { @@ -24,18 +24,18 @@ describe('Settings mobile menu', () => { cy.location().should(location => { expect(location.pathname).to.contain('settings'); }); - cy.get('body').toMatchImageSnapshot(); + cy.get('.tw-content').should('not.exist'); }); it('should enter the account settings', () => { cy.intercept('api/user').as('getUser'); cy.contains('a', 'Account').click(); cy.wait('@getUser'); - cy.get('body').toMatchImageSnapshot(); + cy.get('.tw-content').should('be.visible'); }); it('should go back to the menu', () => { cy.contains('a', 'Navigate back').click(); - cy.get('body').toMatchImageSnapshot(); + cy.get('.tw-content').should('not.exist'); }); }); diff --git a/cypress/e2e/settings/users.cy.ts b/cypress/e2e/settings/users.cy.ts index 8443a9ba5d..2839e271c6 100644 --- a/cypress/e2e/settings/users.cy.ts +++ b/cypress/e2e/settings/users.cy.ts @@ -24,7 +24,6 @@ describe('Users', () => { it('accesibility check', () => { cy.get('caption').within(() => cy.contains('span', 'Users')); cy.checkA11y(); - cy.getByTestId('settings-content').toMatchImageSnapshot(); cy.contains('button', 'Add user').click(); cy.contains('h1', 'New user'); cy.checkA11y(); @@ -45,6 +44,7 @@ describe('Users', () => { cy.get('#username').type('User_1'); cy.get('#email').type('user@mailer.com'); cy.get('#password').type('secret'); + cy.getByTestId('multiselect').scrollIntoView(); cy.getByTestId('multiselect').within(() => { cy.get('button').click(); cy.contains('Activistas').click(); diff --git a/database/elastic_mapping/elasticMapFactory.ts b/database/elastic_mapping/elasticMapFactory.ts index ad3927a252..09bc2b03c7 100644 --- a/database/elastic_mapping/elasticMapFactory.ts +++ b/database/elastic_mapping/elasticMapFactory.ts @@ -1,15 +1,14 @@ -import { getConnection, getClient } from 'api/common.v2/database/getConnectionForCurrentTenant'; -import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; +import { getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant'; import { MongoSettingsDataSource } from 'api/settings.v2/database/MongoSettingsDataSource'; import { RelationshipPropertyMappingFactory } from 'api/templates.v2/database/mappings/RelationshipPropertyMappingFactory'; import { MongoTemplatesDataSource } from 'api/templates.v2/database/MongoTemplatesDataSource'; import { TemplateSchema } from 'shared/types/templateType'; +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { propertyMappings } from './mappings'; const createNewRelationshipMappingFactory = async () => { const db = getConnection(); - const client = getClient(); - const transactionManager = new MongoTransactionManager(client); + const transactionManager = DefaultTransactionManager(); const settingsDataSource = new MongoSettingsDataSource(db, transactionManager); if (!(await settingsDataSource.readNewRelationshipsAllowed())) { diff --git a/e2e/suite1/copy-from.test.ts b/e2e/suite1/copy-from.test.ts deleted file mode 100644 index b2fb41df61..0000000000 --- a/e2e/suite1/copy-from.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { adminLogin, logout } from '../helpers/login'; -import proxyMock from '../helpers/proxyMock'; -import insertFixtures from '../helpers/insertFixtures'; -import disableTransitions from '../helpers/disableTransitions'; -import { host } from '../config'; - -describe('Copy from', () => { - beforeAll(async () => { - await insertFixtures(); - await proxyMock(); - await adminLogin(); - await page.goto(`${host}/library`); - await disableTransitions(); - }); - - afterAll(async () => { - await logout(); - }); - - it('should create a new entity', async () => { - await expect(page).toClick( - 'div.item-document:nth-child(3) > div:nth-child(1) > h2:nth-child(1)', - { text: 'Artavia Murillo y otros' } - ); - await expect(page).toClick('.sidepanel-footer > .btn-cluster > a', { text: 'View' }); - await expect(page).toClick('.tab-link', { - text: 'Relationships', - }); - await expect(page).toClick('.entity-footer > .btn-cluster > .edit-metadata', { text: 'Edit' }); - await expect(page).toClick('button', { - text: 'Add entities / documents', - }); - await expect(page).toClick('button', { - text: 'Create Entity', - }); - await expect(page).toFill('textarea[name="relationships.metadata.title"]', 'Test title'); - await expect(page).toSelect('select', 'Causa'); - }); - - it('should copy the metadata from an existing entity', async () => { - await expect(page).toClick('button', { - text: 'Copy From', - }); - await expect(page).toFill( - 'aside.connections-metadata div.search-box > div > input', - 'artavia', - { delay: 100 } - ); - await expect(page).toClick('div.copy-from .item-info', { text: 'Artavia Murillo et al' }); - await expect(page).toClick('button', { text: 'Copy Highlighted' }); - await expect(page).toClick( - 'div.btn-cluster:nth-child(2) > button:nth-child(2) > span:nth-child(1) ', - { - text: 'Save', - } - ); - await expect(page).toClick('.alert.alert-success'); - await expect(page).toClick('.entity-footer > div > button', { text: 'Save' }); - }); - - it('should check the data', async () => { - await expect(page).toClick('div.item-info', { text: 'Test title' }); - await expect(page).toMatchElement('.metadata-type-relationship > dd:nth-child(2) ', { - text: 'Costa Rica', - }); - await expect(page).toMatchElement('.metadata-type-select > dd:nth-child(2)', { - text: 'Activo', - }); - await expect(page).toMatchElement('.metadata-type-multiselect > dd:nth-child(2)', { - text: 'Derechos reproductivos', - }); - await expect(page).toMatchElement('dl.metadata-type-multidate:nth-child(8) > dd:nth-child(2)', { - text: 'Dec 19, 2011', - }); - }); -}); diff --git a/package.json b/package.json index 824e10b063..d299bd2718 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uwazi", - "version": "1.140.0", + "version": "1.141.0", "description": "Uwazi is a free, open-source solution for organising, analysing and publishing your documents.", "keywords": [ "react" @@ -36,8 +36,8 @@ "tailwind-watch": "yarn run tailwind --watch", "tailwind": " npx tailwindcss -i ./app/react/App/styles/main.css -o ./app/react/App/styles/globals.css", "hot": "export HOT=true; export BABEL_ENV=debug; yarn run watch-types & yarn tailwind-watch & yarn run dev-server & yarn run webpack-server", - "hot-e2e": "DATABASE_NAME=uwazi_e2e INDEX_NAME=uwazi_e2e yarn hot", - "run-e2e": "DATABASE_NAME=uwazi_e2e INDEX_NAME=uwazi_e2e yarn run-production", + "hot-e2e": "DATABASE_NAME=uwazi_e2e INDEX_NAME=uwazi_e2e EXTERNAL_SERVICES=true yarn hot", + "run-e2e": "DATABASE_NAME=uwazi_e2e INDEX_NAME=uwazi_e2e EXTERNAL_SERVICES=true yarn run-production", "hot-inspect": "export HOT=true; export BABEL_ENV=debug; yarn run watch-types & yarn dev-server --inspect & yarn run webpack-server", "hot-debug": "export HOT=true; export BABEL_ENV=debug; node --no-experimental-fetch --inspect-brk=9229 server.js", "reindex": "node --no-experimental-fetch scripts/run.js ../database/reindex_elastic.js", @@ -165,7 +165,7 @@ "lodash": "^4.17.21", "luxon": "^3.4.3", "mark.js": "^8.11.1", - "markdown-it": "13.0.1", + "markdown-it": "13.0.2", "markdown-it-container": "3.0.0", "mime-types": "^2.1.35", "moment": "^2.29.4", @@ -203,7 +203,7 @@ "react-redux": "5.0.6", "react-redux-form": "^1.16.14", "react-render-if-visible": "^2.1.1", - "react-router-dom": "6.14.1", + "react-router-dom": "6.17.0", "react-table": "^7.8.0", "react-table-sticky": "^1.1.3", "react-tabs": "^6.0.1", @@ -331,7 +331,7 @@ "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", "cypress": "12.17.4", - "cypress-axe": "^1.4.0", + "cypress-axe": "^1.5.0", "cypress-plugin-snapshots": "^1.4.4", "enzyme": "3.11.0", "enzyme-to-json": "^3.6.2", @@ -362,8 +362,8 @@ "node-polyfill-webpack-plugin": "^2.0.1", "nodemon": "^2.0.22", "plop": "^3.0.6", - "postcss": "^8.4.23", - "prettier": "3.0.2", + "postcss": "^8.4.31", + "prettier": "3.0.3", "puppeteer": "^13.5.2", "react-dnd-test-backend": "15.1.1", "redux-mock-store": "^1.5.4", diff --git a/scripts/compareTranslations.js b/scripts/compareTranslations.js index 5902db31b4..5ec028d918 100644 --- a/scripts/compareTranslations.js +++ b/scripts/compareTranslations.js @@ -19,28 +19,24 @@ const getClient = async () => { const getTranslationsFromDB = async () => { const client = await getClient(); const db = client.db(process.env.DATABASE_NAME || 'uwazi_development'); - const translations = await db.collection('translations').find().toArray(); + const translations = await db.collection('translationsV2').find().toArray(); client.close(); - const locToSystemContext = {}; - translations.forEach(tr => { - const context = tr.contexts.find(c => c.id === 'System'); - const [keys, values, keyValues] = context.values.reduce( - (newValues, currentTranslation) => { - newValues[0].push(currentTranslation.key); - newValues[1].push(currentTranslation.value); - // eslint-disable-next-line no-param-reassign - newValues[2] = { ...newValues[2], [currentTranslation.key]: currentTranslation.value }; - return newValues; - }, - [[], [], {}] - ); - locToSystemContext[tr.locale] = { keys, values, keyValues }; - }); + const locToSystemContext = _.mapValues( + _.groupBy( + translations.filter(tr => tr.context.id === 'System'), + 'language' + ), + values => ({ + keys: values.map(tr => tr.key), + values: values.map(tr => tr.value), + keyValues: _.reduce(values, (keyValues, tr) => ({ ...keyValues, [tr.key]: tr.value }), {}), + }) + ); return locToSystemContext; }; -const getAvaiableLanguages = async () => +const getAvailableLanguages = async () => new Promise((resolve, reject) => { fs.readdir(TRANSLATIONS_DIR, (err, files) => { if (err) reject(err); @@ -157,7 +153,7 @@ async function compareTranslations(locale, update, outdir) { const valuesFromDB = dbTranslations.en.values; const dbKeyValues = dbTranslations.en.keyValues; - const languages = locale ? [locale] : await getAvaiableLanguages(); + const languages = locale ? [locale] : await getAvailableLanguages(); const result = await Promise.all( languages.map(language => processLanguage(keysFromDB, valuesFromDB, language)) ); @@ -194,6 +190,7 @@ async function compareTranslations(locale, update, outdir) { } } catch (e) { process.stdout.write(` === An error occurred === \n ${e}\n`); + process.exit(1); } } diff --git a/yarn.lock b/yarn.lock index ed98fb82dc..c1034a2a51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3911,10 +3911,10 @@ resolved "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz" integrity sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg== -"@remix-run/router@1.7.1", "@remix-run/router@^1.4.0": - version "1.7.1" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.1.tgz#fea7ac35ae4014637c130011f59428f618730498" - integrity sha512-bgVQM4ZJ2u2CM8k1ey70o1ePFXsEzYVZoWghh6WjM8p59jQ7HxzbHW4SbnWFG7V9ig9chLawQxDTZ3xzOF8MkQ== +"@remix-run/router@1.10.0", "@remix-run/router@^1.4.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.10.0.tgz#e2170dc2049b06e65bbe883adad0e8ddf8291278" + integrity sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw== "@sentry-internal/tracing@7.69.0": version "7.69.0" @@ -8981,10 +8981,10 @@ cwd@^0.10.0: find-pkg "^0.1.2" fs-exists-sync "^0.1.0" -cypress-axe@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-1.4.0.tgz#e67482bfe9e740796bf77c7823f19781a8a2faff" - integrity sha512-Ut7NKfzjyKm0BEbt2WxuKtLkIXmx6FD2j0RwdvO/Ykl7GmB/qRQkwbKLk3VP35+83hiIr8GKD04PDdrTK5BnyA== +cypress-axe@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-1.5.0.tgz#95082734583da77b51ce9b7784e14a442016c7a1" + integrity sha512-Hy/owCjfj+25KMsecvDgo4fC/781ccL+e8p+UUYoadGVM2ogZF9XIKbiM6KI8Y3cEaSreymdD6ZzccbI2bY0lQ== cypress-plugin-snapshots@^1.4.4: version "1.4.4" @@ -14509,10 +14509,10 @@ markdown-it-container@3.0.0: resolved "https://registry.npmjs.org/markdown-it-container/-/markdown-it-container-3.0.0.tgz" integrity sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw== -markdown-it@13.0.1: - version "13.0.1" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-13.0.1.tgz#c6ecc431cacf1a5da531423fc6a42807814af430" - integrity sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q== +markdown-it@13.0.2: + version "13.0.2" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-13.0.2.tgz#1bc22e23379a6952e5d56217fbed881e0c94d536" + integrity sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w== dependencies: argparse "^2.0.1" entities "~3.0.1" @@ -16728,10 +16728,10 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.3.11, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.24: - version "8.4.24" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.24.tgz#f714dba9b2284be3cc07dbd2fc57ee4dc972d2df" - integrity sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg== +postcss@^8.3.11, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.24, postcss@^8.4.31: + version "8.4.31" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" + integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== dependencies: nanoid "^3.3.6" picocolors "^1.0.0" @@ -16772,10 +16772,10 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.2.tgz#78fcecd6d870551aa5547437cdae39d4701dca5b" - integrity sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ== +prettier@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" + integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== prettier@^1.16.4: version "1.19.1" @@ -17429,20 +17429,20 @@ react-resize-detector@^8.0.4: dependencies: lodash "^4.17.21" -react-router-dom@6.14.1: - version "6.14.1" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.14.1.tgz#0ad7ba7abdf75baa61169d49f096f0494907a36f" - integrity sha512-ssF6M5UkQjHK70fgukCJyjlda0Dgono2QGwqGvuk7D+EDGHdacEN3Yke2LTMjkrpHuFwBfDFsEjGVXBDmL+bWw== +react-router-dom@6.17.0: + version "6.17.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.17.0.tgz#ea73f89186546c1cf72b10fcb7356d874321b2ad" + integrity sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ== dependencies: - "@remix-run/router" "1.7.1" - react-router "6.14.1" + "@remix-run/router" "1.10.0" + react-router "6.17.0" -react-router@6.14.1: - version "6.14.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.14.1.tgz#5e82bcdabf21add859dc04b1859f91066b3a5810" - integrity sha512-U4PfgvG55LdvbQjg5Y9QRWyVxIdO1LlpYT7x+tMAxd9/vmiPuJhIwdxZuIQLN/9e3O4KFDHYfR9gzGeYMasW8g== +react-router@6.17.0: + version "6.17.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.17.0.tgz#7b680c4cefbc425b57537eb9c73bedecbdc67c1e" + integrity sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA== dependencies: - "@remix-run/router" "1.7.1" + "@remix-run/router" "1.10.0" react-shallow-renderer@^16.15.0: version "16.15.0"