Skip to content

Commit f40dd20

Browse files
committed
Adds reporting of _JobStatus
1 parent e493ce9 commit f40dd20

File tree

6 files changed

+230
-51
lines changed

6 files changed

+230
-51
lines changed

spec/CloudCode.spec.js

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,15 +1051,17 @@ it('beforeSave should not affect fetched pointers', done => {
10511051
});
10521052
});
10531053

1054-
it('should not run without master key', (done) => {
1054+
it('should run with master key', (done) => {
10551055
expect(() => {
10561056
Parse.Cloud.job('myJob', (req, res) => {
10571057
expect(req.functionName).toBeUndefined();
10581058
expect(req.jobName).toBe('myJob');
1059+
expect(typeof req.jobId).toBe('string');
10591060
expect(typeof res.success).toBe('function');
10601061
expect(typeof res.error).toBe('function');
10611062
expect(typeof res.message).toBe('function');
10621063
res.success();
1064+
done();
10631065
});
10641066
}).not.toThrow();
10651067

@@ -1069,12 +1071,77 @@ it('beforeSave should not affect fetched pointers', done => {
10691071
'X-Parse-Application-Id': Parse.applicationId,
10701072
'X-Parse-Master-Key': Parse.masterKey,
10711073
},
1072-
}).then((result) => {
1074+
}).then((response) => {
1075+
}, (err) =>  {
1076+
fail(err);
10731077
done();
1078+
});
1079+
});
1080+
1081+
it('should set the message / success on the job', (done) => {
1082+
Parse.Cloud.job('myJob', (req, res) => {
1083+
res.message('hello').then(() => {
1084+
return getJobStatus(req.jobId);
1085+
}).then((jobStatus) => {
1086+
expect(jobStatus.get('message')).toEqual('hello');
1087+
expect(jobStatus.get('status')).toEqual('running');
1088+
return res.success().then(() => {
1089+
return getJobStatus(req.jobId);
1090+
});
1091+
}).then((jobStatus) => {
1092+
expect(typeof jobStatus.get('message')).not.toBe('string');
1093+
expect(jobStatus.get('status')).toEqual('succeeded');
1094+
done();
1095+
}).catch(err => {
1096+
console.error(err);
1097+
jfail(err);
1098+
done();
1099+
});
1100+
});
1101+
1102+
rp.post({
1103+
url: 'http://localhost:8378/1/jobs/myJob',
1104+
headers: {
1105+
'X-Parse-Application-Id': Parse.applicationId,
1106+
'X-Parse-Master-Key': Parse.masterKey,
1107+
},
1108+
}).then((response) => {
10741109
}, (err) =>  {
10751110
fail(err);
10761111
done();
10771112
});
10781113
});
1114+
1115+
it('should set the failure on the job', (done) => {
1116+
Parse.Cloud.job('myJob', (req, res) => {
1117+
res.error('Something went wrong').then(() => {
1118+
return getJobStatus(req.jobId);
1119+
}).then((jobStatus) => {
1120+
expect(jobStatus.get('message')).toEqual('Something went wrong');
1121+
expect(jobStatus.get('status')).toEqual('failed');
1122+
done();
1123+
}).catch(err => {
1124+
jfail(err);
1125+
done();
1126+
});
1127+
});
1128+
1129+
rp.post({
1130+
url: 'http://localhost:8378/1/jobs/myJob',
1131+
headers: {
1132+
'X-Parse-Application-Id': Parse.applicationId,
1133+
'X-Parse-Master-Key': Parse.masterKey,
1134+
},
1135+
}).then((response) => {
1136+
}, (err) =>  {
1137+
fail(err);
1138+
done();
1139+
});
1140+
});
1141+
1142+
function getJobStatus(jobId) {
1143+
let q = new Parse.Query('_JobStatus');
1144+
return q.get(jobId, {useMasterKey: true});
1145+
}
10791146
});
10801147
});

src/Adapters/Storage/Postgres/PostgresStorageAdapter.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ export class PostgresStorageAdapter {
534534
let joins = results.reduce((list, schema) => {
535535
return list.concat(joinTablesForSchema(schema.schema));
536536
}, []);
537-
const classes = ['_SCHEMA','_PushStatus','_Hooks','_GlobalConfig', ...results.map(result => result.className), ...joins];
537+
const classes = ['_SCHEMA','_PushStatus','_JobStatus','_Hooks','_GlobalConfig', ...results.map(result => result.className), ...joins];
538538
return this._client.tx(t=>t.batch(classes.map(className=>t.none('DROP TABLE IF EXISTS $<className:name>', { className }))));
539539
}, error => {
540540
if (error.code === PostgresRelationDoesNotExistError) {
@@ -783,7 +783,11 @@ export class PostgresStorageAdapter {
783783

784784
for (let fieldName in update) {
785785
let fieldValue = update[fieldName];
786-
if (fieldName == 'authData') {
786+
if (fieldValue === null) {
787+
updatePatterns.push(`$${index}:name = NULL`);
788+
values.push(fieldName);
789+
index += 1;
790+
} else if (fieldName == 'authData') {
787791
// This recursively sets the json_object
788792
// Only 1 level deep
789793
let generate = (jsonb, key, value) => {
@@ -848,6 +852,10 @@ export class PostgresStorageAdapter {
848852
updatePatterns.push(`$${index}:name = $${index + 1}`);
849853
values.push(fieldName, toPostgresValue(fieldValue));
850854
index += 2;
855+
} else if (fieldValue instanceof Date) {
856+
updatePatterns.push(`$${index}:name = $${index + 1}`);
857+
values.push(fieldName, fieldValue);
858+
index += 2;
851859
} else if (fieldValue.__type === 'File') {
852860
updatePatterns.push(`$${index}:name = $${index + 1}`);
853861
values.push(fieldName, toPostgresValue(fieldValue));

src/Controllers/PushController.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,9 @@ export class PushController extends AdaptableController {
9898
}).then((results) => {
9999
return pushStatus.complete(results);
100100
}).catch((err) => {
101-
pushStatus.fail(err);
102-
return Promise.reject(err);
101+
return pushStatus.fail(err).then(() => {
102+
throw err;
103+
});
103104
});
104105
}
105106

src/Controllers/SchemaController.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ const defaultColumns = Object.freeze({
8787
"sentPerType": {type:'Object'},
8888
"failedPerType":{type:'Object'},
8989
},
90+
_JobStatus: {
91+
"jobName": {type: 'String'},
92+
"source": {type: 'String'},
93+
"status": {type: 'String'},
94+
"message": {type: 'String'},
95+
"params": {type: 'Object'}, // params received when calling the job
96+
"finishedAt": {type: 'Date'}
97+
},
9098
_Hooks: {
9199
"functionName": {type:'String'},
92100
"className": {type:'String'},
@@ -104,9 +112,9 @@ const requiredColumns = Object.freeze({
104112
_Role: ["name", "ACL"]
105113
});
106114

107-
const systemClasses = Object.freeze(['_User', '_Installation', '_Role', '_Session', '_Product', '_PushStatus']);
115+
const systemClasses = Object.freeze(['_User', '_Installation', '_Role', '_Session', '_Product', '_PushStatus', '_JobStatus']);
108116

109-
const volatileClasses = Object.freeze(['_PushStatus', '_Hooks', '_GlobalConfig']);
117+
const volatileClasses = Object.freeze(['_JobStatus', '_PushStatus', '_Hooks', '_GlobalConfig']);
110118

111119
// 10 alpha numberic chars + uppercase
112120
const userIdRegex = /^[a-zA-Z0-9]{10}$/;
@@ -275,7 +283,12 @@ const _PushStatusSchema = convertSchemaToAdapterSchema(injectDefaultSchema({
275283
fields: {},
276284
classLevelPermissions: {}
277285
}));
278-
const VolatileClassesSchemas = [_HooksSchema, _PushStatusSchema, _GlobalConfigSchema];
286+
const _JobStatusSchema = convertSchemaToAdapterSchema(injectDefaultSchema({
287+
className: "_JobStatus",
288+
fields: {},
289+
classLevelPermissions: {}
290+
}));
291+
const VolatileClassesSchemas = [_HooksSchema, _JobStatusSchema, _PushStatusSchema, _GlobalConfigSchema];
279292

280293
const dbTypeMatchesObjectType = (dbType, objectType) => {
281294
if (dbType.type !== objectType.type) return false;

src/Routers/FunctionsRouter.js

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var express = require('express'),
66

77
import PromiseRouter from '../PromiseRouter';
88
import { promiseEnforceMasterKeyAccess } from '../middlewares';
9+
import { jobStatusHandler } from '../StatusHandler';
910
import _ from 'lodash';
1011
import { logger } from '../logger';
1112

@@ -33,8 +34,44 @@ export class FunctionsRouter extends PromiseRouter {
3334

3435
mountRoutes() {
3536
this.route('POST', '/functions/:functionName', FunctionsRouter.handleCloudFunction);
36-
this.route('POST', '/jobs/:functionName', promiseEnforceMasterKeyAccess, function(req) {
37-
return FunctionsRouter.handleCloudFunction(req, true);
37+
this.route('POST', '/jobs/:jobName', promiseEnforceMasterKeyAccess, function(req) {
38+
return FunctionsRouter.handleCloudJob(req);
39+
});
40+
}
41+
42+
static handleCloudJob(req) {
43+
const jobName = req.params.jobName;
44+
const applicationId = req.config.applicationId;
45+
const jobHandler = jobStatusHandler(req.config);
46+
const jobFunction = triggers.getJob(jobName, applicationId);
47+
if (!jobFunction) {
48+
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Invalid job.');
49+
}
50+
let params = Object.assign({}, req.body, req.query);
51+
params = parseParams(params);
52+
const request = {
53+
params: params,
54+
log: req.config.loggerController,
55+
headers: req.headers,
56+
jobName
57+
};
58+
const status = {
59+
success: jobHandler.setSucceeded.bind(jobHandler),
60+
error: jobHandler.setFailed.bind(jobHandler),
61+
message: jobHandler.setMessage.bind(jobHandler)
62+
}
63+
return jobHandler.setRunning(jobName, params).then((jobStatus) => {
64+
request.jobId = jobStatus.objectId
65+
// run the function async
66+
process.nextTick(() => {
67+
jobFunction(request, status);
68+
});
69+
return {
70+
headers: {
71+
'X-Parse-Job-Status-Id': jobStatus.objectId
72+
},
73+
response: {}
74+
}
3875
});
3976
}
4077

@@ -58,19 +95,12 @@ export class FunctionsRouter extends PromiseRouter {
5895
}
5996
}
6097

61-
static handleCloudFunction(req, isJob = false) {
62-
const applicationId = req.config.applicationId;
98+
static handleCloudFunction(req) {
6399
const functionName = req.params.functionName;
64-
const getter = isJob ? triggers.getJob : triggers.getFunction;
65-
const theFunction = getter(req.params.functionName, applicationId);
100+
const applicationId = req.config.applicationId;
101+
const theFunction = triggers.getFunction(functionName, applicationId);
66102
const theValidator = triggers.getValidator(req.params.functionName, applicationId);
67-
if (theFunction) {
68-
if (isJob) {
69-
req.connection.setTimeout(15*60*1000); // 15 minutes
70-
}
71-
const optionKey = isJob ? 'jobName' : 'functionName';
72-
const type = isJob ? 'cloud job' : 'cloud function';
73-
103+
if (theFunction) {
74104
let params = Object.assign({}, req.body, req.query);
75105
params = parseParams(params);
76106
var request = {
@@ -80,7 +110,7 @@ export class FunctionsRouter extends PromiseRouter {
80110
installationId: req.info.installationId,
81111
log: req.config.loggerController,
82112
headers: req.headers,
83-
[optionKey]: functionName
113+
functionName
84114
};
85115

86116
if (theValidator && typeof theValidator === "function") {
@@ -96,9 +126,9 @@ export class FunctionsRouter extends PromiseRouter {
96126
var response = FunctionsRouter.createResponseObject((result) => {
97127
try {
98128
const cleanResult = logger.truncateLogMessage(JSON.stringify(result.response.result));
99-
logger.info(`Ran ${type} ${functionName} for user ${userString} `
129+
logger.info(`Ran cloud function ${functionName} for user ${userString} `
100130
+ `with:\n Input: ${cleanInput }\n Result: ${cleanResult }`, {
101-
[optionKey]: functionName,
131+
functionName,
102132
params,
103133
user: userString,
104134
});
@@ -108,10 +138,10 @@ export class FunctionsRouter extends PromiseRouter {
108138
}
109139
}, (error) => {
110140
try {
111-
logger.error(`Failed running ${type} ${functionName} for `
141+
logger.error(`Failed running cloud function ${functionName} for `
112142
+ `user ${userString} with:\n Input: ${cleanInput}\n Error: `
113143
+ JSON.stringify(error), {
114-
[optionKey]: functionName,
144+
functionName,
115145
error,
116146
params,
117147
user: userString
@@ -120,7 +150,7 @@ export class FunctionsRouter extends PromiseRouter {
120150
} catch (e) {
121151
reject(e);
122152
}
123-
}, isJob ? logger.info : undefined);
153+
});
124154
// Force the keys before the function calls.
125155
Parse.applicationId = req.config.applicationId;
126156
Parse.javascriptKey = req.config.javascriptKey;

0 commit comments

Comments
 (0)