From 64d391320bbdb519af8ff93fe8579315ef48e36e Mon Sep 17 00:00:00 2001 From: patelmilanun <20059797+patelmilanun@users.noreply.github.com> Date: Wed, 28 Jun 2023 20:33:16 +0530 Subject: [PATCH] feat: Add support for confirmation dialog before script execution in data browser (#2481) --- README.md | 29 +++++++-- .../BrowserCell/BrowserCell.react.js | 64 +++++++++++++++---- src/components/BrowserCell/BrowserCell.scss | 7 +- 3 files changed, 80 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 14b50243af..c55beb58b9 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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) @@ -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: @@ -367,7 +381,6 @@ 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": [ { @@ -375,7 +388,9 @@ You can specify scripts to execute Cloud Functions with the `scripts` option: { "title": "Delete Account", "classes": ["_User"], - "cloudCodeFunction": "deleteAccount" + "cloudCodeFunction": "deleteAccount", + "showConfirmationDialog": true, + "confirmationDialogStyle": "critical" } ] } diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index ddb8650917..1e46dda562 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -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, }; } @@ -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)))] @@ -278,6 +283,7 @@ 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({ @@ -285,16 +291,12 @@ export default class BrowserCell extends Component { 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); } } }) @@ -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 { @@ -423,6 +441,27 @@ export default class BrowserCell extends Component { classes.push(styles.required); } + let extras = null; + if (this.state.showConfirmationDialog) + extras = ( + this.toggleConfirmationDialog()} + onConfirm={() => { + this.executeSript(this.selectedScript); + this.toggleConfirmationDialog(); + }} + > +
+ {`Do you want to run script "${this.selectedScript.title}" on "${this.selectedScript.className}" object "${this.selectedScript.objectId}"?`} +
+
+ ); + return {this.state.content} + {extras} } } diff --git a/src/components/BrowserCell/BrowserCell.scss b/src/components/BrowserCell/BrowserCell.scss index 8fc90b8eef..f2244498d8 100644 --- a/src/components/BrowserCell/BrowserCell.scss +++ b/src/components/BrowserCell/BrowserCell.scss @@ -77,4 +77,9 @@ .readonly { color: #04263bd1; -} \ No newline at end of file +} + +.action { + padding: 28px; + border-style: solid; +}