Parse Dashboard "delete all rows" does not trigger before/after delete triggers
A quick note, I'm not certain if this is a Parse Dashboard issue since the behavior in question is part of Parse.Cloud, but I can only trigger this behavior from Dashboard since the JS SDK does not have a delete all records method available.
If I need to move this issue over to the "correct" repo, I will report it there as well and reference this one.
Make sure these boxes are checked before submitting your issue -- thanks for reporting issues back to Parse Dashboard!
- [x] You're running version >=1.0.21 of Parse Dashboard.
- [x] You're running version >=2.2.24 of Parse Server.
- [x] You've searched through existing issues. Chances are that your issue has been reported or resolved before.
Use case:
Since MongoDB does not support record indexing, I'm trying to create a custom Class that listens for records being created and deleted using the beforeSave/afterSave methods. However, in testing I noticed that when I delete all records, neither of the above events are fired and the docs do not specify an alternate event for all records being deleted. So I'm curious how Parse Dashboard is accomplishing this?
Environment Setup
Running Parse Server via an Node app bulit on express, you can fire up the https://github.com/ParsePlatform/parse-server-example repo.
Steps to reproduce
-
Replace the contents of the
index.jsfile wit hthe following source:// Example express application adding the parse-server module to expose Parse // compatible API routes. var express = require('express'); var ParseServer = require('parse-server').ParseServer; var path = require('path'); var databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI; if (!databaseUri) { console.log('DATABASE_URI not specified, falling back to localhost.'); } var api = new ParseServer({ databaseURI: databaseUri || 'mongodb://localhost:27017/dev' , cloud: process.env.CLOUD_CODE_MAIN || __dirname + '/cloud/main.js' , appId: process.env.APP_ID || 'myAppId' , masterKey: process.env.MASTER_KEY || 'afesfklafkslsjjksljklfesfesjkl' //Add your master key here. Keep it secret! , serverURL: process.env.SERVER_URL || 'http://localhost:1337/parse' // Don't forget to change to https if needed , liveQuery: { classNames: ["Posts", "Comments"] // List of classes to support for query subscriptions } }); // Client-keys like the javascript key or the .NET key are not necessary with parse-server // If you wish you require them, you can set them as options in the initialization above: // javascriptKey, restAPIKey, dotNetKey, clientKey var app = express(); // Serve static assets from the /public folder app.use('/public', express.static(path.join(__dirname, '/public'))); // Serve the Parse API on the /parse URL prefix var mountPath = process.env.PARSE_MOUNT || '/parse'; app.use(mountPath, api); // Parse Server plays nicely with the rest of your web routes app.get('/', function(req, res) { res.status(200).send('I dream of being a website. Please star the parse-server repo on GitHub!'); }); // There will be a test page available on the /test path of your server url // Remove this before launching your app app.get('/test', function(req, res) { res.sendFile(path.join(__dirname, '/public/test.html')); }); var port = process.env.PORT || 1337; var httpServer = require('http').createServer(app); httpServer.listen(port, function() { console.log('parse-server-example running on port ' + port + '.'); }); var Parse = require('parse/node').Parse , ParseDashboard = require('parse-dashboard') ; var parseDashboardConfig = { apps: [ { appId: process.env.APP_ID || 'myAppId' , appName: process.env.APP_NAME || 'myAppName' , serverURL: process.env.SERVER_URL || 'http://localhost:1337/parse' , masterKey: process.env.MASTER_KEY || 'afesfklafkslsjjksljklfesfesjkl' , production: false } ] , users: [ { user: 'admin' , pass: 'password' } ] }; var dashboard = new ParseDashboard(parseDashboardConfig, true); app.use('/dashboard', dashboard); Parse.Cloud = Object.assign({}, Parse.Cloud, require('parse-server/lib/cloud-code/Parse.Cloud.js')); var CUSTOM_CLASS = 'CustomDummyClass'; Parse.Cloud.beforeSave(CUSTOM_CLASS, function(req, res) { console.log('---- BEFORE SAVE RECORD', req); res.success(); }); Parse.Cloud.afterSave(CUSTOM_CLASS, function(req) { console.log('---- AFTER SAVE RECORD', req); }); Parse.Cloud.beforeDelete(CUSTOM_CLASS, function(req, res) { console.log('---- BEFORE DELETE RECORD', req); res.success(); }); Parse.Cloud.afterDelete(CUSTOM_CLASS, function(req) { console.log('---- AFTER DELETE RECORD', req); }); // This will enable the Live Query real-time server ParseServer.createLiveQueryServer(httpServer); -
You'll likely need to run
npm i -S parse-dashboardas theparse-server-exampledoesn't include it by default -
Start your local server instance via the CLI and open your browser to the
/dashboardlink and login withadminandpasswordrespectively per theparseDashboardConfig.usersconfig above -
Create a Class in Parse and name it the same as your
CUSTOM_CLASSvariable from the snippet above -
Navigate into your newly created class and add a couple records
- you should see the message
---- BEFORE/AFTER SAVE RECORD { ... }and then the record data of a parse subclass object
- you should see the message
-
Delete one or many of the records you created from that class to verify the delete call is logging
---- BEFORE/AFTER DELETE RECORD {...}as expected- if you select multiple rows and choose
Delete these rows, thebefore/afterDeleteevents are fired for each row that was deleted
- if you select multiple rows and choose
-
Now make sure at least one row exists in your Class and choose the
Delete all rowsoption from the Edit context menu in the dashboard.- EXPECTED: We should expect some logged data to occur after the delete events have triggered
- ACTUAL: Nothing gets logged
Logs/Trace
Note: If you get a browser JS error please run npm run dev. This will provide source maps and a much more useful stack trace.
λ npm run start
> [email protected] start C:\Users\matr06749\github\parse-server-example
> node index.js
DATABASE_URI not specified, falling back to localhost.
(node:10548) DeprecationWarning: Using Buffer without `new` will soon stop working. Use `new Buffer()`, or preferably `Buffer.from()`, `Buffer.allocUnsafe()` or `Buffer.alloc()` instead.
parse-server-example running on port 1337.
info: Parse LiveQuery Server starts running
---- BEFORE SAVE RECORD { triggerName: 'beforeSave',
object: ParseObject { _objCount: 0, className: 'CustomDummyClass' },
master: true,
log: LoggerController { options: undefined, appId: 'myAppId' },
installationId: '48c297b0-8be1-9442-08b3-32dfd79436d3' }
info: beforeSave triggered for CustomDummyClass for user undefined:
Input: {"random":"afefefes"}
Result: {"object":{"random":"afefefes"}} className=CustomDummyClass, triggerType=beforeSave, user=undefined
---- AFTER SAVE RECORD { triggerName: 'afterSave',
object: ParseObject { _objCount: 1, className: 'CustomDummyClass', id: 'y29OTsRCgb' },
master: true,
log: LoggerController { options: undefined, appId: 'myAppId' },
installationId: '48c297b0-8be1-9442-08b3-32dfd79436d3' }
info: afterSave triggered for CustomDummyClass for user undefined:
Input: {"random":"afefefes","createdAt":"2016-12-23T01:59:46.295Z","updatedAt":"2016-12-23T01:59:46.295Z","objectId":"y29OTsRCgb"} className=CustomDummyClass, triggerType=afterSave, user=undefined
---- BEFORE SAVE RECORD { triggerName: 'beforeSave',
object: ParseObject { _objCount: 2, className: 'CustomDummyClass' },
master: true,
log: LoggerController { options: undefined, appId: 'myAppId' },
installationId: '48c297b0-8be1-9442-08b3-32dfd79436d3' }
info: beforeSave triggered for CustomDummyClass for user undefined:
Input: {"random":"gahjkljslkfes"}
Result: {"object":{"random":"gahjkljslkfes"}} className=CustomDummyClass, triggerType=beforeSave, user=undefined
---- AFTER SAVE RECORD { triggerName: 'afterSave',
object: ParseObject { _objCount: 3, className: 'CustomDummyClass', id: 'QVoI7qNiRp' },
master: true,
log: LoggerController { options: undefined, appId: 'myAppId' },
installationId: '48c297b0-8be1-9442-08b3-32dfd79436d3' }
info: afterSave triggered for CustomDummyClass for user undefined:
Input: {"random":"gahjkljslkfes","createdAt":"2016-12-23T01:59:52.253Z","updatedAt":"2016-12-23T01:59:52.253Z","objectId":"QVoI7qNiRp"} className=CustomDummyClass, triggerType=afterSave, user=undefined
---- BEFORE SAVE RECORD { triggerName: 'beforeSave',
object: ParseObject { _objCount: 4, className: 'CustomDummyClass' },
master: true,
log: LoggerController { options: undefined, appId: 'myAppId' },
installationId: '48c297b0-8be1-9442-08b3-32dfd79436d3' }
info: beforeSave triggered for CustomDummyClass for user undefined:
Input: {"random":"fajkfls;ajfke;fes"}
Result: {"object":{"random":"fajkfls;ajfke;fes"}} className=CustomDummyClass, triggerType=beforeSave, user=undefined
---- AFTER SAVE RECORD { triggerName: 'afterSave',
object: ParseObject { _objCount: 5, className: 'CustomDummyClass', id: 'fid9oCorDl' },
master: true,
log: LoggerController { options: undefined, appId: 'myAppId' },
installationId: '48c297b0-8be1-9442-08b3-32dfd79436d3' }
info: afterSave triggered for CustomDummyClass for user undefined:
Input: {"random":"fajkfls;ajfke;fes","createdAt":"2016-12-23T01:59:57.254Z","updatedAt":"2016-12-23T01:59:57.254Z","objectId":"fid9oCorDl"} className=CustomDummyClass, triggerType=afterSave, user=undefined
---- BEFORE SAVE RECORD { triggerName: 'beforeSave',
object: ParseObject { _objCount: 6, className: 'CustomDummyClass' },
master: true,
log: LoggerController { options: undefined, appId: 'myAppId' },
installationId: '48c297b0-8be1-9442-08b3-32dfd79436d3' }
info: beforeSave triggered for CustomDummyClass for user undefined:
Input: {"random":"fajskfel;agjosa"}
Result: {"object":{"random":"fajskfel;agjosa"}} className=CustomDummyClass, triggerType=beforeSave, user=undefined
---- AFTER SAVE RECORD { triggerName: 'afterSave',
object: ParseObject { _objCount: 7, className: 'CustomDummyClass', id: 'ovTqvxZWlf' },
master: true,
log: LoggerController { options: undefined, appId: 'myAppId' },
installationId: '48c297b0-8be1-9442-08b3-32dfd79436d3' }
info: afterSave triggered for CustomDummyClass for user undefined:
Input: {"random":"fajskfel;agjosa","createdAt":"2016-12-23T02:00:06.579Z","updatedAt":"2016-12-23T02:00:06.579Z","objectId":"ovTqvxZWlf"} className=CustomDummyClass, triggerType=afterSave, user=undefined
---- BEFORE SAVE RECORD { triggerName: 'beforeSave',
object: ParseObject { _objCount: 8, className: 'CustomDummyClass' },
master: true,
log: LoggerController { options: undefined, appId: 'myAppId' },
installationId: '48c297b0-8be1-9442-08b3-32dfd79436d3' }
info: beforeSave triggered for CustomDummyClass for user undefined:
Input: {"random":"saflejflkajfeles"}
Result: {"object":{"random":"saflejflkajfeles"}} className=CustomDummyClass, triggerType=beforeSave, user=undefined
---- AFTER SAVE RECORD { triggerName: 'afterSave',
object: ParseObject { _objCount: 9, className: 'CustomDummyClass', id: '3GZHPxY8bK' },
master: true,
log: LoggerController { options: undefined, appId: 'myAppId' },
installationId: '48c297b0-8be1-9442-08b3-32dfd79436d3' }
info: afterSave triggered for CustomDummyClass for user undefined:
Input: {"random":"saflejflkajfeles","createdAt":"2016-12-23T02:00:11.482Z","updatedAt":"2016-12-23T02:00:11.482Z","objectId":"3GZHPxY8bK"} className=CustomDummyClass, triggerType=afterSave, user=undefined
---- BEFORE DELETE RECORD { triggerName: 'beforeDelete',
object:
ParseObject {
_objCount: 10,
className: 'CustomDummyClass',
id: 'ovTqvxZWlf' },
master: true,
log: LoggerController { options: undefined, appId: 'myAppId' },
installationId: '48c297b0-8be1-9442-08b3-32dfd79436d3' }
info: beforeDelete triggered for CustomDummyClass for user undefined:
Input: {"random":"fajskfel;agjosa","createdAt":"2016-12-23T02:00:06.579Z","updatedAt":"2016-12-23T02:00:06.579Z","objectId":"ovTqvxZWlf"}
Result: {} className=CustomDummyClass, triggerType=beforeDelete, user=undefined
---- BEFORE DELETE RECORD { triggerName: 'beforeDelete',
object:
ParseObject {
_objCount: 11,
className: 'CustomDummyClass',
id: 'fid9oCorDl' },
master: true,
log: LoggerController { options: undefined, appId: 'myAppId' },
installationId: '48c297b0-8be1-9442-08b3-32dfd79436d3' }
info: beforeDelete triggered for CustomDummyClass for user undefined:
Input: {"random":"fajkfls;ajfke;fes","createdAt":"2016-12-23T01:59:57.254Z","updatedAt":"2016-12-23T01:59:57.254Z","objectId":"fid9oCorDl"}
Result: {} className=CustomDummyClass, triggerType=beforeDelete, user=undefined
---- BEFORE DELETE RECORD { triggerName: 'beforeDelete',
object:
ParseObject {
_objCount: 12,
className: 'CustomDummyClass',
id: 'QVoI7qNiRp' },
master: true,
log: LoggerController { options: undefined, appId: 'myAppId' },
installationId: '48c297b0-8be1-9442-08b3-32dfd79436d3' }
info: beforeDelete triggered for CustomDummyClass for user undefined:
Input: {"random":"gahjkljslkfes","createdAt":"2016-12-23T01:59:52.253Z","updatedAt":"2016-12-23T01:59:52.253Z","objectId":"QVoI7qNiRp"}
Result: {} className=CustomDummyClass, triggerType=beforeDelete, user=undefined
---- AFTER DELETE RECORD { triggerName: 'afterDelete',
object:
ParseObject {
_objCount: 10,
className: 'CustomDummyClass',
id: 'ovTqvxZWlf' },
master: true,
log: LoggerController { options: undefined, appId: 'myAppId' },
installationId: '48c297b0-8be1-9442-08b3-32dfd79436d3' }
info: afterDelete triggered for CustomDummyClass for user undefined:
Input: {"random":"fajskfel;agjosa","createdAt":"2016-12-23T02:00:06.579Z","updatedAt":"2016-12-23T02:00:06.579Z","objectId":"ovTqvxZWlf"} className=CustomDummyClass, triggerType=afterDelete, user=undefined
---- AFTER DELETE RECORD { triggerName: 'afterDelete',
object:
ParseObject {
_objCount: 11,
className: 'CustomDummyClass',
id: 'fid9oCorDl' },
master: true,
log: LoggerController { options: undefined, appId: 'myAppId' },
installationId: '48c297b0-8be1-9442-08b3-32dfd79436d3' }
info: afterDelete triggered for CustomDummyClass for user undefined:
Input: {"random":"fajkfls;ajfke;fes","createdAt":"2016-12-23T01:59:57.254Z","updatedAt":"2016-12-23T01:59:57.254Z","objectId":"fid9oCorDl"} className=CustomDummyClass, triggerType=afterDelete, user=undefined
---- AFTER DELETE RECORD { triggerName: 'afterDelete',
object:
ParseObject {
_objCount: 12,
className: 'CustomDummyClass',
id: 'QVoI7qNiRp' },
master: true,
log: LoggerController { options: undefined, appId: 'myAppId' },
installationId: '48c297b0-8be1-9442-08b3-32dfd79436d3' }
info: afterDelete triggered for CustomDummyClass for user undefined:
Input: {"random":"gahjkljslkfes","createdAt":"2016-12-23T01:59:52.253Z","updatedAt":"2016-12-23T01:59:52.253Z","objectId":"QVoI7qNiRp"} className=CustomDummyClass, triggerType=afterDelete, user=undefined
#----------------
# NOTE: There should be extra log data regarding deleteBefore and deleteAfter since I triggered `Delete all rows` at this point, however there is no log data due to the events not triggering after this action has taken place, despite the data being removed.
#----------------
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
I got a same issue
yes same issue