Skip to content

Commit cee8b8d

Browse files
authored
feat: Add script execution on parallel batches with option script.executionBatchSize (#2828)
1 parent d21bd03 commit cee8b8d

File tree

2 files changed

+74
-34
lines changed

2 files changed

+74
-34
lines changed

README.md

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -126,25 +126,26 @@ Parse Dashboard is continuously tested with the most recent releases of Node.js
126126

127127
### Options
128128

129-
| Parameter | Type | Optional | Default | Example | Description |
130-
|----------------------------------------|---------------------|----------|---------|--------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------|
131-
| `apps` | Array<Object> | no | - | `[{ ... }, { ... }]` | The apps that are configured for the dashboard. |
132-
| `apps.appId` | String | yes | - | `"myAppId"` | The Application ID for your Parse Server instance. |
133-
| `apps.masterKey` | String \| Function | yes | - | `"exampleMasterKey"`, `() => "exampleMasterKey"` | The master key for full access to Parse Server. It can be provided directly as a String or as a Function returning a String. |
134-
| `apps.masterKeyTtl` | Number | no | - | `3600` | Time-to-live (TTL) for the master key in seconds. This defines how long the master key is cached before the `masterKey` function is re-triggered. |
135-
| `apps.serverURL` | String | yes | - | `"http://localhost:1337/parse"` | The URL where your Parse Server is running. |
136-
| `apps.appName` | String | no | - | `"MyApp"` | The display name of the app in the dashboard. |
137-
| `infoPanel` | Array<Object> | yes | - | `[{ ... }, { ... }]` | The [info panel](#info-panel) configuration. |
138-
| `infoPanel[*].title` | String | no | - | `User Details` | The panel title. |
139-
| `infoPanel[*].classes` | Array<String> | no | - | `["_User"]` | The classes for which the info panel should be displayed. |
140-
| `infoPanel[*].cloudCodeFunction` | String | no | - | `getUserDetails` | The Cloud Code Function which received the selected object in the data browser and returns the response to be displayed in the info panel. |
141-
| `apps.scripts` | Array<Object> | yes | `[]` | `[{ ... }, { ... }]` | The scripts that can be executed for that app. |
142-
| `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. |
143-
| `apps.scripts.classes` | Array<String> | no | - | `['_User']` | The classes of Parse Objects for which the scripts can be executed. |
144-
| `apps.scripts.cloudCodeFunction` | String | no | - | `'deleteUser'` | The name of the Parse Cloud Function to execute. |
145-
| `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. |
146-
| `apps.scripts.confirmationDialogStyle` | String | yes | `info` | `critical` | The style of the confirmation dialog. Valid values: `info` (blue style), `critical` (red style). |
147-
| `apps.cloudConfigHistoryLimit` | Integer | yes | `100` | `100` | The number of historic values that should be saved in the Cloud Config change history. Valid values: `0`...`Number.MAX_SAFE_INTEGER`. |
129+
| Parameter | Type | Optional | Default | Example | Description |
130+
|----------------------------------------|---------------------|----------|---------|--------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
131+
| `apps` | Array<Object> | no | - | `[{ ... }, { ... }]` | The apps that are configured for the dashboard. |
132+
| `apps.appId` | String | yes | - | `"myAppId"` | The Application ID for your Parse Server instance. |
133+
| `apps.masterKey` | String \| Function | yes | - | `"exampleMasterKey"`, `() => "exampleMasterKey"` | The master key for full access to Parse Server. It can be provided directly as a String or as a Function returning a String. |
134+
| `apps.masterKeyTtl` | Number | no | - | `3600` | Time-to-live (TTL) for the master key in seconds. This defines how long the master key is cached before the `masterKey` function is re-triggered. |
135+
| `apps.serverURL` | String | yes | - | `"http://localhost:1337/parse"` | The URL where your Parse Server is running. |
136+
| `apps.appName` | String | no | - | `"MyApp"` | The display name of the app in the dashboard. |
137+
| `infoPanel` | Array<Object> | yes | - | `[{ ... }, { ... }]` | The [info panel](#info-panel) configuration. |
138+
| `infoPanel[*].title` | String | no | - | `User Details` | The panel title. |
139+
| `infoPanel[*].classes` | Array<String> | no | - | `["_User"]` | The classes for which the info panel should be displayed. |
140+
| `infoPanel[*].cloudCodeFunction` | String | no | - | `getUserDetails` | The Cloud Code Function which received the selected object in the data browser and returns the response to be displayed in the info panel. |
141+
| `apps.scripts` | Array<Object> | yes | `[]` | `[{ ... }, { ... }]` | The scripts that can be executed for that app. |
142+
| `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. |
143+
| `apps.scripts.classes` | Array<String> | no | - | `['_User']` | The classes of Parse Objects for which the scripts can be executed. |
144+
| `apps.scripts.cloudCodeFunction` | String | no | - | `'deleteUser'` | The name of the Parse Cloud Function to execute. |
145+
| `apps.scripts.executionBatchSize` | Integer | yes | `1` | `10` | The batch size with which a script should be executed on all selected objects. For example, with 50 objects selected, a batch size of 10 means the script will run on 10 objects in parallel, running a total of 5 batches in serial. |
146+
| `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. |
147+
| `apps.scripts.confirmationDialogStyle` | String | yes | `info` | `critical` | The style of the confirmation dialog. Valid values: `info` (blue style), `critical` (red style). |
148+
| `apps.cloudConfigHistoryLimit` | Integer | yes | `100` | `100` | The number of historic values that should be saved in the Cloud Config change history. Valid values: `0`...`Number.MAX_SAFE_INTEGER`. |
148149

149150
### File
150151

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

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,24 +1571,63 @@ class Browser extends DashboardView {
15711571
}
15721572

15731573
async confirmExecuteScriptRows(script) {
1574+
const batchSize = script.executionBatchSize || 1;
15741575
try {
1575-
const objects = [];
1576-
Object.keys(this.state.selection).forEach(key =>
1577-
objects.push(Parse.Object.extend(this.props.params.className).createWithoutData(key))
1576+
const objects = Object.keys(this.state.selection).map(key =>
1577+
Parse.Object.extend(this.props.params.className).createWithoutData(key)
15781578
);
1579-
for (const object of objects) {
1580-
const response = await Parse.Cloud.run(
1581-
script.cloudCodeFunction,
1582-
{ object: object.toPointer() },
1583-
{ useMasterKey: true }
1579+
1580+
let totalErrorCount = 0;
1581+
let batchCount = 0;
1582+
const totalBatchCount = Math.ceil(objects.length / batchSize);
1583+
1584+
for (let i = 0; i < objects.length; i += batchSize) {
1585+
batchCount++;
1586+
const batch = objects.slice(i, i + batchSize);
1587+
const promises = batch.map(object =>
1588+
Parse.Cloud.run(
1589+
script.cloudCodeFunction,
1590+
{ object: object.toPointer() },
1591+
{ useMasterKey: true }
1592+
).then(response => ({
1593+
objectId: object.id,
1594+
response,
1595+
})).catch(error => ({
1596+
objectId: object.id,
1597+
error,
1598+
}))
15841599
);
1585-
this.setState(prevState => ({
1586-
processedScripts: prevState.processedScripts + 1,
1587-
}));
1588-
const note =
1589-
(typeof response === 'object' ? JSON.stringify(response) : response) ||
1590-
`Ran script "${script.title}" on "${object.id}".`;
1591-
this.showNote(note);
1600+
1601+
const results = await Promise.all(promises);
1602+
1603+
let batchErrorCount = 0;
1604+
results.forEach(({ objectId, response, error }) => {
1605+
this.setState(prevState => ({
1606+
processedScripts: prevState.processedScripts + 1,
1607+
}));
1608+
1609+
if (error) {
1610+
batchErrorCount += 1;
1611+
const errorMessage = `Error running script "${script.title}" on "${objectId}": ${error.message}`;
1612+
this.showNote(errorMessage, true);
1613+
console.error(errorMessage, error);
1614+
} else {
1615+
const note =
1616+
(typeof response === 'object' ? JSON.stringify(response) : response) ||
1617+
`Ran script "${script.title}" on "${objectId}".`;
1618+
this.showNote(note);
1619+
}
1620+
});
1621+
1622+
totalErrorCount += batchErrorCount;
1623+
1624+
if (objects.length > 1) {
1625+
this.showNote(`Ran script "${script.title}" on ${batch.length} objects in batch ${batchCount}/${totalBatchCount} with ${batchErrorCount} errors.`, batchErrorCount > 0);
1626+
}
1627+
}
1628+
1629+
if (objects.length > 1) {
1630+
this.showNote(`Ran script "${script.title}" on ${objects.length} objects in ${batchCount} batches with ${totalErrorCount} errors.`, totalErrorCount > 0);
15921631
}
15931632
this.refresh();
15941633
} catch (e) {

0 commit comments

Comments
 (0)