From 8feac9b8359f27ecd7bd4342be7bcae75289ba35 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 28 Jun 2024 09:16:49 +1000 Subject: [PATCH] feat: Add optional restriction of script execution to certain object fields and values (#2488) --- README.md | 25 +++++++++++++ .../BrowserCell/BrowserCell.react.js | 37 ++++++++++++++++--- src/components/BrowserRow/BrowserRow.react.js | 2 + .../ContextMenu/ContextMenu.react.js | 4 ++ .../Data/Browser/BrowserTable.react.js | 2 + 5 files changed, 64 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b273835767..8d89b31906 100644 --- a/README.md +++ b/README.md @@ -398,6 +398,31 @@ You can specify scripts to execute Cloud Functions with the `scripts` option: ] ``` +You can also specify custom fields with the `scrips` option: + +```json +"apps": [ + { + "scripts": [ + { + "title": "Delete account", + "classes": [ + { + "name": "_User", + "fields": [ + { "name": "createdAt", "validator": "value => value > new Date(\"2025\")" } + ] + } + ], + "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 diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index a033568c77..c2d31de5a3 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -336,10 +336,34 @@ export default class BrowserCell extends Component { }); } - const { className, objectId } = this.props; - const validScripts = (this.props.scripts || []).filter(script => - script.classes?.includes(this.props.className) - ); + const { className, objectId, field, scripts = [], rowValue } = this.props; + let validator = null; + const validScripts = (scripts || []).filter(script => { + if (script.classes?.includes(className)) { + return true; + } + for (const script of script?.classes || []) { + if (script?.name !== className) { + continue; + } + const fields = script?.fields || []; + if (script?.fields.includes(field) || script?.fields.includes('*')) { + return true; + } + for (const currentField of fields) { + if (Object.prototype.toString.call(currentField) === '[object Object]') { + if (currentField.name === field) { + if (typeof currentField.validator === 'string') { + validator = eval(currentField.validator); + } else { + validator = currentField.validator; + } + return true; + } + } + } + } + }); if (validScripts.length) { onEditSelectedRow && contextMenuOptions.push({ @@ -347,12 +371,13 @@ export default class BrowserCell extends Component { items: validScripts.map(script => { return { text: script.title, + disabled: validator?.(rowValue, field) === false, callback: () => { this.selectedScript = { ...script, className, objectId }; if (script.showConfirmationDialog) { this.toggleConfirmationDialog(); } else { - this.executeSript(script); + this.executeScript(script); } }, }; @@ -363,7 +388,7 @@ export default class BrowserCell extends Component { return contextMenuOptions; } - async executeSript(script) { + async executeScript(script) { try { const object = Parse.Object.extend(this.props.className).createWithoutData( this.props.objectId diff --git a/src/components/BrowserRow/BrowserRow.react.js b/src/components/BrowserRow/BrowserRow.react.js index fc32426d30..c2054942b2 100644 --- a/src/components/BrowserRow/BrowserRow.react.js +++ b/src/components/BrowserRow/BrowserRow.react.js @@ -31,6 +31,7 @@ export default class BrowserRow extends Component { order, readOnlyFields, row, + rowValue, rowWidth, selection, selectRow, @@ -122,6 +123,7 @@ export default class BrowserRow extends Component { className={className} field={name} row={row} + rowValue={rowValue} col={j} type={type} readonly={isUnique || readOnlyFields.indexOf(name) > -1} diff --git a/src/components/ContextMenu/ContextMenu.react.js b/src/components/ContextMenu/ContextMenu.react.js index 04d973b7d9..40c33462da 100644 --- a/src/components/ContextMenu/ContextMenu.react.js +++ b/src/components/ContextMenu/ContextMenu.react.js @@ -71,7 +71,11 @@ const MenuSection = ({ level, items, path, setPath, hide }) => {
  • { + if (item.disabled === true) { + return; + } item.callback && item.callback(); hide(); }} diff --git a/src/dashboard/Data/Browser/BrowserTable.react.js b/src/dashboard/Data/Browser/BrowserTable.react.js index 95cd313cc7..6bf22627fc 100644 --- a/src/dashboard/Data/Browser/BrowserTable.react.js +++ b/src/dashboard/Data/Browser/BrowserTable.react.js @@ -155,6 +155,7 @@ export default class BrowserTable extends React.Component { order={this.props.order} readOnlyFields={READ_ONLY} row={index} + rowValue={this.props.data[index]} rowWidth={rowWidth} selection={this.props.selection} selectRow={this.props.selectRow} @@ -310,6 +311,7 @@ export default class BrowserTable extends React.Component { order={this.props.order} readOnlyFields={READ_ONLY} row={i} + rowValue={this.props.data[i]} rowWidth={rowWidth} selection={this.props.selection} selectRow={this.props.selectRow}