From ca27c8bd98f70fde6689d9f202642d073fa8c8cd Mon Sep 17 00:00:00 2001 From: Milan Patel Date: Wed, 28 Jun 2023 02:09:59 +0530 Subject: [PATCH 01/10] feat: add support for dialog for script - add showConfirmationDialog and confirmationDialogStyle in script --- .../BrowserCell/BrowserCell.react.js | 66 +++++++++++++++---- .../Data/Browser/ExecuteScriptDialog.react.js | 38 +++++++++++ .../Data/Browser/ExecuteScriptDialog.scss | 13 ++++ 3 files changed, 105 insertions(+), 12 deletions(-) create mode 100644 src/dashboard/Data/Browser/ExecuteScriptDialog.react.js create mode 100644 src/dashboard/Data/Browser/ExecuteScriptDialog.scss diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index 11fb0401f..1363c564f 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -15,16 +15,20 @@ 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 ExecuteScriptDialog from 'dashboard/Data/Browser/ExecuteScriptDialog.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: [], + showExecuteScriptDialog: false, }; } @@ -208,7 +212,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.showExecuteScriptDialog !== this.state.showExecuteScriptDialog) { return true; } const shallowVerifyProps = [...new Set(Object.keys(this.props).concat(Object.keys(nextProps)))] @@ -278,6 +282,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 +290,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()}, {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.toggleExecuteScriptDialog(); + else + this.executeSript(script); } } }) @@ -304,6 +305,30 @@ 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 || `${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}`); + } + } + + toggleExecuteScriptDialog(){ + this.setState((prevState) => ({ showExecuteScriptDialog: !prevState.showExecuteScriptDialog })); + } + getSetFilterContextMenuOption(constraints) { if (constraints) { return { @@ -423,6 +448,22 @@ export default class BrowserCell extends Component { classes.push(styles.required); } + let extras = null; + if (this.state.showExecuteScriptDialog) + extras = ( + this.toggleExecuteScriptDialog()} + onConfirm={() => { + this.executeSript(this.selectedScript); + this.toggleExecuteScriptDialog(); + }} + /> + ); + return {this.state.content} + {extras} } } diff --git a/src/dashboard/Data/Browser/ExecuteScriptDialog.react.js b/src/dashboard/Data/Browser/ExecuteScriptDialog.react.js new file mode 100644 index 000000000..aad5b5e0c --- /dev/null +++ b/src/dashboard/Data/Browser/ExecuteScriptDialog.react.js @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016-present, Parse, LLC + * All rights reserved. + * + * This source code is licensed under the license found in the LICENSE file in + * the root directory of this source tree. + */ +import Modal from 'components/Modal/Modal.react'; +import React from 'react'; +import labelStyles from 'components/Label/Label.scss'; +import styles from 'dashboard/Data/Browser/ExecuteScriptDialog.scss'; + +export default class ExecuteScriptDialog extends React.Component { + constructor() { + super(); + } + + render() { + return ( + +
+ {`Do you want to run script "${this.props.scriptName}" on "${this.props.className}" object "${this.props.objectId}"?`} +
+
+ ); + } +} diff --git a/src/dashboard/Data/Browser/ExecuteScriptDialog.scss b/src/dashboard/Data/Browser/ExecuteScriptDialog.scss new file mode 100644 index 000000000..d9a7548e3 --- /dev/null +++ b/src/dashboard/Data/Browser/ExecuteScriptDialog.scss @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2016-present, Parse, LLC + * All rights reserved. + * + * This source code is licensed under the license found in the LICENSE file in + * the root directory of this source tree. + */ + @import 'stylesheets/globals.scss'; + + .action { + padding: 28px; + border-style: solid; + } From 11dd9a8bda2c831c08d75d5a273fb1c524447a04 Mon Sep 17 00:00:00 2001 From: Milan Patel Date: Wed, 28 Jun 2023 02:17:47 +0530 Subject: [PATCH 02/10] fix lint errors --- src/dashboard/Data/Browser/ExecuteScriptDialog.react.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/dashboard/Data/Browser/ExecuteScriptDialog.react.js b/src/dashboard/Data/Browser/ExecuteScriptDialog.react.js index aad5b5e0c..b32ded01b 100644 --- a/src/dashboard/Data/Browser/ExecuteScriptDialog.react.js +++ b/src/dashboard/Data/Browser/ExecuteScriptDialog.react.js @@ -18,9 +18,7 @@ export default class ExecuteScriptDialog extends React.Component { render() { return ( -
+
{`Do you want to run script "${this.props.scriptName}" on "${this.props.className}" object "${this.props.objectId}"?`}
From aefd794dc5badbe1fa5893e17ed2a7bafedecb3f Mon Sep 17 00:00:00 2001 From: Milan Patel Date: Wed, 28 Jun 2023 12:17:38 +0530 Subject: [PATCH 03/10] fix format and text --- .../BrowserCell/BrowserCell.react.js | 47 +++++++++---------- src/components/BrowserCell/BrowserCell.scss | 7 ++- .../Data/Browser/ExecuteScriptDialog.react.js | 36 -------------- .../Data/Browser/ExecuteScriptDialog.scss | 13 ----- 4 files changed, 29 insertions(+), 74 deletions(-) delete mode 100644 src/dashboard/Data/Browser/ExecuteScriptDialog.react.js delete mode 100644 src/dashboard/Data/Browser/ExecuteScriptDialog.scss diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index 1363c564f..51f0f2ecf 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -15,7 +15,8 @@ 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 ExecuteScriptDialog from 'dashboard/Data/Browser/ExecuteScriptDialog.react'; +import labelStyles from 'components/Label/Label.scss'; +import Modal from 'components/Modal/Modal.react'; export default class BrowserCell extends Component { constructor() { @@ -306,23 +307,15 @@ export default class BrowserCell extends Component { } 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 || `${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}`); - } + 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}`); + } } toggleExecuteScriptDialog(){ @@ -451,17 +444,23 @@ export default class BrowserCell extends Component { let extras = null; if (this.state.showExecuteScriptDialog) extras = ( - this.toggleExecuteScriptDialog()} onConfirm={() => { this.executeSript(this.selectedScript); this.toggleExecuteScriptDialog(); }} - /> + > +
+ {`Do you want to run script "${this.selectedScript.title}" on "${this.selectedScript.className}" object "${this.selectedScript.objectId}"?`} +
+ ); return -
- {`Do you want to run script "${this.props.scriptName}" on "${this.props.className}" object "${this.props.objectId}"?`} -
- - ); - } -} diff --git a/src/dashboard/Data/Browser/ExecuteScriptDialog.scss b/src/dashboard/Data/Browser/ExecuteScriptDialog.scss deleted file mode 100644 index d9a7548e3..000000000 --- a/src/dashboard/Data/Browser/ExecuteScriptDialog.scss +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) 2016-present, Parse, LLC - * All rights reserved. - * - * This source code is licensed under the license found in the LICENSE file in - * the root directory of this source tree. - */ - @import 'stylesheets/globals.scss'; - - .action { - padding: 28px; - border-style: solid; - } From 97ade51c949d00c005ad5bffd955a5b0324f43ea Mon Sep 17 00:00:00 2001 From: Milan Patel Date: Wed, 28 Jun 2023 12:25:03 +0530 Subject: [PATCH 04/10] fix ExecuteScript to ConfirmationDialog and lint --- .../BrowserCell/BrowserCell.react.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index 51f0f2ecf..7d98265d1 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -29,7 +29,7 @@ export default class BrowserCell extends Component { showTooltip: false, content: null, classes: [], - showExecuteScriptDialog: false, + showConfirmationDialog: false, }; } @@ -213,7 +213,7 @@ export default class BrowserCell extends Component { } shouldComponentUpdate(nextProps, nextState) { - if (nextState.showTooltip !== this.state.showTooltip || nextState.content !== this.state.content || nextState.showExecuteScriptDialog !== this.state.showExecuteScriptDialog) { + 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)))] @@ -294,7 +294,7 @@ export default class BrowserCell extends Component { callback: () => { this.selectedScript = { ...script, className, objectId }; if(script.showConfirmationDialog) - this.toggleExecuteScriptDialog(); + this.toggleConfirmationDialog(); else this.executeSript(script); } @@ -318,8 +318,8 @@ export default class BrowserCell extends Component { } } - toggleExecuteScriptDialog(){ - this.setState((prevState) => ({ showExecuteScriptDialog: !prevState.showExecuteScriptDialog })); + toggleConfirmationDialog(){ + this.setState((prevState) => ({ showConfirmationDialog: !prevState.showConfirmationDialog })); } getSetFilterContextMenuOption(constraints) { @@ -442,19 +442,19 @@ export default class BrowserCell extends Component { } let extras = null; - if (this.state.showExecuteScriptDialog) + if (this.state.showConfirmationDialog) extras = ( this.toggleExecuteScriptDialog()} + onCancel={() => this.toggleConfirmationDialog()} onConfirm={() => { this.executeSript(this.selectedScript); - this.toggleExecuteScriptDialog(); + this.toggleConfirmationDialog(); }} >
From 211bb6ed66318a16ae1524b467e8f981e85380b9 Mon Sep 17 00:00:00 2001 From: Milan Patel Date: Wed, 28 Jun 2023 13:16:14 +0530 Subject: [PATCH 05/10] fix parameter name --- src/components/BrowserCell/BrowserCell.react.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index 7d98265d1..7998d8331 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -445,7 +445,7 @@ export default class BrowserCell extends Component { if (this.state.showConfirmationDialog) extras = ( Date: Wed, 28 Jun 2023 13:22:45 +0530 Subject: [PATCH 06/10] add new options to readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 14b50243a..f33f1bb2d 100644 --- a/README.md +++ b/README.md @@ -375,7 +375,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" // or "info" for blue style } ] } From f89935851d2cc093bba84762047cb5d7edb222e2 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Wed, 28 Jun 2023 16:18:50 +0200 Subject: [PATCH 07/10] change default style to info Signed-off-by: Manuel <5673677+mtrezza@users.noreply.github.com> --- src/components/BrowserCell/BrowserCell.react.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index 7998d8331..f97543090 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -445,7 +445,7 @@ export default class BrowserCell extends Component { if (this.state.showConfirmationDialog) extras = ( Date: Wed, 28 Jun 2023 16:24:08 +0200 Subject: [PATCH 08/10] remove subtitle --- src/components/BrowserCell/BrowserCell.react.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index f97543090..1e46dda56 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -448,7 +448,6 @@ export default class BrowserCell extends Component { type={this.selectedScript.confirmationDialogStyle === 'critical' ? Modal.Types.DANGER : Modal.Types.INFO} icon="warn-outline" title={this.selectedScript.title} - subtitle="Confirm that you want to run this script." confirmText="Continue" cancelText="Cancel" onCancel={() => this.toggleConfirmationDialog()} From f82a98e48ac9dda153570a5cfd801c0d4db3bba0 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Wed, 28 Jun 2023 16:56:11 +0200 Subject: [PATCH 09/10] Update README.md --- README.md | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f33f1bb2d..65819a28b 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 Overview](#options-overview) - [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 Overview + +| 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": [ { @@ -377,7 +390,7 @@ You can specify scripts to execute Cloud Functions with the `scripts` option: "classes": ["_User"], "cloudCodeFunction": "deleteAccount", "showConfirmationDialog": true, - "confirmationDialogStyle": "critical" // or "info" for blue style + "confirmationDialogStyle": "critical" } ] } From e8c1bd8d75cc6aef84ad0caf9936239ee346967e Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Wed, 28 Jun 2023 16:56:44 +0200 Subject: [PATCH 10/10] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 65819a28b..c55beb58b 100644 --- a/README.md +++ b/README.md @@ -31,7 +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 Overview](#options-overview) + - [Options](#options) - [File](#file) - [Environment variables](#environment-variables) - [Multiple apps](#multiple-apps) @@ -113,7 +113,7 @@ Parse Dashboard is continuously tested with the most recent releases of Node.js ## Configuring Parse Dashboard -### Options Overview +### Options | Parameter | Type | Optional | Default | Example | Description | |----------------------------------------|---------------------|----------|---------|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------|