Skip to content

Commit

Permalink
feat: Add Cloud Function execution on Parse Object in data browser (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
dblythy committed Jun 23, 2023
1 parent 5f4696c commit 996ce91
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 4 deletions.
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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:

<details>
<summary>Parse Server &gt;=4.4.0 &lt;6.2.0</summary>

```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
});
```

</details>

<details>
<summary>Parse Server &gt;=2.1.4 &lt;4.4.0</summary>

```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});
});
```

</details>

# 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.
Expand Down
24 changes: 23 additions & 1 deletion src/components/BrowserCell/BrowserCell.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}

Expand Down
6 changes: 5 additions & 1 deletion src/components/BrowserRow/BrowserRow.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
/>
);
})}
</div>
Expand Down
12 changes: 11 additions & 1 deletion src/dashboard/Data/Browser/BrowserTable.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
/>
<Button
value="Clone"
Expand Down Expand Up @@ -211,6 +214,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}
/>
<Button
value="Add"
Expand Down Expand Up @@ -267,7 +273,11 @@ export default class BrowserTable extends React.Component {
setRelation={this.props.setRelation}
setCopyableValue={this.props.setCopyableValue}
setContextMenu={this.props.setContextMenu}
onEditSelectedRow={this.props.onEditSelectedRow} />
onEditSelectedRow={this.props.onEditSelectedRow}
showNote={this.props.showNote}
onRefresh={this.props.onRefresh}
scripts={this.context.scripts}
/>
}

if (this.props.editing) {
Expand Down
4 changes: 3 additions & 1 deletion src/lib/ParseApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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');
Expand Down

0 comments on commit 996ce91

Please sign in to comment.