diff --git a/services/web/app/src/Features/InactiveData/InactiveProjectManager.js b/services/web/app/src/Features/InactiveData/InactiveProjectManager.js index fec9405a44..cdbcfd888b 100644 --- a/services/web/app/src/Features/InactiveData/InactiveProjectManager.js +++ b/services/web/app/src/Features/InactiveData/InactiveProjectManager.js @@ -1,12 +1,15 @@ const OError = require('@overleaf/o-error') const logger = require('@overleaf/logger') const DocstoreManager = require('../Docstore/DocstoreManager') +const DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler') const ProjectGetter = require('../Project/ProjectGetter') const ProjectUpdateHandler = require('../Project/ProjectUpdateHandler') const { Project } = require('../../models/Project') const { ObjectId } = require('mongodb-legacy') +const Modules = require('../../infrastructure/Modules') const { READ_PREFERENCE_SECONDARY } = require('../../infrastructure/mongodb') const { callbackifyAll } = require('@overleaf/promise-utils') +const Metrics = require('@overleaf/metrics') const MILISECONDS_IN_DAY = 86400000 const InactiveProjectManager = { @@ -93,6 +96,26 @@ const InactiveProjectManager = { async deactivateProject(projectId) { logger.debug({ projectId }, 'deactivating inactive project') + // ensure project is removed from document updater (also flushes updates to history) + try { + await DocumentUpdaterHandler.promises.flushProjectToMongoAndDelete( + projectId + ) + } catch (err) { + logger.warn( + { err, projectId }, + 'error flushing project to mongo when archiving' + ) + Metrics.inc('inactive-project', 1, { + method: 'archive', + status: 'flush-error', + }) + throw err + } + + await Modules.promises.hooks.fire('deactivateProject', projectId) + + // now archive the project and mark it as inactive try { await DocstoreManager.promises.archiveProject(projectId) await ProjectUpdateHandler.promises.markAsInactive(projectId) diff --git a/services/web/test/unit/src/InactiveData/InactiveProjectManagerTests.js b/services/web/test/unit/src/InactiveData/InactiveProjectManagerTests.js index 32d402a7eb..5c0bf71862 100644 --- a/services/web/test/unit/src/InactiveData/InactiveProjectManagerTests.js +++ b/services/web/test/unit/src/InactiveData/InactiveProjectManagerTests.js @@ -11,12 +11,18 @@ const { expect } = require('chai') describe('InactiveProjectManager', function () { beforeEach(function () { this.settings = {} + this.metrics = { inc: sinon.stub() } this.DocstoreManager = { promises: { unarchiveProject: sinon.stub(), archiveProject: sinon.stub(), }, } + this.DocumentUpdaterHandler = { + promises: { + flushProjectToMongoAndDelete: sinon.stub(), + }, + } this.ProjectUpdateHandler = { promises: { markAsActive: sinon.stub(), @@ -24,14 +30,19 @@ describe('InactiveProjectManager', function () { }, } this.ProjectGetter = { promises: { getProject: sinon.stub() } } + this.Modules = { promises: { hooks: { fire: sinon.stub() } } } this.InactiveProjectManager = SandboxedModule.require(modulePath, { requires: { 'mongodb-legacy': { ObjectId }, '@overleaf/settings': this.settings, + '@overleaf/metrics': this.metrics, '../Docstore/DocstoreManager': this.DocstoreManager, + '../DocumentUpdater/DocumentUpdaterHandler': + this.DocumentUpdaterHandler, '../Project/ProjectUpdateHandler': this.ProjectUpdateHandler, '../Project/ProjectGetter': this.ProjectGetter, '../../models/Project': {}, + '../../infrastructure/Modules': this.Modules, '../../infrastructure/mongodb': { ObjectId, READ_PREFERENCE_SECONDARY: ReadPreference.secondaryPreferred.mode, @@ -94,13 +105,21 @@ describe('InactiveProjectManager', function () { }) describe('deactivateProject', function () { - it('should call unarchiveProject and markAsInactive', async function () { + it('should call archiveProject and markAsInactive after flushing', async function () { this.DocstoreManager.promises.archiveProject.resolves() + this.DocumentUpdaterHandler.promises.flushProjectToMongoAndDelete.resolves() this.ProjectUpdateHandler.promises.markAsInactive.resolves() + this.Modules.promises.hooks.fire.resolves() await this.InactiveProjectManager.promises.deactivateProject( this.project_id ) + this.DocumentUpdaterHandler.promises.flushProjectToMongoAndDelete + .calledWith(this.project_id) + .should.equal(true) + this.Modules.promises.hooks.fire + .calledWith('deactivateProject', this.project_id) + .should.equal(true) this.DocstoreManager.promises.archiveProject .calledWith(this.project_id) .should.equal(true) @@ -111,11 +130,16 @@ describe('InactiveProjectManager', function () { it('should not call markAsInactive if there was a problem archiving in docstore', async function () { this.DocstoreManager.promises.archiveProject.rejects() + this.DocumentUpdaterHandler.promises.flushProjectToMongoAndDelete.resolves() this.ProjectUpdateHandler.promises.markAsInactive.resolves() + this.Modules.promises.hooks.fire.resolves() await expect( this.InactiveProjectManager.promises.deactivateProject(this.project_id) ).to.be.rejected + this.DocumentUpdaterHandler.promises.flushProjectToMongoAndDelete + .calledWith(this.project_id) + .should.equal(true) this.DocstoreManager.promises.archiveProject .calledWith(this.project_id) .should.equal(true)