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();
|
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 HooksController = require('../lib/Controllers/HooksController').default;
|
||||||
const express = require("express");
|
const express = require("express");
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
|
const auth = require('../lib/Auth');
|
||||||
|
const Config = require('../lib/Config');
|
||||||
|
|
||||||
|
|
||||||
const port = 12345;
|
const port = 12345;
|
||||||
const hookServerURL = "http://localhost:" + port;
|
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.clientSDK = clientSDK;
|
||||||
this.storage = {};
|
this.storage = {};
|
||||||
this.runOptions = {};
|
this.runOptions = {};
|
||||||
|
this.context = {};
|
||||||
if (!query && data.objectId) {
|
if (!query && data.objectId) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId is an invalid field name.');
|
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 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) => {
|
}).then((response) => {
|
||||||
if (response && response.object) {
|
if (response && response.object) {
|
||||||
this.storage.fieldsChangedByTrigger = _.reduce(response.object, (result, value, key) => {
|
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);
|
this.config.liveQueryController.onAfterSave(updatedObject.className, updatedObject, originalObject);
|
||||||
|
|
||||||
// Run afterSave trigger
|
// 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) {
|
.catch(function(err) {
|
||||||
logger.warn('afterSave caught an error', 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 = {};
|
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;
|
applicationId = applicationId || Parse.applicationId;
|
||||||
_triggerStore[applicationId] = _triggerStore[applicationId] || baseStore();
|
_triggerStore[applicationId] = _triggerStore[applicationId] || baseStore();
|
||||||
_triggerStore[applicationId].Functions[functionName] = handler;
|
let store = _triggerStore[applicationId][category];
|
||||||
_triggerStore[applicationId].Validators[functionName] = validationHandler;
|
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) {
|
export function addJob(jobName, handler, applicationId) {
|
||||||
applicationId = applicationId || Parse.applicationId;
|
add(Category.Jobs, jobName, handler, applicationId);
|
||||||
_triggerStore[applicationId] = _triggerStore[applicationId] || baseStore();
|
|
||||||
_triggerStore[applicationId].Jobs[jobName] = handler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addTrigger(type, className, handler, applicationId) {
|
export function addTrigger(type, className, handler, applicationId) {
|
||||||
validateClassNameForTriggers(className, type);
|
validateClassNameForTriggers(className, type);
|
||||||
applicationId = applicationId || Parse.applicationId;
|
add(Category.Triggers, `${type}.${className}`, handler, applicationId);
|
||||||
_triggerStore[applicationId] = _triggerStore[applicationId] || baseStore();
|
|
||||||
_triggerStore[applicationId].Triggers[type][className] = handler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addLiveQueryEventHandler(handler, applicationId) {
|
export function addLiveQueryEventHandler(handler, applicationId) {
|
||||||
@@ -73,13 +107,11 @@ export function addLiveQueryEventHandler(handler, applicationId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function removeFunction(functionName, applicationId) {
|
export function removeFunction(functionName, applicationId) {
|
||||||
applicationId = applicationId || Parse.applicationId;
|
remove(Category.Functions, functionName, applicationId);
|
||||||
delete _triggerStore[applicationId].Functions[functionName]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeTrigger(type, className, applicationId) {
|
export function removeTrigger(type, className, applicationId) {
|
||||||
applicationId = applicationId || Parse.applicationId;
|
remove(Category.Triggers, `${type}.${className}`, applicationId);
|
||||||
delete _triggerStore[applicationId].Triggers[type][className]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function _unregisterAll() {
|
export function _unregisterAll() {
|
||||||
@@ -90,14 +122,7 @@ export function getTrigger(className, triggerType, applicationId) {
|
|||||||
if (!applicationId) {
|
if (!applicationId) {
|
||||||
throw "Missing ApplicationID";
|
throw "Missing ApplicationID";
|
||||||
}
|
}
|
||||||
var manager = _triggerStore[applicationId]
|
return get(Category.Triggers, `${triggerType}.${className}`, applicationId);
|
||||||
if (manager
|
|
||||||
&& manager.Triggers
|
|
||||||
&& manager.Triggers[triggerType]
|
|
||||||
&& manager.Triggers[triggerType][className]) {
|
|
||||||
return manager.Triggers[triggerType][className];
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function triggerExists(className: string, type: string, applicationId: string): boolean {
|
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) {
|
export function getFunction(functionName, applicationId) {
|
||||||
var manager = _triggerStore[applicationId];
|
return get(Category.Functions, functionName, applicationId);
|
||||||
if (manager && manager.Functions) {
|
|
||||||
return manager.Functions[functionName];
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getJob(jobName, applicationId) {
|
export function getJob(jobName, applicationId) {
|
||||||
var manager = _triggerStore[applicationId];
|
return get(Category.Jobs, jobName, applicationId);
|
||||||
if (manager && manager.Jobs) {
|
|
||||||
return manager.Jobs[jobName];
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getJobs(applicationId) {
|
export function getJobs(applicationId) {
|
||||||
@@ -130,15 +147,11 @@ export function getJobs(applicationId) {
|
|||||||
|
|
||||||
|
|
||||||
export function getValidator(functionName, applicationId) {
|
export function getValidator(functionName, applicationId) {
|
||||||
var manager = _triggerStore[applicationId];
|
return get(Category.Validators, functionName, applicationId);
|
||||||
if (manager && manager.Validators) {
|
|
||||||
return manager.Validators[functionName];
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRequestObject(triggerType, auth, parseObject, originalParseObject, config) {
|
export function getRequestObject(triggerType, auth, parseObject, originalParseObject, config, context) {
|
||||||
var request = {
|
const request = {
|
||||||
triggerName: triggerType,
|
triggerName: triggerType,
|
||||||
object: parseObject,
|
object: parseObject,
|
||||||
master: false,
|
master: false,
|
||||||
@@ -151,6 +164,11 @@ export function getRequestObject(triggerType, auth, parseObject, originalParseOb
|
|||||||
request.original = originalParseObject;
|
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) {
|
if (!auth) {
|
||||||
return request;
|
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
|
// 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.
|
// trigger will set the object key to the rest format object to save.
|
||||||
// originalParseObject is optional, we only need that for before/afterSave functions
|
// 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) {
|
if (!parseObject) {
|
||||||
return Promise.resolve({});
|
return Promise.resolve({});
|
||||||
}
|
}
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
var trigger = getTrigger(parseObject.className, triggerType, config.applicationId);
|
var trigger = getTrigger(parseObject.className, triggerType, config.applicationId);
|
||||||
if (!trigger) return resolve();
|
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) => {
|
var { success, error } = getResponseObject(request, (object) => {
|
||||||
logTriggerSuccessBeforeHook(
|
logTriggerSuccessBeforeHook(
|
||||||
triggerType, parseObject.className, parseObject.toJSON(), object, auth);
|
triggerType, parseObject.className, parseObject.toJSON(), object, auth);
|
||||||
|
if (triggerType === Types.beforeSave || triggerType === Types.afterSave) {
|
||||||
|
Object.assign(context, request.context);
|
||||||
|
}
|
||||||
resolve(object);
|
resolve(object);
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
logTriggerErrorBeforeHook(
|
logTriggerErrorBeforeHook(
|
||||||
|
|||||||
Reference in New Issue
Block a user