From 4a9bc7dc288e4f655cec10d7a8b475711afb12b8 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Wed, 13 Sep 2017 16:18:43 -0400 Subject: [PATCH 1/3] Adds ability to track sent/failed PerUTCOffset in the PushWorker - for scheduled push notifications at a certain time, it helps keep track of the state --- spec/PushWorker.spec.js | 134 +++++++++++++++++++++++++++++++++++++++- src/Push/PushWorker.js | 8 +-- src/StatusHandler.js | 6 +- 3 files changed, 142 insertions(+), 6 deletions(-) diff --git a/spec/PushWorker.spec.js b/spec/PushWorker.spec.js index 85ce31a30a..100869bef4 100644 --- a/spec/PushWorker.spec.js +++ b/spec/PushWorker.spec.js @@ -228,7 +228,7 @@ describe('PushWorker', () => { }, response: { error: 'invalid error...' } } - ], true); + ], undefined, true); expect(spy).toHaveBeenCalled(); expect(spy.calls.count()).toBe(1); const lastCall = spy.calls.mostRecent(); @@ -241,5 +241,137 @@ describe('PushWorker', () => { }); done(); }); + + it('tracks push status per UTC offsets', (done) => { + const config = new Config('test'); + const handler = pushStatusHandler(config, 'ABCDEF1234'); + const spy = spyOn(config.database, "update").and.callThrough(); + const UTCOffset = 1; + handler.setInitial().then(() => { + return handler.trackSent([ + { + transmitted: false, + device: { + deviceToken: 1, + deviceType: 'ios', + }, + }, + { + transmitted: true, + device: { + deviceToken: 1, + deviceType: 'ios', + } + }, + ], UTCOffset) + }).then(() => { + expect(spy).toHaveBeenCalled(); + expect(spy.calls.count()).toBe(1); + const lastCall = spy.calls.mostRecent(); + expect(lastCall.args[0]).toBe('_PushStatus'); + const updatePayload = lastCall.args[2]; + expect(updatePayload.updatedAt instanceof Date).toBeTruthy(); + // remove the updatedAt as not testable + delete updatePayload.updatedAt; + + expect(lastCall.args[2]).toEqual({ + numSent: { __op: 'Increment', amount: 1 }, + numFailed: { __op: 'Increment', amount: 1 }, + 'sentPerType.ios': { __op: 'Increment', amount: 1 }, + 'failedPerType.ios': { __op: 'Increment', amount: 1 }, + [`sentPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 }, + [`failedPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 }, + count: { __op: 'Increment', amount: -2 }, + }); + const query = new Parse.Query('_PushStatus'); + return query.get('ABCDEF1234', { useMasterKey: true }); + }).then((pushStatus) => { + const sentPerUTCOffset = pushStatus.get('sentPerUTCOffset'); + expect(sentPerUTCOffset['1']).toBe(1); + const failedPerUTCOffset = pushStatus.get('failedPerUTCOffset'); + expect(failedPerUTCOffset['1']).toBe(1); + return handler.trackSent([ + { + transmitted: false, + device: { + deviceToken: 1, + deviceType: 'ios', + }, + }, + { + transmitted: true, + device: { + deviceToken: 1, + deviceType: 'ios', + } + }, + { + transmitted: true, + device: { + deviceToken: 1, + deviceType: 'ios', + } + }, + ], UTCOffset) + }).then(() => { + const query = new Parse.Query('_PushStatus'); + return query.get('ABCDEF1234', { useMasterKey: true }); + }).then((pushStatus) => { + const sentPerUTCOffset = pushStatus.get('sentPerUTCOffset'); + expect(sentPerUTCOffset['1']).toBe(3); + const failedPerUTCOffset = pushStatus.get('failedPerUTCOffset'); + expect(failedPerUTCOffset['1']).toBe(2); + }).then(done).catch(done.fail); + }); + + it('tracks push status per UTC offsets with negative offsets', (done) => { + const config = new Config('test'); + const handler = pushStatusHandler(config); + spyOn(config.database, "create").and.callFake(() => { + return Promise.resolve(); + }); + const spy = spyOn(config.database, "update").and.callFake(() => { + return Promise.resolve(); + }); + const UTCOffset = -6; + handler.trackSent([ + { + transmitted: false, + device: { + deviceToken: 1, + deviceType: 'ios', + }, + response: { error: 'Unregistered' } + }, + { + transmitted: true, + device: { + deviceToken: 1, + deviceType: 'ios', + }, + response: { error: 'Unregistered' } + }, + ], UTCOffset).then(() => { + expect(spy).toHaveBeenCalled(); + expect(spy.calls.count()).toBe(1); + const lastCall = spy.calls.mostRecent(); + expect(lastCall.args[0]).toBe('_PushStatus'); + const updatePayload = lastCall.args[2]; + expect(updatePayload.updatedAt instanceof Date).toBeTruthy(); + // remove the updatedAt as not testable + delete updatePayload.updatedAt; + + expect(lastCall.args[2]).toEqual({ + numSent: { __op: 'Increment', amount: 1 }, + numFailed: { __op: 'Increment', amount: 1 }, + 'sentPerType.ios': { __op: 'Increment', amount: 1 }, + 'failedPerType.ios': { __op: 'Increment', amount: 1 }, + [`sentPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 }, + [`failedPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 }, + count: { __op: 'Increment', amount: -2 }, + }); + done(); + }); + }); }); }); diff --git a/src/Push/PushWorker.js b/src/Push/PushWorker.js index ffcd924e6a..4a29d6f14d 100644 --- a/src/Push/PushWorker.js +++ b/src/Push/PushWorker.js @@ -47,7 +47,7 @@ export class PushWorker { } } - run({ body, query, pushStatus, applicationId }: any): Promise<*> { + run({ body, query, pushStatus, applicationId, UTCOffset }: any): Promise<*> { const config = new Config(applicationId); const auth = master(config); const where = utils.applyDeviceTokenExists(query.where); @@ -59,6 +59,8 @@ export class PushWorker { return this.sendToAdapter(body, results, pushStatus, config); }, err => { throw err; + }).then((results) => { + return pushStatus.trackSent(results, UTCOffset); }); } @@ -82,9 +84,7 @@ export class PushWorker { if (!utils.isPushIncrementing(body)) { logger.verbose(`Sending push to ${installations.length}`); - return this.adapter.send(body, installations, pushStatus.objectId).then((results) => { - return pushStatus.trackSent(results); - }); + return this.adapter.send(body, installations, pushStatus.objectId); } // Collect the badges to reduce the # of calls diff --git a/src/StatusHandler.js b/src/StatusHandler.js index 3c252eb602..818d76fddc 100644 --- a/src/StatusHandler.js +++ b/src/StatusHandler.js @@ -162,7 +162,7 @@ export function pushStatusHandler(config, objectId = newObjectId(config.objectId {status: "running", updatedAt: new Date(), count }); } - const trackSent = function(results, cleanupInstallations = process.env.PARSE_SERVER_CLEANUP_INVALID_INSTALLATIONS) { + const trackSent = function(results, UTCOffset, cleanupInstallations = process.env.PARSE_SERVER_CLEANUP_INVALID_INSTALLATIONS) { const update = { updatedAt: new Date(), numSent: 0, @@ -179,6 +179,10 @@ export function pushStatusHandler(config, objectId = newObjectId(config.objectId const deviceType = result.device.deviceType; const key = result.transmitted ? `sentPerType.${deviceType}` : `failedPerType.${deviceType}`; memo[key] = incrementOp(memo, key); + if (typeof UTCOffset !== 'undefined') { + const offsetKey = result.transmitted ? `sentPerUTCOffset.${UTCOffset}` : `failedPerUTCOffset.${UTCOffset}`; + memo[offsetKey] = incrementOp(memo, offsetKey); + } if (result.transmitted) { memo.numSent++; } else { From 30590865f23da24f489b8b78be2460c9376c288c Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Wed, 13 Sep 2017 16:44:49 -0400 Subject: [PATCH 2/3] Makes sure we track it all correctly --- src/Push/PushWorker.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Push/PushWorker.js b/src/Push/PushWorker.js index 4a29d6f14d..deedaaed4f 100644 --- a/src/Push/PushWorker.js +++ b/src/Push/PushWorker.js @@ -56,15 +56,13 @@ export class PushWorker { if (results.length == 0) { return; } - return this.sendToAdapter(body, results, pushStatus, config); + return this.sendToAdapter(body, results, pushStatus, config, UTCOffset); }, err => { throw err; - }).then((results) => { - return pushStatus.trackSent(results, UTCOffset); }); } - sendToAdapter(body: any, installations: any[], pushStatus: any, config: Config): Promise<*> { + sendToAdapter(body: any, installations: any[], pushStatus: any, config: Config, UTCOffset: ?any): Promise<*> { pushStatus = pushStatusHandler(config, pushStatus.objectId); // Check if we have locales in the push body const locales = utils.getLocalesFromPush(body); @@ -77,14 +75,16 @@ export class PushWorker { const promises = Object.keys(grouppedInstallations).map((locale) => { const installations = grouppedInstallations[locale]; const body = bodiesPerLocales[locale]; - return this.sendToAdapter(body, installations, pushStatus, config); + return this.sendToAdapter(body, installations, pushStatus, config, UTCOffset); }); return Promise.all(promises); } if (!utils.isPushIncrementing(body)) { logger.verbose(`Sending push to ${installations.length}`); - return this.adapter.send(body, installations, pushStatus.objectId); + return this.adapter.send(body, installations, pushStatus.objectId).then((results) => { + return pushStatus.trackSent(results, UTCOffset).then(() => results); + }); } // Collect the badges to reduce the # of calls @@ -95,7 +95,7 @@ export class PushWorker { const payload = deepcopy(body); payload.data.badge = parseInt(badge); const installations = badgeInstallationsMap[badge]; - return this.sendToAdapter(payload, installations, pushStatus, config); + return this.sendToAdapter(payload, installations, pushStatus, config, UTCOffset); }); return Promise.all(promises); } From ed39d55ad85563e63663b0ffdb34c6501aeb359f Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Wed, 13 Sep 2017 17:03:18 -0400 Subject: [PATCH 3/3] Adds to Postgres --- src/Controllers/SchemaController.js | 30 +++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index e61f30a242..507e155d07 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -72,20 +72,22 @@ const defaultColumns = Object.freeze({ "subtitle": {type:'String'}, }, _PushStatus: { - "pushTime": {type:'String'}, - "source": {type:'String'}, // rest or webui - "query": {type:'String'}, // the stringified JSON query - "payload": {type:'String'}, // the stringified JSON payload, - "title": {type:'String'}, - "expiry": {type:'Number'}, - "status": {type:'String'}, - "numSent": {type:'Number'}, - "numFailed": {type:'Number'}, - "pushHash": {type:'String'}, - "errorMessage": {type:'Object'}, - "sentPerType": {type:'Object'}, - "failedPerType":{type:'Object'}, - "count": {type:'Number'} + "pushTime": {type:'String'}, + "source": {type:'String'}, // rest or webui + "query": {type:'String'}, // the stringified JSON query + "payload": {type:'String'}, // the stringified JSON payload, + "title": {type:'String'}, + "expiry": {type:'Number'}, + "status": {type:'String'}, + "numSent": {type:'Number'}, + "numFailed": {type:'Number'}, + "pushHash": {type:'String'}, + "errorMessage": {type:'Object'}, + "sentPerType": {type:'Object'}, + "failedPerType": {type:'Object'}, + "sentPerUTCOffset": {type:'Object'}, + "failedPerUTCOffset": {type:'Object'}, + "count": {type:'Number'} }, _JobStatus: { "jobName": {type: 'String'},