Skip to content

Commit

Permalink
feat: Add support for confirmation dialog before script execution in …
Browse files Browse the repository at this point in the history
…data browser (#2481)
  • Loading branch information
patelmilanun committed Jun 28, 2023
1 parent 359ebdc commit 64d3913
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 20 deletions.
29 changes: 22 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https
- [Parse Server](#parse-server)
- [Node.js](#nodejs)
- [Configuring Parse Dashboard](#configuring-parse-dashboard)
- [Options](#options)
- [File](#file)
- [Environment variables](#environment-variables)
- [Multiple apps](#multiple-apps)
Expand All @@ -42,6 +43,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)
- [Persistent Filters](#persistent-filters)
- [Scripts](#scripts)
- [Running as Express Middleware](#running-as-express-middleware)
- [Deploying Parse Dashboard](#deploying-parse-dashboard)
Expand Down Expand Up @@ -103,14 +105,26 @@ Parse Dashboard is compatible with the following Parse Server versions.
### Node.js
Parse Dashboard is continuously tested with the most recent releases of Node.js to ensure compatibility. We follow the [Node.js Long Term Support plan](https://github.com/nodejs/Release) and only test against versions that are officially supported and have not reached their end-of-life date.

| Version | Latest Version | End-of-Life | Compatible |
|------------|----------------|-------------|--------------|
| Node.js 14 | 14.20.1 | April 2023 | ✅ Yes |
| Node.js 16 | 16.17.0 | April 2024 | ✅ Yes |
| Node.js 18 | 18.9.0 | May 2025 | ✅ Yes |
| Version | Latest Version | End-of-Life | Compatible |
|------------|----------------|-------------|------------|
| Node.js 14 | 14.20.1 | April 2023 | ✅ Yes |
| Node.js 16 | 16.17.0 | April 2024 | ✅ Yes |
| Node.js 18 | 18.9.0 | May 2025 | ✅ Yes |

## Configuring Parse Dashboard

### Options

| Parameter | Type | Optional | Default | Example | Description |
|----------------------------------------|---------------------|----------|---------|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
| `apps` | Array<Object> | no | - | `[{ ... }, { ... }]` | The apps that are configured for the dashboard. |
| `apps.scripts` | Array<Object> | yes | `[]` | `[{ ... }, { ... }]` | The scripts that can be executed for that app. |
| `apps.scripts.title` | String | no | - | `'Delete User'` | The title that will be displayed in the data browser context menu and the script run confirmation dialog. |
| `apps.scripts.classes` | Array<String> | no | - | `['_User']` | The classes of Parse Objects for which the scripts can be executed. |
| `apps.scripts.cloudCodeFunction` | String | no | - | `'deleteUser'` | The name of the Parse Cloud Function to execute. |
| `apps.scripts.showConfirmationDialog` | Bool | yes | `false` | `true` | Is `true` if a confirmation dialog should be displayed before the script is executed, `false` if the script should be executed immediately. |
| `apps.scripts.confirmationDialogStyle` | String | yes | `info` | `critical` | The style of the confirmation dialog. Valid values: `info` (blue style), `critical` (red style). |

### File

You can also start the dashboard from the command line with a config file. To do this, create a new file called `parse-dashboard-config.json` inside your local Parse Dashboard directory hierarchy. The file should match the following format:
Expand Down Expand Up @@ -367,15 +381,16 @@ You can conveniently create a filter definition without having to write it by ha

You can specify scripts to execute Cloud Functions with the `scripts` option:


```json
"apps": [
{
"scripts": [
{
"title": "Delete Account",
"classes": ["_User"],
"cloudCodeFunction": "deleteAccount"
"cloudCodeFunction": "deleteAccount",
"showConfirmationDialog": true,
"confirmationDialogStyle": "critical"
}
]
}
Expand Down
64 changes: 52 additions & 12 deletions src/components/BrowserCell/BrowserCell.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,21 @@ import React, { Component } from 'react';
import styles from 'components/BrowserCell/BrowserCell.scss';
import baseStyles from 'stylesheets/base.scss';
import * as ColumnPreferences from 'lib/ColumnPreferences';
import labelStyles from 'components/Label/Label.scss';
import Modal from 'components/Modal/Modal.react';

export default class BrowserCell extends Component {
constructor() {
super();

this.cellRef = React.createRef();
this.copyableValue = undefined;
this.selectedScript = null;
this.state = {
showTooltip: false,
content: null,
classes: []
classes: [],
showConfirmationDialog: false,
};
}

Expand Down Expand Up @@ -208,7 +213,7 @@ export default class BrowserCell extends Component {
}

shouldComponentUpdate(nextProps, nextState) {
if (nextState.showTooltip !== this.state.showTooltip || nextState.content !== this.state.content ) {
if (nextState.showTooltip !== this.state.showTooltip || nextState.content !== this.state.content || nextState.showConfirmationDialog !== this.state.showConfirmationDialog) {
return true;
}
const shallowVerifyProps = [...new Set(Object.keys(this.props).concat(Object.keys(nextProps)))]
Expand Down Expand Up @@ -278,23 +283,20 @@ export default class BrowserCell extends Component {
});
}

const { className, objectId } = this.props;
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(), selectedField: this.props.field}, {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}`);
}
callback: () => {
this.selectedScript = { ...script, className, objectId };
if(script.showConfirmationDialog)
this.toggleConfirmationDialog();
else
this.executeSript(script);
}
}
})
Expand All @@ -304,6 +306,22 @@ export default class BrowserCell extends Component {
return contextMenuOptions;
}

async executeSript(script) {
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 || `Ran script "${script.title}" on "${this.props.className}" object "${object.id}".`);
this.props.onRefresh();
} catch (e) {
this.props.showNote(e.message, true);
console.log(`Could not run ${script.title}: ${e}`);
}
}

toggleConfirmationDialog(){
this.setState((prevState) => ({ showConfirmationDialog: !prevState.showConfirmationDialog }));
}

getSetFilterContextMenuOption(constraints) {
if (constraints) {
return {
Expand Down Expand Up @@ -423,6 +441,27 @@ export default class BrowserCell extends Component {
classes.push(styles.required);
}

let extras = null;
if (this.state.showConfirmationDialog)
extras = (
<Modal
type={this.selectedScript.confirmationDialogStyle === 'critical' ? Modal.Types.DANGER : Modal.Types.INFO}
icon="warn-outline"
title={this.selectedScript.title}
confirmText="Continue"
cancelText="Cancel"
onCancel={() => this.toggleConfirmationDialog()}
onConfirm={() => {
this.executeSript(this.selectedScript);
this.toggleConfirmationDialog();
}}
>
<div className={[labelStyles.label, labelStyles.text, styles.action].join(' ')}>
{`Do you want to run script "${this.selectedScript.title}" on "${this.selectedScript.className}" object "${this.selectedScript.objectId}"?`}
</div>
</Modal>
);

return <span
ref={this.cellRef}
className={classes.join(' ')}
Expand Down Expand Up @@ -454,6 +493,7 @@ export default class BrowserCell extends Component {
onContextMenu={this.onContextMenu.bind(this)}
>
{this.state.content}
{extras}
</span>
}
}
7 changes: 6 additions & 1 deletion src/components/BrowserCell/BrowserCell.scss
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,9 @@

.readonly {
color: #04263bd1;
}
}

.action {
padding: 28px;
border-style: solid;
}

0 comments on commit 64d3913

Please sign in to comment.