diff --git a/webapp/src/ts/services/telemetry.service.ts b/webapp/src/ts/services/telemetry.service.ts index 6526a1f4320..cdc7979bc70 100644 --- a/webapp/src/ts/services/telemetry.service.ts +++ b/webapp/src/ts/services/telemetry.service.ts @@ -1,7 +1,6 @@ import { Inject, Injectable, NgZone } from '@angular/core'; import { DOCUMENT } from '@angular/common'; import { v4 as uuidv4 } from 'uuid'; -import * as moment from 'moment'; import { DbService } from '@mm-services/db.service'; import { SessionService } from '@mm-services/session.service'; @@ -13,7 +12,6 @@ export class TelemetryService { private readonly TELEMETRY_PREFIX = 'telemetry'; private readonly POUCH_PREFIX = '_pouch_'; private readonly NAME_DIVIDER = '-'; - private readonly DATE_FORMAT = 'YYYY-MM-DD'; // Intentionally scoped to the whole browser (for this domain). We can then tell if multiple users use the same device private readonly DEVICE_ID_KEY = 'medic-telemetry-device-id'; private isAggregationRunning = false; @@ -117,10 +115,15 @@ export class TelemetryService { }; } + private isValidTelemetryDBName(dbName): boolean { + const expression = new RegExp(`^${this.TELEMETRY_PREFIX}-[0-9]{4}-[0-1]?[0-9]-[0-3]?[0-9].*`, 'g'); + return expression.test(dbName); + } + private async getTelemetryDBs(databases): Promise { return databases ?.map(db => db.name?.replace(this.POUCH_PREFIX, '') || '') - .filter(dbName => dbName?.startsWith(this.TELEMETRY_PREFIX)); + .filter(dbName => this.isValidTelemetryDBName(dbName)); } /** @@ -182,10 +185,10 @@ export class TelemetryService { * Moment when the aggregation starts (i.e. the beginning of the current day) */ private getToday(): TodayMoment { - const today = moment().startOf('day'); + const today = new Date(); return { today, - formatted: today.format(this.DATE_FORMAT), + formatted: `${today.getFullYear()}-${today.getMonth() + 1}-${today.getDate()}`, }; } @@ -310,7 +313,7 @@ export class TelemetryService { * It was decided to not aggregate the DB content. * @private */ - private async deleteDeprecatedTelemetryDB(databases) { + private async deleteDeprecatedTelemetryDB(databases) { // NOSONAR if (this.hasTransitionFinished) { return; } @@ -318,10 +321,11 @@ export class TelemetryService { databases?.forEach(db => { const nameNoPrefix = db.name?.replace(this.POUCH_PREFIX, '') || ''; - // Skips new Telemetry DB, then matches the old deprecated Telemetry DB. - if (!nameNoPrefix.startsWith(this.TELEMETRY_PREFIX) + // Skips new Telemetry DB, then matches malformed or the old deprecated Telemetry DB. + if (!this.isValidTelemetryDBName(nameNoPrefix) && nameNoPrefix.includes(this.TELEMETRY_PREFIX) && nameNoPrefix.includes(this.sessionService.userCtx().name)) { + console.warn(`Invalid telemetry database name, deleting database. Name: ${nameNoPrefix}`); this.windowRef?.indexedDB.deleteDatabase(db.name); } }); diff --git a/webapp/tests/karma/ts/services/telemetry.service.spec.ts b/webapp/tests/karma/ts/services/telemetry.service.spec.ts index 606ff16c486..cbf809ae26d 100644 --- a/webapp/tests/karma/ts/services/telemetry.service.spec.ts +++ b/webapp/tests/karma/ts/services/telemetry.service.spec.ts @@ -17,6 +17,7 @@ describe('TelemetryService', () => { let clock; let telemetryDb; let consoleErrorSpy; + let consoleWarnSpy; let windowMock; const windowScreenOriginal = { @@ -90,6 +91,7 @@ describe('TelemetryService', () => { getStub.returns(medicDb); dbService = { get: getStub }; consoleErrorSpy = sinon.spy(console, 'error'); + consoleWarnSpy = sinon.spy(console, 'warn'); telemetryDb = { post: sinon.stub().resolves(), close: sinon.stub(), @@ -102,7 +104,7 @@ describe('TelemetryService', () => { sessionService = { userCtx: sinon.stub().returns({ name: 'greg' }) }; windowMock = { PouchDB: sinon.stub().returns(telemetryDb), - indexedDB: { databases: sinon.stub() }, + indexedDB: { databases: sinon.stub(), deleteDatabase: sinon.stub() }, localStorage: { getItem: sinon.stub(), setItem: sinon.stub() }, }; const documentMock = { @@ -132,23 +134,69 @@ describe('TelemetryService', () => { it('should record a piece of telemetry', async () => { medicDb.query.resolves({ rows: [] }); telemetryDb.query.resolves({ rows: [] }); + const invalidDBNameWarning = 'Invalid telemetry database name, deleting database. Name:'; + const oldTelemetryDBNames = [ + { name: '_pouch_medic-user-koko-telemetry-98y7c3a1-5a1a-4d3f-a076-d86ec38b1d87' }, + { name: '_pouch_medic-user-greg-telemetry-59d4c3a1-5a1a-4d3f-a076-d86ec38b1d32' }, + ]; + const wrongDBNames = [ + { name: '_pouch_telemetry-59d4c3a1-5a1a-4d3f-a076-d86ec38b1d32' }, + { name: '_pouch_telemetry-2018-greg' }, + { name: '_pouch_telemetry-2018-01-greg' }, + { name: '_pouch_telemetry-11-10-greg' }, + { name: '_pouch_telemetry-10-greg' }, + { name: '_pouch_telemetry-greg' }, + { name: '_pouch_telemetry-२०२४-०२-०९-greg' }, + { name: '_pouch_telemetry' }, + { name: undefined }, + ]; windowMock.indexedDB.databases.resolves([ + ...oldTelemetryDBNames, { name: '_pouch_telemetry-2018-11-10-greg' }, + { name: '_pouch_telemetry-2018-12-31-greg' }, + ...wrongDBNames, + { name: '_pouch_telemetry-2019-1-1-greg' }, + { name: '_pouch_telemetry-2019-1-22-greg' }, { name: '_pouch_some-other-db' }, - { name: '_pouch_telemetry-2018-11-09-greg' }, + { name: '_pouch_telemetry-2018-10-09-greg' }, ]); await service.record('test', 100); expect(consoleErrorSpy.notCalled).to.be.true; + expect(consoleWarnSpy.callCount).to.equal(7); + expect(consoleWarnSpy.args).to.have.deep.members([ + [ `${invalidDBNameWarning} medic-user-greg-telemetry-59d4c3a1-5a1a-4d3f-a076-d86ec38b1d32` ], + [ `${invalidDBNameWarning} telemetry-2018-greg` ], + [ `${invalidDBNameWarning} telemetry-2018-01-greg` ], + [ `${invalidDBNameWarning} telemetry-11-10-greg` ], + [ `${invalidDBNameWarning} telemetry-10-greg` ], + [ `${invalidDBNameWarning} telemetry-greg` ], + [ `${invalidDBNameWarning} telemetry-२०२४-०२-०९-greg` ], + ]); + expect(windowMock.indexedDB.deleteDatabase.callCount).to.equal(7); + expect(windowMock.indexedDB.deleteDatabase.args).to.have.deep.members([ + [ '_pouch_medic-user-greg-telemetry-59d4c3a1-5a1a-4d3f-a076-d86ec38b1d32' ], + [ '_pouch_telemetry-2018-greg' ], + [ '_pouch_telemetry-2018-01-greg' ], + [ '_pouch_telemetry-11-10-greg' ], + [ '_pouch_telemetry-10-greg' ], + [ '_pouch_telemetry-greg' ], + [ '_pouch_telemetry-२०२४-०२-०९-greg' ], + ]); expect(telemetryDb.post.calledOnce).to.be.true; expect(telemetryDb.post.args[0][0]).to.deep.include({ key: 'test', value: 100 }); expect(telemetryDb.post.args[0][0].date_recorded).to.be.above(0); expect(windowMock.indexedDB.databases.calledOnce).to.be.true; - expect(windowMock.PouchDB.calledTwice).to.be.true; - expect(windowMock.PouchDB.args[0]).to.deep.equal([ 'telemetry-2018-11-09-greg' ]); - expect(windowMock.PouchDB.args[1]).to.deep.equal([ 'telemetry-2018-11-10-greg' ]); - expect(telemetryDb.destroy.calledOnce).to.be.true; + expect(windowMock.PouchDB.callCount).to.equal(5); + expect(windowMock.PouchDB.args).to.have.deep.members([ + [ 'telemetry-2018-12-31-greg' ], + [ 'telemetry-2019-1-1-greg' ], + [ 'telemetry-2018-10-09-greg' ], + [ 'telemetry-2019-1-22-greg' ], + [ 'telemetry-2018-11-10-greg' ], + ]); + expect(telemetryDb.destroy.callCount).to.equal(4); }); it('should default the value to 1 if not passed', async () => {