Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for confirmation dialog before script execution in data browser #2481

Merged
merged 11 commits into from
Jun 28, 2023
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 {
mtrezza marked this conversation as resolved.
Show resolved Hide resolved
padding: 28px;
border-style: solid;
}