diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js
index ec4897117c..fbf6a31df5 100644
--- a/src/dashboard/Data/Browser/Browser.react.js
+++ b/src/dashboard/Data/Browser/Browser.react.js
@@ -18,6 +18,7 @@ import EmptyState from 'components/EmptyState/EmptyState.react';
import ExportDialog from 'dashboard/Data/Browser/ExportDialog.react';
import AttachRowsDialog from 'dashboard/Data/Browser/AttachRowsDialog.react';
import AttachSelectedRowsDialog from 'dashboard/Data/Browser/AttachSelectedRowsDialog.react';
+import ExecuteScriptRowsDialog from 'dashboard/Data/Browser/ExecuteScriptRowsDialog.react';
import CloneSelectedRowsDialog from 'dashboard/Data/Browser/CloneSelectedRowsDialog.react';
import EditRowDialog from 'dashboard/Data/Browser/EditRowDialog.react';
import ExportSelectedRowsDialog from 'dashboard/Data/Browser/ExportSelectedRowsDialog.react';
@@ -95,6 +96,8 @@ class Browser extends DashboardView {
useMasterKey: true,
currentUser: Parse.User.current(),
+
+ processedScripts: 0,
};
this.prefetchData = this.prefetchData.bind(this);
@@ -114,6 +117,9 @@ class Browser extends DashboardView {
this.cancelAttachRows = this.cancelAttachRows.bind(this);
this.confirmAttachRows = this.confirmAttachRows.bind(this);
this.showAttachSelectedRowsDialog = this.showAttachSelectedRowsDialog.bind(this);
+ this.showExecuteScriptRowsDialog = this.showExecuteScriptRowsDialog.bind(this);
+ this.confirmExecuteScriptRows = this.confirmExecuteScriptRows.bind(this);
+ this.cancelExecuteScriptRowsDialog = this.cancelExecuteScriptRowsDialog.bind(this);
this.confirmAttachSelectedRows = this.confirmAttachSelectedRows.bind(this);
this.cancelAttachSelectedRows = this.cancelAttachSelectedRows.bind(this);
this.showCloneSelectedRowsDialog = this.showCloneSelectedRowsDialog.bind(this);
@@ -1326,6 +1332,18 @@ class Browser extends DashboardView {
});
}
+ showExecuteScriptRowsDialog() {
+ this.setState({
+ showExecuteScriptRowsDialog: true,
+ });
+ }
+
+ cancelExecuteScriptRowsDialog() {
+ this.setState({
+ showExecuteScriptRowsDialog: false,
+ });
+ }
+
async confirmAttachSelectedRows(
className,
targetObjectId,
@@ -1346,6 +1364,37 @@ class Browser extends DashboardView {
});
}
+ async confirmExecuteScriptRows(script) {
+ try {
+ const objects = [];
+ Object.keys(this.state.selection).forEach(key =>
+ objects.push(Parse.Object.extend(this.props.params.className).createWithoutData(key))
+ );
+ for (const object of objects) {
+ const response = await Parse.Cloud.run(
+ script.cloudCodeFunction,
+ { object: object.toPointer() },
+ { useMasterKey: true }
+ );
+ this.setState(prevState => ({
+ processedScripts: prevState.processedScripts + 1,
+ }));
+ this.showNote(
+ response ||
+ `Ran script "${script.title}" on "${this.props.className}" object "${object.id}".`
+ );
+ }
+ this.refresh();
+ } catch (e) {
+ this.showNote(e.message, true);
+ console.log(`Could not run ${script.title}: ${e}`);
+ } finally{
+ this.setState(({
+ processedScripts: 0,
+ }));
+ }
+ }
+
showCloneSelectedRowsDialog() {
this.setState({
showCloneSelectedRowsDialog: true,
@@ -1790,6 +1839,7 @@ class Browser extends DashboardView {
onRefresh={this.refresh}
onAttachRows={this.showAttachRowsDialog}
onAttachSelectedRows={this.showAttachSelectedRowsDialog}
+ onExecuteScriptRows={this.showExecuteScriptRowsDialog}
onCloneSelectedRows={this.showCloneSelectedRowsDialog}
onEditSelectedRow={this.showEditRowDialog}
onEditPermissions={this.onDialogToggle}
@@ -1943,6 +1993,16 @@ class Browser extends DashboardView {
onConfirm={this.confirmAttachSelectedRows}
/>
);
+ } else if (this.state.showExecuteScriptRowsDialog) {
+ extras = (
+
+ );
} else if (this.state.showCloneSelectedRowsDialog) {
extras = (
onDeleteRows(selection)}
/>
+
{enableColumnManipulation ? (
) : (
@@ -378,6 +380,18 @@ const BrowserToolbar = ({
)}
{enableSecurityDialog ? : }
+
+
+
{menu}
{editCloneRows && editCloneRows.length > 0 && }
{editCloneRows && editCloneRows.length > 0 && (
diff --git a/src/dashboard/Data/Browser/ExecuteScriptRowsDialog.react.js b/src/dashboard/Data/Browser/ExecuteScriptRowsDialog.react.js
new file mode 100644
index 0000000000..5daf2c3e9e
--- /dev/null
+++ b/src/dashboard/Data/Browser/ExecuteScriptRowsDialog.react.js
@@ -0,0 +1,78 @@
+import React from 'react';
+import FormModal from 'components/FormModal/FormModal.react';
+import Field from 'components/Field/Field.react';
+import Label from 'components/Label/Label.react';
+import Dropdown from 'components/Dropdown/Dropdown.react';
+import Option from 'components/Dropdown/Option.react';
+import { CurrentApp } from 'context/currentApp';
+
+export default class ExecuteScriptRowsDialog extends React.Component {
+ static contextType = CurrentApp;
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ currentScript: null,
+ validScripts: [],
+ };
+
+ this.handleConfirm = this.handleConfirm.bind(this);
+ this.handleScriptChange = this.handleScriptChange.bind(this);
+ }
+
+ componentWillMount() {
+ const { selection, currentClass } = this.props;
+
+ const validScripts = (this.context.scripts || []).filter(script =>
+ script.classes?.includes(currentClass)
+ );
+
+ if (selection && validScripts.length > 0) {
+ this.setState({
+ currentScript: validScripts[0],
+ validScripts: validScripts,
+ });
+ }
+ }
+
+ handleConfirm() {
+ return this.props.onConfirm(this.state.currentScript);
+ }
+
+ handleScriptChange(scriptName) {
+ this.setState({
+ currentScript: this.state.validScripts.find(script => script.title === scriptName),
+ });
+ }
+
+ render() {
+ const { validScripts } = this.state;
+ const { selection, processedScripts } = this.props;
+ const selectionLength = Object.keys(selection).length;
+ return (
+ 1 ? `Run script on ${selectionLength} selected rows` : 'Run script on selected row'}
+ submitText="Run"
+ inProgressText={`Executed ${processedScripts} of ${selectionLength} rows`}
+ onClose={this.props.onCancel}
+ onSubmit={this.handleConfirm}
+ >
+ }
+ input={
+
+ {validScripts.map(script => (
+
+ ))}
+
+ }
+ />
+
+ );
+ }
+}