diff --git a/README.md b/README.md
index 0a12d09e8c..e39438399a 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,7 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https
- [Other Configuration Options](#other-configuration-options)
- [Prevent columns sorting](#prevent-columns-sorting)
- [Custom order in the filter popup](#custom-order-in-the-filter-popup)
+ - [Scripts](#scripts)
- [Running as Express Middleware](#running-as-express-middleware)
- [Deploying Parse Dashboard](#deploying-parse-dashboard)
- [Preparing for Deployment](#preparing-for-deployment)
@@ -362,6 +363,71 @@ For example:
You can conveniently create a filter definition without having to write it by hand by first saving a filter in the data browser, then exporting the filter definition under *App Settings > Export Class Preferences*.
+### Scripts
+
+You can specify scripts to execute Cloud Functions with the `scripts` option:
+
+
+```json
+"apps": [
+ {
+ "scripts": [
+ {
+ "title": "Delete Account",
+ "classes": ["_User"],
+ "cloudCodeFunction": "deleteAccount"
+ }
+ ]
+ }
+]
+```
+
+Next, define the Cloud Function in Parse Server that will be called. The object that has been selected in the data browser will be made available as a request parameter:
+
+```js
+Parse.Cloud.define('deleteAccount', async (req) => {
+ req.params.object.set('deleted', true);
+ await req.params.object.save(null, {useMasterKey: true});
+}, {
+ requireMaster: true
+});
+```
+
+⚠️ Depending on your Parse Server version you may need to set the Parse Server option `encodeParseObjectInCloudFunction` to `true` so that the selected object in the data browser is made available in the Cloud Function as an instance of `Parse.Object`. If the option is not set, is set to `false`, or you are using an older version of Parse Server, the object is made available as a plain JavaScript object and needs to be converted from a JSON object to a `Parse.Object` instance with `req.params.object = Parse.Object.fromJSON(req.params.object);`, before you can call any `Parse.Object` properties and methods on it.
+
+For older versions of Parse Server:
+
+
+Parse Server >=4.4.0 <6.2.0
+
+```js
+Parse.Cloud.define('deleteAccount', async (req) => {
+ req.params.object = Parse.Object.fromJSON(req.params.object);
+ req.params.object.set('deleted', true);
+ await req.params.object.save(null, {useMasterKey: true});
+}, {
+ requireMaster: true
+});
+```
+
+
+
+
+Parse Server >=2.1.4 <4.4.0
+
+```js
+Parse.Cloud.define('deleteAccount', async (req) => {
+ if (!req.master || !req.params.object) {
+ throw 'Unauthorized';
+ }
+ req.params.object = Parse.Object.fromJSON(req.params.object);
+ req.params.object.set('deleted', true);
+ await req.params.object.save(null, {useMasterKey: true});
+});
+```
+
+
+
# Running as Express Middleware
Instead of starting Parse Dashboard with the CLI, you can also run it as an [express](https://github.com/expressjs/express) middleware.
diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js
index 94d5cfc2b8..11fb0401f8 100644
--- a/src/components/BrowserCell/BrowserCell.react.js
+++ b/src/components/BrowserCell/BrowserCell.react.js
@@ -15,7 +15,6 @@ import React, { Component } from 'react';
import styles from 'components/BrowserCell/BrowserCell.scss';
import baseStyles from 'stylesheets/base.scss';
import * as ColumnPreferences from 'lib/ColumnPreferences';
-
export default class BrowserCell extends Component {
constructor() {
super();
@@ -279,6 +278,29 @@ export default class BrowserCell extends Component {
});
}
+ const validScripts = (this.props.scripts || []).filter(script => script.classes?.includes(this.props.className));
+ if (validScripts.length) {
+ onEditSelectedRow && contextMenuOptions.push({
+ text: 'Scripts',
+ items: validScripts.map(script => {
+ return {
+ text: script.title,
+ callback: async () => {
+ try {
+ const object = Parse.Object.extend(this.props.className).createWithoutData(this.props.objectId);
+ const response = await Parse.Cloud.run(script.cloudCodeFunction, {object: object.toPointer()}, {useMasterKey: true});
+ this.props.showNote(response || `${script.title} ran with object ${object.id}}`);
+ this.props.onRefresh();
+ } catch (e) {
+ this.props.showNote(e.message, true);
+ console.log(`Could not run ${script.title}: ${e}`);
+ }
+ }
+ }
+ })
+ });
+ }
+
return contextMenuOptions;
}
diff --git a/src/components/BrowserRow/BrowserRow.react.js b/src/components/BrowserRow/BrowserRow.react.js
index 5ae4e78ac3..7a6c99a74e 100644
--- a/src/components/BrowserRow/BrowserRow.react.js
+++ b/src/components/BrowserRow/BrowserRow.react.js
@@ -100,7 +100,11 @@ export default class BrowserRow extends Component {
markRequiredFieldRow={markRequiredFieldRow}
setCopyableValue={setCopyableValue}
setContextMenu={setContextMenu}
- onEditSelectedRow={onEditSelectedRow} />
+ onEditSelectedRow={onEditSelectedRow}
+ showNote={this.props.showNote}
+ onRefresh={this.props.onRefresh}
+ scripts={this.props.scripts}
+ />
);
})}
diff --git a/src/dashboard/Data/Browser/BrowserTable.react.js b/src/dashboard/Data/Browser/BrowserTable.react.js
index 235241ccd8..2fb8ec22aa 100644
--- a/src/dashboard/Data/Browser/BrowserTable.react.js
+++ b/src/dashboard/Data/Browser/BrowserTable.react.js
@@ -159,6 +159,9 @@ export default class BrowserTable extends React.Component {
setContextMenu={this.props.setContextMenu}
onEditSelectedRow={this.props.onEditSelectedRow}
markRequiredFieldRow={this.props.markRequiredFieldRow}
+ showNote={this.props.showNote}
+ onRefresh={this.props.onRefresh}
+ scripts={this.context.scripts}
/>
+ onEditSelectedRow={this.props.onEditSelectedRow}
+ showNote={this.props.showNote}
+ onRefresh={this.props.onRefresh}
+ scripts={this.context.scripts}
+ />
}
if (this.props.editing) {
diff --git a/src/lib/ParseApp.js b/src/lib/ParseApp.js
index b71d14dc48..9b84a50fe7 100644
--- a/src/lib/ParseApp.js
+++ b/src/lib/ParseApp.js
@@ -46,7 +46,8 @@ export default class ParseApp {
preventSchemaEdits,
graphQLServerURL,
columnPreference,
- classPreference
+ scripts,
+ classPreference,
}) {
this.name = appName;
this.createdAt = created_at ? new Date(created_at) : new Date();
@@ -73,6 +74,7 @@ export default class ParseApp {
this.preventSchemaEdits = preventSchemaEdits || false;
this.graphQLServerURL = graphQLServerURL;
this.columnPreference = columnPreference;
+ this.scripts = scripts;
if(!supportedPushLocales) {
console.warn('Missing push locales for \'' + appName + '\', see this link for details on setting localizations up. https://github.com/parse-community/parse-dashboard#configuring-localized-push-notifications');