Skip to content

Commit 4dce3bd

Browse files
aontasflovilmart
authored andcommitted
Add support for more audience fields. (#4145)
* Add support for more audience fields. * Only update audience when defined audience_id.
1 parent 9fbb5e2 commit 4dce3bd

File tree

6 files changed

+160
-17
lines changed

6 files changed

+160
-17
lines changed

spec/AudienceRouter.spec.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ describe('AudiencesRouter', () => {
285285
);
286286
});
287287

288-
it_exclude_dbs(['postgres'])('should not log error with legacy parse.com times_used and _last_used fields', (done) => {
288+
it_exclude_dbs(['postgres'])('should support legacy parse.com audience fields', (done) => {
289289
const database = (new Config(Parse.applicationId)).database.adapter.database;
290290
const now = new Date();
291291
Parse._request('POST', 'push_audiences', { name: 'My Audience', query: JSON.stringify({ deviceType: 'ios' })}, { useMasterKey: true })
@@ -306,18 +306,32 @@ describe('AudiencesRouter', () => {
306306
expect(error).toEqual(null)
307307
expect(rows[0]['times_used']).toEqual(1);
308308
expect(rows[0]['_last_used']).toEqual(now);
309-
Parse._request('GET', 'push_audiences', {}, {useMasterKey: true})
310-
.then((results) => {
311-
expect(results.results.length).toEqual(1);
312-
expect(results.results[0].name).toEqual('My Audience');
313-
expect(results.results[0].query.deviceType).toEqual('ios');
314-
expect(results.results[0].times_used).toEqual(undefined);
315-
expect(results.results[0]._last_used).toEqual(undefined);
309+
Parse._request('GET', 'push_audiences/' + audience.objectId, {}, {useMasterKey: true})
310+
.then((audience) => {
311+
expect(audience.name).toEqual('My Audience');
312+
expect(audience.query.deviceType).toEqual('ios');
313+
expect(audience.timesUsed).toEqual(1);
314+
expect(audience.lastUsed).toEqual(now.toISOString());
316315
done();
317316
})
318317
.catch((error) => { done.fail(error); })
319318
});
320319
});
321320
});
322321
});
322+
323+
it('should be able to search on audiences', (done) => {
324+
Parse._request('POST', 'push_audiences', { name: 'neverUsed', query: JSON.stringify({ deviceType: 'ios' })}, { useMasterKey: true })
325+
.then(() => {
326+
const query = {"timesUsed": {"$exists": false}, "lastUsed": {"$exists": false}};
327+
Parse._request('GET', 'push_audiences?order=-createdAt&limit=1', {where: query}, {useMasterKey: true})
328+
.then((results) => {
329+
expect(results.results.length).toEqual(1);
330+
const audience = results.results[0];
331+
expect(audience.name).toEqual("neverUsed");
332+
done();
333+
})
334+
.catch((error) => { done.fail(error); })
335+
})
336+
});
323337
});

spec/PushController.spec.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,95 @@ describe('PushController', () => {
10001000
}).catch(done.fail);
10011001
});
10021002

1003+
it('should update audiences', (done) => {
1004+
var pushAdapter = {
1005+
send: function(body, installations) {
1006+
return successfulTransmissions(body, installations);
1007+
},
1008+
getValidPushTypes: function() {
1009+
return ["ios"];
1010+
}
1011+
}
1012+
1013+
var config = new Config(Parse.applicationId);
1014+
var auth = {
1015+
isMaster: true
1016+
}
1017+
1018+
var audienceId = null;
1019+
var now = new Date();
1020+
var timesUsed = 0;
1021+
1022+
const where = {
1023+
'deviceType': 'ios'
1024+
}
1025+
spyOn(pushAdapter, 'send').and.callThrough();
1026+
var pushController = new PushController();
1027+
reconfigureServer({
1028+
push: { adapter: pushAdapter }
1029+
}).then(() => {
1030+
var installations = [];
1031+
while (installations.length != 5) {
1032+
const installation = new Parse.Object("_Installation");
1033+
installation.set("installationId", "installation_" + installations.length);
1034+
installation.set("deviceToken","device_token_" + installations.length)
1035+
installation.set("badge", installations.length);
1036+
installation.set("originalBadge", installations.length);
1037+
installation.set("deviceType", "ios");
1038+
installations.push(installation);
1039+
}
1040+
return Parse.Object.saveAll(installations);
1041+
}).then(() => {
1042+
// Create an audience
1043+
const query = new Parse.Query("_Audience");
1044+
query.descending("createdAt");
1045+
query.equalTo("query", JSON.stringify(where));
1046+
const parseResults = (results) => {
1047+
if (results.length > 0) {
1048+
audienceId = results[0].id;
1049+
timesUsed = results[0].get('timesUsed');
1050+
if (!isFinite(timesUsed)) {
1051+
timesUsed = 0;
1052+
}
1053+
}
1054+
}
1055+
const audience = new Parse.Object("_Audience");
1056+
audience.set("name", "testAudience")
1057+
audience.set("query", JSON.stringify(where));
1058+
return Parse.Object.saveAll(audience).then(() => {
1059+
return query.find({ useMasterKey: true }).then(parseResults);
1060+
});
1061+
}).then(() => {
1062+
var body = {
1063+
data: { alert: 'hello' },
1064+
audience_id: audienceId
1065+
}
1066+
return pushController.sendPush(body, where, config, auth)
1067+
}).then(() => {
1068+
// Wait so the push is completed.
1069+
return new Promise((resolve) => { setTimeout(() => { resolve(); }, 1000); });
1070+
}).then(() => {
1071+
expect(pushAdapter.send.calls.count()).toBe(1);
1072+
const firstCall = pushAdapter.send.calls.first();
1073+
expect(firstCall.args[0].data).toEqual({
1074+
alert: 'hello'
1075+
});
1076+
expect(firstCall.args[1].length).toBe(5);
1077+
}).then(() => {
1078+
// Get the audience we used above.
1079+
const query = new Parse.Query("_Audience");
1080+
query.equalTo("objectId", audienceId);
1081+
return query.find({ useMasterKey: true })
1082+
}).then((results) => {
1083+
const audience = results[0];
1084+
expect(audience.get('query')).toBe(JSON.stringify(where));
1085+
expect(audience.get('timesUsed')).toBe(timesUsed + 1);
1086+
expect(audience.get('lastUsed')).not.toBeLessThan(now);
1087+
}).then(() => {
1088+
done();
1089+
}).catch(done.fail);
1090+
});
1091+
10031092
describe('pushTimeHasTimezoneComponent', () => {
10041093
it('should be accurate', () => {
10051094
expect(PushController.pushTimeHasTimezoneComponent('2017-09-06T17:14:01.048Z'))

spec/helper.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ afterEach(function(done) {
212212
} else {
213213
// Other system classes will break Parse.com, so make sure that we don't save anything to _SCHEMA that will
214214
// break it.
215-
return ['_User', '_Installation', '_Role', '_Session', '_Product'].indexOf(className) >= 0;
215+
return ['_User', '_Installation', '_Role', '_Session', '_Product', '_Audience'].indexOf(className) >= 0;
216216
}
217217
}});
218218
});

src/Adapters/Storage/Mongo/MongoTransform.js

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ const transformKey = (className, fieldName, schema) => {
1010
case 'createdAt': return '_created_at';
1111
case 'updatedAt': return '_updated_at';
1212
case 'sessionToken': return '_session_token';
13+
case 'lastUsed': return '_last_used';
14+
case 'timesUsed': return 'times_used';
1315
}
1416

1517
if (schema.fields[fieldName] && schema.fields[fieldName].__type == 'Pointer') {
@@ -77,6 +79,16 @@ const transformKeyValueForUpdate = (className, restKey, restValue, parseFormatSc
7779
case '_rperm':
7880
case '_wperm':
7981
return {key: key, value: restValue};
82+
case 'lastUsed':
83+
case '_last_used':
84+
key = '_last_used';
85+
timeField = true;
86+
break;
87+
case 'timesUsed':
88+
case 'times_used':
89+
key = 'times_used';
90+
timeField = true;
91+
break;
8092
}
8193

8294
if ((parseFormatSchema.fields[key] && parseFormatSchema.fields[key].type === 'Pointer') || (!parseFormatSchema.fields[key] && restValue && restValue.__type == 'Pointer')) {
@@ -200,6 +212,14 @@ function transformQueryKeyValue(className, key, value, schema) {
200212
return {key: '$or', value: value.map(subQuery => transformWhere(className, subQuery, schema))};
201213
case '$and':
202214
return {key: '$and', value: value.map(subQuery => transformWhere(className, subQuery, schema))};
215+
case 'lastUsed':
216+
if (valueAsDate(value)) {
217+
return {key: '_last_used', value: valueAsDate(value)}
218+
}
219+
key = '_last_used';
220+
break;
221+
case 'timesUsed':
222+
return {key: 'times_used', value: value};
203223
default: {
204224
// Other auth data
205225
const authDataMatch = key.match(/^authData\.([a-zA-Z0-9_]+)\.id$/);
@@ -923,11 +943,15 @@ const mongoObjectToParseObject = (className, mongoObject, schema) => {
923943
case '_expiresAt':
924944
restObject['expiresAt'] = Parse._encode(new Date(mongoObject[key]));
925945
break;
946+
case 'lastUsed':
947+
case '_last_used':
948+
restObject['lastUsed'] = Parse._encode(new Date(mongoObject[key])).iso;
949+
break;
950+
case 'timesUsed':
951+
case 'times_used':
952+
restObject['timesUsed'] = mongoObject[key];
953+
break;
926954
default:
927-
if (className === '_Audience' && (key === '_last_used' || key === 'times_used')) {
928-
// Ignore these parse.com legacy fields
929-
break;
930-
}
931955
// Check other auth data keys
932956
var authDataMatch = key.match(/^_auth_data_([a-zA-Z0-9_]+)$/);
933957
if (authDataMatch) {

src/Controllers/PushController.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,20 @@ export class PushController {
5454
}).then(() => {
5555
onPushStatusSaved(pushStatus.objectId);
5656
return badgeUpdate();
57+
}).then(() => {
58+
// Update audience lastUsed and timesUsed
59+
if (body.audience_id) {
60+
const audienceId = body.audience_id;
61+
62+
var updateAudience = {
63+
lastUsed: { __type: "Date", iso: new Date().toISOString() },
64+
timesUsed: { __op: "Increment", "amount": 1 }
65+
};
66+
const write = new RestWrite(config, master(config), '_Audience', {objectId: audienceId}, updateAudience);
67+
write.execute();
68+
}
69+
// Don't wait for the audience update promise to resolve.
70+
return Promise.resolve();
5771
}).then(() => {
5872
if (body.hasOwnProperty('push_time') && config.hasPushScheduledSupport) {
5973
return Promise.resolve();

src/Controllers/SchemaController.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,14 @@ const defaultColumns = Object.freeze({
113113
},
114114
_GlobalConfig: {
115115
"objectId": {type: 'String'},
116-
"params": {type: 'Object'}
116+
"params": {type: 'Object'}
117117
},
118118
_Audience: {
119-
"objectId": {type:'String'},
120-
"name": {type:'String'},
121-
"query": {type:'String'} //storing query as JSON string to prevent "Nested keys should not contain the '$' or '.' characters" error
119+
"objectId": {type:'String'},
120+
"name": {type:'String'},
121+
"query": {type:'String'}, //storing query as JSON string to prevent "Nested keys should not contain the '$' or '.' characters" error
122+
"lastUsed": {type:'Date'},
123+
"timesUsed": {type:'Number'}
122124
}
123125
});
124126

0 commit comments

Comments
 (0)