Skip to content

Commit d598d73

Browse files
authored
Adds ability to track sent/failed PerUTCOffset in the PushWorker (#4158)
* 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 * Makes sure we track it all correctly * Adds to Postgres
1 parent be2760f commit d598d73

File tree

4 files changed

+160
-22
lines changed

4 files changed

+160
-22
lines changed

spec/PushWorker.spec.js

+133-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ describe('PushWorker', () => {
228228
},
229229
response: { error: 'invalid error...' }
230230
}
231-
], true);
231+
], undefined, true);
232232
expect(spy).toHaveBeenCalled();
233233
expect(spy.calls.count()).toBe(1);
234234
const lastCall = spy.calls.mostRecent();
@@ -241,5 +241,137 @@ describe('PushWorker', () => {
241241
});
242242
done();
243243
});
244+
245+
it('tracks push status per UTC offsets', (done) => {
246+
const config = new Config('test');
247+
const handler = pushStatusHandler(config, 'ABCDEF1234');
248+
const spy = spyOn(config.database, "update").and.callThrough();
249+
const UTCOffset = 1;
250+
handler.setInitial().then(() => {
251+
return handler.trackSent([
252+
{
253+
transmitted: false,
254+
device: {
255+
deviceToken: 1,
256+
deviceType: 'ios',
257+
},
258+
},
259+
{
260+
transmitted: true,
261+
device: {
262+
deviceToken: 1,
263+
deviceType: 'ios',
264+
}
265+
},
266+
], UTCOffset)
267+
}).then(() => {
268+
expect(spy).toHaveBeenCalled();
269+
expect(spy.calls.count()).toBe(1);
270+
const lastCall = spy.calls.mostRecent();
271+
expect(lastCall.args[0]).toBe('_PushStatus');
272+
const updatePayload = lastCall.args[2];
273+
expect(updatePayload.updatedAt instanceof Date).toBeTruthy();
274+
// remove the updatedAt as not testable
275+
delete updatePayload.updatedAt;
276+
277+
expect(lastCall.args[2]).toEqual({
278+
numSent: { __op: 'Increment', amount: 1 },
279+
numFailed: { __op: 'Increment', amount: 1 },
280+
'sentPerType.ios': { __op: 'Increment', amount: 1 },
281+
'failedPerType.ios': { __op: 'Increment', amount: 1 },
282+
[`sentPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 },
283+
[`failedPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 },
284+
count: { __op: 'Increment', amount: -2 },
285+
});
286+
const query = new Parse.Query('_PushStatus');
287+
return query.get('ABCDEF1234', { useMasterKey: true });
288+
}).then((pushStatus) => {
289+
const sentPerUTCOffset = pushStatus.get('sentPerUTCOffset');
290+
expect(sentPerUTCOffset['1']).toBe(1);
291+
const failedPerUTCOffset = pushStatus.get('failedPerUTCOffset');
292+
expect(failedPerUTCOffset['1']).toBe(1);
293+
return handler.trackSent([
294+
{
295+
transmitted: false,
296+
device: {
297+
deviceToken: 1,
298+
deviceType: 'ios',
299+
},
300+
},
301+
{
302+
transmitted: true,
303+
device: {
304+
deviceToken: 1,
305+
deviceType: 'ios',
306+
}
307+
},
308+
{
309+
transmitted: true,
310+
device: {
311+
deviceToken: 1,
312+
deviceType: 'ios',
313+
}
314+
},
315+
], UTCOffset)
316+
}).then(() => {
317+
const query = new Parse.Query('_PushStatus');
318+
return query.get('ABCDEF1234', { useMasterKey: true });
319+
}).then((pushStatus) => {
320+
const sentPerUTCOffset = pushStatus.get('sentPerUTCOffset');
321+
expect(sentPerUTCOffset['1']).toBe(3);
322+
const failedPerUTCOffset = pushStatus.get('failedPerUTCOffset');
323+
expect(failedPerUTCOffset['1']).toBe(2);
324+
}).then(done).catch(done.fail);
325+
});
326+
327+
it('tracks push status per UTC offsets with negative offsets', (done) => {
328+
const config = new Config('test');
329+
const handler = pushStatusHandler(config);
330+
spyOn(config.database, "create").and.callFake(() => {
331+
return Promise.resolve();
332+
});
333+
const spy = spyOn(config.database, "update").and.callFake(() => {
334+
return Promise.resolve();
335+
});
336+
const UTCOffset = -6;
337+
handler.trackSent([
338+
{
339+
transmitted: false,
340+
device: {
341+
deviceToken: 1,
342+
deviceType: 'ios',
343+
},
344+
response: { error: 'Unregistered' }
345+
},
346+
{
347+
transmitted: true,
348+
device: {
349+
deviceToken: 1,
350+
deviceType: 'ios',
351+
},
352+
response: { error: 'Unregistered' }
353+
},
354+
], UTCOffset).then(() => {
355+
expect(spy).toHaveBeenCalled();
356+
expect(spy.calls.count()).toBe(1);
357+
const lastCall = spy.calls.mostRecent();
358+
expect(lastCall.args[0]).toBe('_PushStatus');
359+
const updatePayload = lastCall.args[2];
360+
expect(updatePayload.updatedAt instanceof Date).toBeTruthy();
361+
// remove the updatedAt as not testable
362+
delete updatePayload.updatedAt;
363+
364+
expect(lastCall.args[2]).toEqual({
365+
numSent: { __op: 'Increment', amount: 1 },
366+
numFailed: { __op: 'Increment', amount: 1 },
367+
'sentPerType.ios': { __op: 'Increment', amount: 1 },
368+
'failedPerType.ios': { __op: 'Increment', amount: 1 },
369+
[`sentPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 },
370+
[`failedPerUTCOffset.${UTCOffset}`]: { __op: 'Increment', amount: 1 },
371+
count: { __op: 'Increment', amount: -2 },
372+
});
373+
done();
374+
});
375+
});
244376
});
245377
});

src/Controllers/SchemaController.js

+16-14
Original file line numberDiff line numberDiff line change
@@ -72,20 +72,22 @@ const defaultColumns = Object.freeze({
7272
"subtitle": {type:'String'},
7373
},
7474
_PushStatus: {
75-
"pushTime": {type:'String'},
76-
"source": {type:'String'}, // rest or webui
77-
"query": {type:'String'}, // the stringified JSON query
78-
"payload": {type:'String'}, // the stringified JSON payload,
79-
"title": {type:'String'},
80-
"expiry": {type:'Number'},
81-
"status": {type:'String'},
82-
"numSent": {type:'Number'},
83-
"numFailed": {type:'Number'},
84-
"pushHash": {type:'String'},
85-
"errorMessage": {type:'Object'},
86-
"sentPerType": {type:'Object'},
87-
"failedPerType":{type:'Object'},
88-
"count": {type:'Number'}
75+
"pushTime": {type:'String'},
76+
"source": {type:'String'}, // rest or webui
77+
"query": {type:'String'}, // the stringified JSON query
78+
"payload": {type:'String'}, // the stringified JSON payload,
79+
"title": {type:'String'},
80+
"expiry": {type:'Number'},
81+
"status": {type:'String'},
82+
"numSent": {type:'Number'},
83+
"numFailed": {type:'Number'},
84+
"pushHash": {type:'String'},
85+
"errorMessage": {type:'Object'},
86+
"sentPerType": {type:'Object'},
87+
"failedPerType": {type:'Object'},
88+
"sentPerUTCOffset": {type:'Object'},
89+
"failedPerUTCOffset": {type:'Object'},
90+
"count": {type:'Number'}
8991
},
9092
_JobStatus: {
9193
"jobName": {type: 'String'},

src/Push/PushWorker.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export class PushWorker {
4747
}
4848
}
4949

50-
run({ body, query, pushStatus, applicationId }: any): Promise<*> {
50+
run({ body, query, pushStatus, applicationId, UTCOffset }: any): Promise<*> {
5151
const config = new Config(applicationId);
5252
const auth = master(config);
5353
const where = utils.applyDeviceTokenExists(query.where);
@@ -56,13 +56,13 @@ export class PushWorker {
5656
if (results.length == 0) {
5757
return;
5858
}
59-
return this.sendToAdapter(body, results, pushStatus, config);
59+
return this.sendToAdapter(body, results, pushStatus, config, UTCOffset);
6060
}, err => {
6161
throw err;
6262
});
6363
}
6464

65-
sendToAdapter(body: any, installations: any[], pushStatus: any, config: Config): Promise<*> {
65+
sendToAdapter(body: any, installations: any[], pushStatus: any, config: Config, UTCOffset: ?any): Promise<*> {
6666
pushStatus = pushStatusHandler(config, pushStatus.objectId);
6767
// Check if we have locales in the push body
6868
const locales = utils.getLocalesFromPush(body);
@@ -75,15 +75,15 @@ export class PushWorker {
7575
const promises = Object.keys(grouppedInstallations).map((locale) => {
7676
const installations = grouppedInstallations[locale];
7777
const body = bodiesPerLocales[locale];
78-
return this.sendToAdapter(body, installations, pushStatus, config);
78+
return this.sendToAdapter(body, installations, pushStatus, config, UTCOffset);
7979
});
8080
return Promise.all(promises);
8181
}
8282

8383
if (!utils.isPushIncrementing(body)) {
8484
logger.verbose(`Sending push to ${installations.length}`);
8585
return this.adapter.send(body, installations, pushStatus.objectId).then((results) => {
86-
return pushStatus.trackSent(results);
86+
return pushStatus.trackSent(results, UTCOffset).then(() => results);
8787
});
8888
}
8989

@@ -95,7 +95,7 @@ export class PushWorker {
9595
const payload = deepcopy(body);
9696
payload.data.badge = parseInt(badge);
9797
const installations = badgeInstallationsMap[badge];
98-
return this.sendToAdapter(payload, installations, pushStatus, config);
98+
return this.sendToAdapter(payload, installations, pushStatus, config, UTCOffset);
9999
});
100100
return Promise.all(promises);
101101
}

src/StatusHandler.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ export function pushStatusHandler(config, objectId = newObjectId(config.objectId
162162
{status: "running", updatedAt: new Date(), count });
163163
}
164164

165-
const trackSent = function(results, cleanupInstallations = process.env.PARSE_SERVER_CLEANUP_INVALID_INSTALLATIONS) {
165+
const trackSent = function(results, UTCOffset, cleanupInstallations = process.env.PARSE_SERVER_CLEANUP_INVALID_INSTALLATIONS) {
166166
const update = {
167167
updatedAt: new Date(),
168168
numSent: 0,
@@ -179,6 +179,10 @@ export function pushStatusHandler(config, objectId = newObjectId(config.objectId
179179
const deviceType = result.device.deviceType;
180180
const key = result.transmitted ? `sentPerType.${deviceType}` : `failedPerType.${deviceType}`;
181181
memo[key] = incrementOp(memo, key);
182+
if (typeof UTCOffset !== 'undefined') {
183+
const offsetKey = result.transmitted ? `sentPerUTCOffset.${UTCOffset}` : `failedPerUTCOffset.${UTCOffset}`;
184+
memo[offsetKey] = incrementOp(memo, offsetKey);
185+
}
182186
if (result.transmitted) {
183187
memo.numSent++;
184188
} else {

0 commit comments

Comments
 (0)