Skip to content

Commit cd057a5

Browse files
authored
Merge pull request #555 from dplewis/jobs-cloud
Add Jobs Functions to Cloud
2 parents 2364019 + c4eee61 commit cd057a5

File tree

10 files changed

+405
-25
lines changed

10 files changed

+405
-25
lines changed

integration/cloud/main.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Parse.Cloud.define("bar", function(request, response) {
2+
if (request.params.key2 === "value1") {
3+
response.success('Foo');
4+
} else {
5+
response.error("bad stuff happened");
6+
}
7+
});
8+
9+
Parse.Cloud.job('CloudJob1', function(request, response) {
10+
response.success({
11+
status: 'cloud job completed'
12+
});
13+
});
14+
15+
Parse.Cloud.job('CloudJob2', function(request, response) {
16+
setTimeout(function() {
17+
response.success({
18+
status: 'cloud job completed'
19+
})
20+
}, 3000);
21+
});
22+
23+
Parse.Cloud.job('CloudJobFailing', function(request, response) {
24+
response.error('cloud job failed');
25+
});

integration/server.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ var api = new ParseServer({
88
databaseURI: 'mongodb://localhost:27017/integration',
99
appId: 'integration',
1010
masterKey: 'notsosecret',
11-
serverURL: 'http://localhost:1337/parse' // Don't forget to change to https if needed
11+
serverURL: 'http://localhost:1337/parse', // Don't forget to change to https if needed
12+
cloud: `${__dirname}/cloud/main.js`
1213
});
1314

1415
// Serve the Parse API on the /parse URL prefix

integration/test/ParseCloudTest.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
const clear = require('./clear');
5+
const mocha = require('mocha');
6+
const Parse = require('../../node');
7+
8+
describe('Parse Cloud', () => {
9+
before((done) => {
10+
Parse.initialize('integration', null, 'notsosecret');
11+
Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse');
12+
Parse.Storage._clear();
13+
clear().then(() => { done() }, () => { done() });
14+
});
15+
16+
it('run function', (done) => {
17+
const params = { key1: 'value2', key2: 'value1' };
18+
Parse.Cloud.run('bar', params).then((result) => {
19+
assert.equal('Foo', result);
20+
done();
21+
});
22+
});
23+
24+
it('run function with user', (done) => {
25+
const params = { key1: 'value2', key2: 'value1' };
26+
const user = new Parse.User();
27+
user.setUsername('someuser');
28+
user.setPassword('somepassword');
29+
user.signUp().then(() => {
30+
return Parse.Cloud.run('bar', params);
31+
}).then((resp) => {
32+
assert.equal('Foo', resp);
33+
return user.destroy({ useMasterKey: true });
34+
}).then(() => {
35+
done();
36+
});
37+
});
38+
39+
it('run function failed', (done) => {
40+
const params = { key1: 'value1', key2: 'value2' };
41+
Parse.Cloud.run('bar', params).then(null).catch((error) => {
42+
assert.equal(error.code, Parse.Error.SCRIPT_FAILED);
43+
done();
44+
});
45+
});
46+
47+
it('run function name fail', (done) => {
48+
const params = { key1: 'value1' };
49+
Parse.Cloud.run('unknown_function', params).then(null).catch((error) => {
50+
assert.equal(error.message, 'Invalid function: "unknown_function"');
51+
done();
52+
});
53+
});
54+
55+
it('run function with geopoint params does not fail', (done) => {
56+
const params = { key1: new Parse.GeoPoint(50, 50) };
57+
Parse.Cloud.run('unknown_function', params).then(null).catch((error) => {
58+
assert.equal(error.message, 'Invalid function: "unknown_function"');
59+
done();
60+
});
61+
});
62+
63+
it('run function with object params fail', (done) => {
64+
const object = new Parse.Object('TestClass');
65+
const params = { key1: object };
66+
try {
67+
Parse.Cloud.run('bar', params);
68+
} catch (e) {
69+
assert.equal(e, 'Error: Parse Objects not allowed here');
70+
done();
71+
}
72+
});
73+
74+
it('run job', (done) => {
75+
const params = { startedBy: 'Monty Python' };
76+
Parse.Cloud.startJob('CloudJob1', params).then((jobStatusId) => {
77+
return Parse.Cloud.getJobStatus(jobStatusId);
78+
}).then((jobStatus) => {
79+
assert.equal(jobStatus.get('status'), 'succeeded');
80+
assert.equal(jobStatus.get('params').startedBy, 'Monty Python');
81+
done();
82+
});
83+
});
84+
85+
it('run long job', (done) => {
86+
Parse.Cloud.startJob('CloudJob2').then((jobStatusId) => {
87+
return Parse.Cloud.getJobStatus(jobStatusId);
88+
}).then((jobStatus) => {
89+
assert.equal(jobStatus.get('status'), 'running');
90+
done();
91+
});
92+
});
93+
94+
it('run bad job', (done) => {
95+
Parse.Cloud.startJob('bad_job').then(null).catch((error) => {
96+
assert.equal(error.code, Parse.Error.SCRIPT_FAILED);
97+
assert.equal(error.message, 'Invalid job.');
98+
done();
99+
});
100+
});
101+
102+
it('run failing job', (done) => {
103+
Parse.Cloud.startJob('CloudJobFailing').then((jobStatusId) => {
104+
return Parse.Cloud.getJobStatus(jobStatusId);
105+
}).then((jobStatus) => {
106+
assert.equal(jobStatus.get('status'), 'failed');
107+
assert.equal(jobStatus.get('message'), 'cloud job failed');
108+
done();
109+
});
110+
});
111+
112+
it('get jobs data', (done) => {
113+
Parse.Cloud.getJobsData().then((result) => {
114+
assert.equal(result.in_use.length, 0);
115+
assert.equal(result.jobs.length, 3);
116+
done();
117+
});
118+
});
119+
120+
it('invalid job status id', (done) => {
121+
Parse.Cloud.getJobStatus('not-a-real-id').then(null).catch((error) => {
122+
assert.equal(error.message, 'Object not found.');
123+
done();
124+
});
125+
});
126+
});

integration/test/ParseQueryAggregateTest.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ describe('Parse Aggregate Query', () => {
6363
assert.equal(results[0], 10);
6464
assert.equal(results[1], 20);
6565
done();
66-
}).catch(done.fail);
66+
});
6767
});
6868

6969
it('distinct equalTo query', (done) => {

src/Cloud.js

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import decode from './decode';
1414
import encode from './encode';
1515
import ParseError from './ParseError';
1616
import ParsePromise from './ParsePromise';
17+
import ParseQuery from './ParseQuery';
1718

1819
/**
1920
* Contains functions for calling and declaring
@@ -60,9 +61,69 @@ export function run(
6061
requestOptions.sessionToken = options.sessionToken;
6162
}
6263

63-
return (
64-
CoreManager.getCloudController().run(name, data, requestOptions)._thenRunCallbacks(options)
65-
);
64+
return CoreManager.getCloudController().run(name, data, requestOptions)._thenRunCallbacks(options);
65+
}
66+
67+
/**
68+
* Gets data for the current set of cloud jobs.
69+
* @method getJobsData
70+
* @name Parse.Cloud.getJobsData
71+
* @param {Object} options A Backbone-style options object
72+
* options.success, if set, should be a function to handle a successful
73+
* call to a cloud function. options.error should be a function that
74+
* handles an error running the cloud function. Both functions are
75+
* optional. Both functions take a single argument.
76+
* @return {Parse.Promise} A promise that will be resolved with the result
77+
* of the function.
78+
*/
79+
export function getJobsData(options: { [key: string]: mixed }): ParsePromise {
80+
options = options || {};
81+
const requestOptions = {
82+
useMasterKey: true
83+
};
84+
return CoreManager.getCloudController().getJobsData(requestOptions)._thenRunCallbacks(options);
85+
}
86+
87+
/**
88+
* Starts a given cloud job, which will process asynchronously.
89+
* @method startJob
90+
* @name Parse.Cloud.startJob
91+
* @param {String} name The function name.
92+
* @param {Object} data The parameters to send to the cloud function.
93+
* @param {Object} options A Backbone-style options object
94+
* options.success, if set, should be a function to handle a successful
95+
* call to a cloud function. options.error should be a function that
96+
* handles an error running the cloud function. Both functions are
97+
* optional. Both functions take a single argument.
98+
* @return {Parse.Promise} A promise that will be resolved with the result
99+
* of the function.
100+
*/
101+
export function startJob(
102+
name: string,
103+
data: mixed,
104+
options: { [key: string]: mixed }
105+
): ParsePromise {
106+
options = options || {};
107+
108+
if (typeof name !== 'string' || name.length === 0) {
109+
throw new TypeError('Cloud job name must be a string.');
110+
}
111+
const requestOptions = {
112+
useMasterKey: true
113+
};
114+
return CoreManager.getCloudController().startJob(name, data, requestOptions)._thenRunCallbacks(options);
115+
}
116+
117+
/**
118+
* Gets job status by Id
119+
* @method getJobStatus
120+
* @name Parse.Cloud.getJobStatus
121+
* @param {String} jobStatusId The Id of Job Status.
122+
* @return {Parse.Object} Status of Job.
123+
*/
124+
export function getJobStatus(jobStatusId: string): ParsePromise {
125+
var query = new ParseQuery('_JobStatus');
126+
return query.get(jobStatusId, { useMasterKey: true });
66127
}
67128

68129
var DefaultController = {
@@ -71,19 +132,11 @@ var DefaultController = {
71132

72133
var payload = encode(data, true);
73134

74-
var requestOptions = {};
75-
if (options.hasOwnProperty('useMasterKey')) {
76-
requestOptions.useMasterKey = options.useMasterKey;
77-
}
78-
if (options.hasOwnProperty('sessionToken')) {
79-
requestOptions.sessionToken = options.sessionToken;
80-
}
81-
82135
var request = RESTController.request(
83136
'POST',
84137
'functions/' + name,
85138
payload,
86-
requestOptions
139+
options
87140
);
88141

89142
return request.then(function(res) {
@@ -97,7 +150,35 @@ var DefaultController = {
97150
'The server returned an invalid response.'
98151
)
99152
);
100-
})._thenRunCallbacks(options);
153+
});
154+
},
155+
156+
getJobsData(options) {
157+
var RESTController = CoreManager.getRESTController();
158+
159+
var request = RESTController.request(
160+
'GET',
161+
'cloud_code/jobs/data',
162+
null,
163+
options
164+
);
165+
166+
return request;
167+
},
168+
169+
startJob(name, data, options) {
170+
var RESTController = CoreManager.getRESTController();
171+
172+
var payload = encode(data, true);
173+
174+
var request = RESTController.request(
175+
'POST',
176+
'jobs/' + name,
177+
payload,
178+
options,
179+
);
180+
181+
return request;
101182
}
102183
};
103184

src/CoreManager.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ type AnalyticsController = {
3030
};
3131
type CloudController = {
3232
run: (name: string, data: mixed, options: { [key: string]: mixed }) => ParsePromise;
33+
getJobsData: (options: { [key: string]: mixed }) => ParsePromise;
34+
startJob: (name: string, data: mixed, options: { [key: string]: mixed }) => ParsePromise;
3335
};
3436
type ConfigController = {
3537
current: () => ParsePromise;
@@ -197,7 +199,7 @@ module.exports = {
197199
},
198200

199201
setCloudController(controller: CloudController) {
200-
requireMethods('CloudController', ['run'], controller);
202+
requireMethods('CloudController', ['run', 'getJobsData', 'startJob'], controller);
201203
config['CloudController'] = controller;
202204
},
203205

src/RESTController.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ const RESTController = {
100100
var response;
101101
try {
102102
response = JSON.parse(xhr.responseText);
103+
104+
if (typeof xhr.getResponseHeader === 'function') {
105+
var jobStatusId = xhr.getResponseHeader('x-parse-job-status-id');
106+
if (jobStatusId) {
107+
response = jobStatusId;
108+
}
109+
}
103110
} catch (e) {
104111
promise.reject(e.toString());
105112
}

0 commit comments

Comments
 (0)