Skip to content

Commit 5d9901e

Browse files
authored
feat: Execute script for selected rows (#2508)
1 parent ca2138b commit 5d9901e

File tree

3 files changed

+152
-0
lines changed

3 files changed

+152
-0
lines changed

src/dashboard/Data/Browser/Browser.react.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import EmptyState from 'components/EmptyState/EmptyState.react';
1818
import ExportDialog from 'dashboard/Data/Browser/ExportDialog.react';
1919
import AttachRowsDialog from 'dashboard/Data/Browser/AttachRowsDialog.react';
2020
import AttachSelectedRowsDialog from 'dashboard/Data/Browser/AttachSelectedRowsDialog.react';
21+
import ExecuteScriptRowsDialog from 'dashboard/Data/Browser/ExecuteScriptRowsDialog.react';
2122
import CloneSelectedRowsDialog from 'dashboard/Data/Browser/CloneSelectedRowsDialog.react';
2223
import EditRowDialog from 'dashboard/Data/Browser/EditRowDialog.react';
2324
import ExportSelectedRowsDialog from 'dashboard/Data/Browser/ExportSelectedRowsDialog.react';
@@ -95,6 +96,8 @@ class Browser extends DashboardView {
9596

9697
useMasterKey: true,
9798
currentUser: Parse.User.current(),
99+
100+
processedScripts: 0,
98101
};
99102

100103
this.prefetchData = this.prefetchData.bind(this);
@@ -114,6 +117,9 @@ class Browser extends DashboardView {
114117
this.cancelAttachRows = this.cancelAttachRows.bind(this);
115118
this.confirmAttachRows = this.confirmAttachRows.bind(this);
116119
this.showAttachSelectedRowsDialog = this.showAttachSelectedRowsDialog.bind(this);
120+
this.showExecuteScriptRowsDialog = this.showExecuteScriptRowsDialog.bind(this);
121+
this.confirmExecuteScriptRows = this.confirmExecuteScriptRows.bind(this);
122+
this.cancelExecuteScriptRowsDialog = this.cancelExecuteScriptRowsDialog.bind(this);
117123
this.confirmAttachSelectedRows = this.confirmAttachSelectedRows.bind(this);
118124
this.cancelAttachSelectedRows = this.cancelAttachSelectedRows.bind(this);
119125
this.showCloneSelectedRowsDialog = this.showCloneSelectedRowsDialog.bind(this);
@@ -1326,6 +1332,18 @@ class Browser extends DashboardView {
13261332
});
13271333
}
13281334

1335+
showExecuteScriptRowsDialog() {
1336+
this.setState({
1337+
showExecuteScriptRowsDialog: true,
1338+
});
1339+
}
1340+
1341+
cancelExecuteScriptRowsDialog() {
1342+
this.setState({
1343+
showExecuteScriptRowsDialog: false,
1344+
});
1345+
}
1346+
13291347
async confirmAttachSelectedRows(
13301348
className,
13311349
targetObjectId,
@@ -1346,6 +1364,37 @@ class Browser extends DashboardView {
13461364
});
13471365
}
13481366

1367+
async confirmExecuteScriptRows(script) {
1368+
try {
1369+
const objects = [];
1370+
Object.keys(this.state.selection).forEach(key =>
1371+
objects.push(Parse.Object.extend(this.props.params.className).createWithoutData(key))
1372+
);
1373+
for (const object of objects) {
1374+
const response = await Parse.Cloud.run(
1375+
script.cloudCodeFunction,
1376+
{ object: object.toPointer() },
1377+
{ useMasterKey: true }
1378+
);
1379+
this.setState(prevState => ({
1380+
processedScripts: prevState.processedScripts + 1,
1381+
}));
1382+
this.showNote(
1383+
response ||
1384+
`Ran script "${script.title}" on "${this.props.className}" object "${object.id}".`
1385+
);
1386+
}
1387+
this.refresh();
1388+
} catch (e) {
1389+
this.showNote(e.message, true);
1390+
console.log(`Could not run ${script.title}: ${e}`);
1391+
} finally{
1392+
this.setState(({
1393+
processedScripts: 0,
1394+
}));
1395+
}
1396+
}
1397+
13491398
showCloneSelectedRowsDialog() {
13501399
this.setState({
13511400
showCloneSelectedRowsDialog: true,
@@ -1790,6 +1839,7 @@ class Browser extends DashboardView {
17901839
onRefresh={this.refresh}
17911840
onAttachRows={this.showAttachRowsDialog}
17921841
onAttachSelectedRows={this.showAttachSelectedRowsDialog}
1842+
onExecuteScriptRows={this.showExecuteScriptRowsDialog}
17931843
onCloneSelectedRows={this.showCloneSelectedRowsDialog}
17941844
onEditSelectedRow={this.showEditRowDialog}
17951845
onEditPermissions={this.onDialogToggle}
@@ -1943,6 +1993,16 @@ class Browser extends DashboardView {
19431993
onConfirm={this.confirmAttachSelectedRows}
19441994
/>
19451995
);
1996+
} else if (this.state.showExecuteScriptRowsDialog) {
1997+
extras = (
1998+
<ExecuteScriptRowsDialog
1999+
currentClass={this.props.params.className}
2000+
selection={this.state.selection}
2001+
onCancel={this.cancelExecuteScriptRowsDialog}
2002+
onConfirm={this.confirmExecuteScriptRows}
2003+
processedScripts={this.state.processedScripts}
2004+
/>
2005+
);
19462006
} else if (this.state.showCloneSelectedRowsDialog) {
19472007
extras = (
19482008
<CloneSelectedRowsDialog

src/dashboard/Data/Browser/BrowserToolbar.react.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const BrowserToolbar = ({
4545
onExport,
4646
onRemoveColumn,
4747
onDeleteRows,
48+
onExecuteScriptRows,
4849
onDropClass,
4950
onChangeCLP,
5051
onRefresh,
@@ -161,6 +162,7 @@ const BrowserToolbar = ({
161162
text={selectionLength === 1 && !selection['*'] ? 'Delete this row' : 'Delete these rows'}
162163
onClick={() => onDeleteRows(selection)}
163164
/>
165+
<Separator />
164166
{enableColumnManipulation ? (
165167
<MenuItem text="Delete a column" onClick={onRemoveColumn} />
166168
) : (
@@ -378,6 +380,18 @@ const BrowserToolbar = ({
378380
<noscript />
379381
)}
380382
{enableSecurityDialog ? <div className={styles.toolbarSeparator} /> : <noscript />}
383+
<BrowserMenu
384+
setCurrent={setCurrent}
385+
title="Script"
386+
icon="gear-solid"
387+
>
388+
<MenuItem
389+
disabled={selectionLength === 0}
390+
text={selectionLength === 1 && !selection['*'] ? 'Run script on selected row...' : `Run script on ${selectionLength} selected rows...`}
391+
onClick={() => onExecuteScriptRows(selection)}
392+
/>
393+
</BrowserMenu>
394+
<div className={styles.toolbarSeparator} />
381395
{menu}
382396
{editCloneRows && editCloneRows.length > 0 && <div className={styles.toolbarSeparator} />}
383397
{editCloneRows && editCloneRows.length > 0 && (
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import React from 'react';
2+
import FormModal from 'components/FormModal/FormModal.react';
3+
import Field from 'components/Field/Field.react';
4+
import Label from 'components/Label/Label.react';
5+
import Dropdown from 'components/Dropdown/Dropdown.react';
6+
import Option from 'components/Dropdown/Option.react';
7+
import { CurrentApp } from 'context/currentApp';
8+
9+
export default class ExecuteScriptRowsDialog extends React.Component {
10+
static contextType = CurrentApp;
11+
constructor(props) {
12+
super(props);
13+
14+
this.state = {
15+
currentScript: null,
16+
validScripts: [],
17+
};
18+
19+
this.handleConfirm = this.handleConfirm.bind(this);
20+
this.handleScriptChange = this.handleScriptChange.bind(this);
21+
}
22+
23+
componentWillMount() {
24+
const { selection, currentClass } = this.props;
25+
26+
const validScripts = (this.context.scripts || []).filter(script =>
27+
script.classes?.includes(currentClass)
28+
);
29+
30+
if (selection && validScripts.length > 0) {
31+
this.setState({
32+
currentScript: validScripts[0],
33+
validScripts: validScripts,
34+
});
35+
}
36+
}
37+
38+
handleConfirm() {
39+
return this.props.onConfirm(this.state.currentScript);
40+
}
41+
42+
handleScriptChange(scriptName) {
43+
this.setState({
44+
currentScript: this.state.validScripts.find(script => script.title === scriptName),
45+
});
46+
}
47+
48+
render() {
49+
const { validScripts } = this.state;
50+
const { selection, processedScripts } = this.props;
51+
const selectionLength = Object.keys(selection).length;
52+
return (
53+
<FormModal
54+
open
55+
icon="gears"
56+
iconSize={40}
57+
title={selectionLength > 1 ? `Run script on ${selectionLength} selected rows` : 'Run script on selected row'}
58+
submitText="Run"
59+
inProgressText={`Executed ${processedScripts} of ${selectionLength} rows`}
60+
onClose={this.props.onCancel}
61+
onSubmit={this.handleConfirm}
62+
>
63+
<Field
64+
label={<Label text="Script" />}
65+
input={
66+
<Dropdown value={this.state.currentScript?.title} onChange={this.handleScriptChange}>
67+
{validScripts.map(script => (
68+
<Option key={script.title} value={script.title}>
69+
{script.title}
70+
</Option>
71+
))}
72+
</Dropdown>
73+
}
74+
/>
75+
</FormModal>
76+
);
77+
}
78+
}

0 commit comments

Comments
 (0)