Adds context object in Cloud Code hooks (#4939)
* wip * Refactors triggers a bit - Adds testing for hooks and context * comment nit * nits
This commit is contained in:
@@ -1809,4 +1809,46 @@ describe('afterFind hooks', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should expose context in before and afterSave', async () => {
|
||||
let calledBefore = false;
|
||||
let calledAfter = false;
|
||||
Parse.Cloud.beforeSave('MyClass', (req) => {
|
||||
req.context = {
|
||||
key: 'value',
|
||||
otherKey: 1,
|
||||
}
|
||||
calledBefore = true;
|
||||
});
|
||||
Parse.Cloud.afterSave('MyClass', (req) => {
|
||||
expect(req.context.otherKey).toBe(1);
|
||||
expect(req.context.key).toBe('value');
|
||||
calledAfter = true;
|
||||
});
|
||||
|
||||
const object = new Parse.Object('MyClass');
|
||||
await object.save();
|
||||
expect(calledBefore).toBe(true);
|
||||
expect(calledAfter).toBe(true);
|
||||
});
|
||||
|
||||
it('should expose context in before and afterSave and let keys be set individually', async () => {
|
||||
let calledBefore = false;
|
||||
let calledAfter = false;
|
||||
Parse.Cloud.beforeSave('MyClass', (req) => {
|
||||
req.context.some = 'value';
|
||||
req.context.yolo = 1;
|
||||
calledBefore = true;
|
||||
});
|
||||
Parse.Cloud.afterSave('MyClass', (req) => {
|
||||
expect(req.context.yolo).toBe(1);
|
||||
expect(req.context.some).toBe('value');
|
||||
calledAfter = true;
|
||||
});
|
||||
|
||||
const object = new Parse.Object('MyClass');
|
||||
await object.save();
|
||||
expect(calledBefore).toBe(true);
|
||||
expect(calledAfter).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,9 @@ const triggers = require('../lib/triggers');
|
||||
const HooksController = require('../lib/Controllers/HooksController').default;
|
||||
const express = require("express");
|
||||
const bodyParser = require('body-parser');
|
||||
const auth = require('../lib/Auth');
|
||||
const Config = require('../lib/Config');
|
||||
|
||||
|
||||
const port = 12345;
|
||||
const hookServerURL = "http://localhost:" + port;
|
||||
@@ -503,3 +506,39 @@ describe('Hooks', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('triggers', () => {
|
||||
it('should produce a proper request object with context in beforeSave', () => {
|
||||
const config = Config.get('test');
|
||||
const master = auth.master(config);
|
||||
const context = {
|
||||
originalKey: 'original'
|
||||
};
|
||||
const req = triggers.getRequestObject(triggers.Types.beforeSave, master, {}, {}, config, context);
|
||||
expect(req.context.originalKey).toBe('original');
|
||||
req.context = {
|
||||
key: 'value'
|
||||
};
|
||||
expect(context.key).toBe(undefined);
|
||||
req.context = {
|
||||
key: 'newValue'
|
||||
};
|
||||
expect(context.key).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should produce a proper request object with context in afterSave', () => {
|
||||
const config = Config.get('test');
|
||||
const master = auth.master(config);
|
||||
const context = {};
|
||||
const req = triggers.getRequestObject(triggers.Types.afterSave, master, {}, {}, config, context);
|
||||
expect(req.context).not.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not set context on beforeFind', () => {
|
||||
const config = Config.get('test');
|
||||
const master = auth.master(config);
|
||||
const context = {};
|
||||
const req = triggers.getRequestObject(triggers.Types.beforeFind, master, {}, {}, config, context);
|
||||
expect(req.context).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,6 +34,7 @@ function RestWrite(config, auth, className, query, data, originalData, clientSDK
|
||||
this.clientSDK = clientSDK;
|
||||
this.storage = {};
|
||||
this.runOptions = {};
|
||||
this.context = {};
|
||||
if (!query && data.objectId) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId is an invalid field name.');
|
||||
}
|
||||
@@ -165,7 +166,7 @@ RestWrite.prototype.runBeforeTrigger = function() {
|
||||
}
|
||||
|
||||
return Promise.resolve().then(() => {
|
||||
return triggers.maybeRunTrigger(triggers.Types.beforeSave, this.auth, updatedObject, originalObject, this.config);
|
||||
return triggers.maybeRunTrigger(triggers.Types.beforeSave, this.auth, updatedObject, originalObject, this.config, this.context);
|
||||
}).then((response) => {
|
||||
if (response && response.object) {
|
||||
this.storage.fieldsChangedByTrigger = _.reduce(response.object, (result, value, key) => {
|
||||
@@ -1142,7 +1143,7 @@ RestWrite.prototype.runAfterTrigger = function() {
|
||||
this.config.liveQueryController.onAfterSave(updatedObject.className, updatedObject, originalObject);
|
||||
|
||||
// Run afterSave trigger
|
||||
return triggers.maybeRunTrigger(triggers.Types.afterSave, this.auth, updatedObject, originalObject, this.config)
|
||||
return triggers.maybeRunTrigger(triggers.Types.afterSave, this.auth, updatedObject, originalObject, this.config, this.context)
|
||||
.catch(function(err) {
|
||||
logger.warn('afterSave caught an error', err);
|
||||
})
|
||||
|
||||
101
src/triggers.js
101
src/triggers.js
@@ -46,24 +46,58 @@ function validateClassNameForTriggers(className, type) {
|
||||
|
||||
const _triggerStore = {};
|
||||
|
||||
export function addFunction(functionName, handler, validationHandler, applicationId) {
|
||||
const Category = {
|
||||
Functions: 'Functions',
|
||||
Validators: 'Validators',
|
||||
Jobs: 'Jobs',
|
||||
Triggers: 'Triggers'
|
||||
}
|
||||
|
||||
function getStore(category, name, applicationId) {
|
||||
const path = name.split('.');
|
||||
path.splice(-1); // remove last component
|
||||
applicationId = applicationId || Parse.applicationId;
|
||||
_triggerStore[applicationId] = _triggerStore[applicationId] || baseStore();
|
||||
_triggerStore[applicationId].Functions[functionName] = handler;
|
||||
_triggerStore[applicationId].Validators[functionName] = validationHandler;
|
||||
let store = _triggerStore[applicationId][category];
|
||||
for (const component of path) {
|
||||
store = store[component];
|
||||
if (!store) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
function add(category, name, handler, applicationId) {
|
||||
const lastComponent = name.split('.').splice(-1);
|
||||
const store = getStore(category, name, applicationId);
|
||||
store[lastComponent] = handler;
|
||||
}
|
||||
|
||||
function remove(category, name, applicationId) {
|
||||
const lastComponent = name.split('.').splice(-1);
|
||||
const store = getStore(category, name, applicationId);
|
||||
delete store[lastComponent];
|
||||
}
|
||||
|
||||
function get(category, name, applicationId) {
|
||||
const lastComponent = name.split('.').splice(-1);
|
||||
const store = getStore(category, name, applicationId);
|
||||
return store[lastComponent];
|
||||
}
|
||||
|
||||
export function addFunction(functionName, handler, validationHandler, applicationId) {
|
||||
add(Category.Functions, functionName, handler, applicationId);
|
||||
add(Category.Validators, functionName, validationHandler, applicationId);
|
||||
}
|
||||
|
||||
export function addJob(jobName, handler, applicationId) {
|
||||
applicationId = applicationId || Parse.applicationId;
|
||||
_triggerStore[applicationId] = _triggerStore[applicationId] || baseStore();
|
||||
_triggerStore[applicationId].Jobs[jobName] = handler;
|
||||
add(Category.Jobs, jobName, handler, applicationId);
|
||||
}
|
||||
|
||||
export function addTrigger(type, className, handler, applicationId) {
|
||||
validateClassNameForTriggers(className, type);
|
||||
applicationId = applicationId || Parse.applicationId;
|
||||
_triggerStore[applicationId] = _triggerStore[applicationId] || baseStore();
|
||||
_triggerStore[applicationId].Triggers[type][className] = handler;
|
||||
add(Category.Triggers, `${type}.${className}`, handler, applicationId);
|
||||
}
|
||||
|
||||
export function addLiveQueryEventHandler(handler, applicationId) {
|
||||
@@ -73,13 +107,11 @@ export function addLiveQueryEventHandler(handler, applicationId) {
|
||||
}
|
||||
|
||||
export function removeFunction(functionName, applicationId) {
|
||||
applicationId = applicationId || Parse.applicationId;
|
||||
delete _triggerStore[applicationId].Functions[functionName]
|
||||
remove(Category.Functions, functionName, applicationId);
|
||||
}
|
||||
|
||||
export function removeTrigger(type, className, applicationId) {
|
||||
applicationId = applicationId || Parse.applicationId;
|
||||
delete _triggerStore[applicationId].Triggers[type][className]
|
||||
remove(Category.Triggers, `${type}.${className}`, applicationId);
|
||||
}
|
||||
|
||||
export function _unregisterAll() {
|
||||
@@ -90,14 +122,7 @@ export function getTrigger(className, triggerType, applicationId) {
|
||||
if (!applicationId) {
|
||||
throw "Missing ApplicationID";
|
||||
}
|
||||
var manager = _triggerStore[applicationId]
|
||||
if (manager
|
||||
&& manager.Triggers
|
||||
&& manager.Triggers[triggerType]
|
||||
&& manager.Triggers[triggerType][className]) {
|
||||
return manager.Triggers[triggerType][className];
|
||||
}
|
||||
return undefined;
|
||||
return get(Category.Triggers, `${triggerType}.${className}`, applicationId);
|
||||
}
|
||||
|
||||
export function triggerExists(className: string, type: string, applicationId: string): boolean {
|
||||
@@ -105,19 +130,11 @@ export function triggerExists(className: string, type: string, applicationId: st
|
||||
}
|
||||
|
||||
export function getFunction(functionName, applicationId) {
|
||||
var manager = _triggerStore[applicationId];
|
||||
if (manager && manager.Functions) {
|
||||
return manager.Functions[functionName];
|
||||
}
|
||||
return undefined;
|
||||
return get(Category.Functions, functionName, applicationId);
|
||||
}
|
||||
|
||||
export function getJob(jobName, applicationId) {
|
||||
var manager = _triggerStore[applicationId];
|
||||
if (manager && manager.Jobs) {
|
||||
return manager.Jobs[jobName];
|
||||
}
|
||||
return undefined;
|
||||
return get(Category.Jobs, jobName, applicationId);
|
||||
}
|
||||
|
||||
export function getJobs(applicationId) {
|
||||
@@ -130,15 +147,11 @@ export function getJobs(applicationId) {
|
||||
|
||||
|
||||
export function getValidator(functionName, applicationId) {
|
||||
var manager = _triggerStore[applicationId];
|
||||
if (manager && manager.Validators) {
|
||||
return manager.Validators[functionName];
|
||||
}
|
||||
return undefined;
|
||||
return get(Category.Validators, functionName, applicationId);
|
||||
}
|
||||
|
||||
export function getRequestObject(triggerType, auth, parseObject, originalParseObject, config) {
|
||||
var request = {
|
||||
export function getRequestObject(triggerType, auth, parseObject, originalParseObject, config, context) {
|
||||
const request = {
|
||||
triggerName: triggerType,
|
||||
object: parseObject,
|
||||
master: false,
|
||||
@@ -151,6 +164,11 @@ export function getRequestObject(triggerType, auth, parseObject, originalParseOb
|
||||
request.original = originalParseObject;
|
||||
}
|
||||
|
||||
if (triggerType === Types.beforeSave || triggerType === Types.afterSave) {
|
||||
// Set a copy of the context on the request object.
|
||||
request.context = Object.assign({}, context);
|
||||
}
|
||||
|
||||
if (!auth) {
|
||||
return request;
|
||||
}
|
||||
@@ -390,17 +408,20 @@ export function maybeRunQueryTrigger(triggerType, className, restWhere, restOpti
|
||||
// Resolves to an object, empty or containing an object key. A beforeSave
|
||||
// trigger will set the object key to the rest format object to save.
|
||||
// originalParseObject is optional, we only need that for before/afterSave functions
|
||||
export function maybeRunTrigger(triggerType, auth, parseObject, originalParseObject, config) {
|
||||
export function maybeRunTrigger(triggerType, auth, parseObject, originalParseObject, config, context) {
|
||||
if (!parseObject) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
return new Promise(function (resolve, reject) {
|
||||
var trigger = getTrigger(parseObject.className, triggerType, config.applicationId);
|
||||
if (!trigger) return resolve();
|
||||
var request = getRequestObject(triggerType, auth, parseObject, originalParseObject, config);
|
||||
var request = getRequestObject(triggerType, auth, parseObject, originalParseObject, config, context);
|
||||
var { success, error } = getResponseObject(request, (object) => {
|
||||
logTriggerSuccessBeforeHook(
|
||||
triggerType, parseObject.className, parseObject.toJSON(), object, auth);
|
||||
if (triggerType === Types.beforeSave || triggerType === Types.afterSave) {
|
||||
Object.assign(context, request.context);
|
||||
}
|
||||
resolve(object);
|
||||
}, (error) => {
|
||||
logTriggerErrorBeforeHook(
|
||||
|
||||
Reference in New Issue
Block a user