Skip to content

Commit ecdf280

Browse files
committed
feat: prefetch info panel data
1 parent 9ea7e2e commit ecdf280

File tree

3 files changed

+110
-3
lines changed

3 files changed

+110
-3
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ Parse Dashboard is continuously tested with the most recent releases of Node.js
140140
| `infoPanel[*].title` | String | no | - | `User Details` | The panel title. |
141141
| `infoPanel[*].classes` | Array<String> | no | - | `["_User"]` | The classes for which the info panel should be displayed. |
142142
| `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. |
143+
| `infoPanel[*].prefetchObjects` | Number | yes | `0` | `2` | Number of rows to prefetch when browsing sequential rows.
144+
| `infoPanel[*].prefetchStale` | Number | yes | `0` | `10` | Duration in seconds after which prefetched data is discarded.
143145
| `apps.scripts` | Array<Object> | yes | `[]` | `[{ ... }, { ... }]` | The scripts that can be executed for that app. |
144146
| `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. |
145147
| `apps.scripts.classes` | Array<String> | no | - | `['_User']` | The classes of Parse Objects for which the scripts can be executed. |
@@ -873,7 +875,9 @@ The following example dashboard configuration shows an info panel for the `_User
873875
{
874876
"title": "User Details",
875877
"classes": ["_User"],
876-
"cloudCodeFunction": "getUserDetails"
878+
"cloudCodeFunction": "getUserDetails",
879+
"prefetchObjects": 2,
880+
"prefetchStale": 10
877881
}
878882
]
879883
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,8 @@ class Browser extends DashboardView {
461461
title: panel.title,
462462
cloudCodeFunction: panel.cloudCodeFunction,
463463
classes: panel.classes,
464+
prefetchObjects: panel.prefetchObjects || 0,
465+
prefetchStale: panel.prefetchStale || 0,
464466
});
465467
});
466468
});

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

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import * as ColumnPreferences from 'lib/ColumnPreferences';
1313
import { dateStringUTC } from 'lib/DateUtils';
1414
import getFileName from 'lib/getFileName';
1515
import React from 'react';
16+
import Parse from 'parse';
1617
import { ResizableBox } from 'react-resizable';
1718
import styles from './Databrowser.scss';
1819

@@ -106,6 +107,8 @@ export default class DataBrowser extends React.Component {
106107
showAggregatedData: true,
107108
frozenColumnIndex: -1,
108109
showRowNumber: storedRowNumber,
110+
prefetchCache: {},
111+
selectionHistory: [],
109112
};
110113

111114
this.handleResizeDiv = this.handleResizeDiv.bind(this);
@@ -122,6 +125,7 @@ export default class DataBrowser extends React.Component {
122125
this.setShowAggregatedData = this.setShowAggregatedData.bind(this);
123126
this.setCopyableValue = this.setCopyableValue.bind(this);
124127
this.setSelectedObjectId = this.setSelectedObjectId.bind(this);
128+
this.handleCallCloudFunction = this.handleCallCloudFunction.bind(this);
125129
this.setContextMenu = this.setContextMenu.bind(this);
126130
this.freezeColumns = this.freezeColumns.bind(this);
127131
this.unfreezeColumns = this.unfreezeColumns.bind(this);
@@ -149,6 +153,8 @@ export default class DataBrowser extends React.Component {
149153
firstSelectedCell: null,
150154
selectedData: [],
151155
frozenColumnIndex: -1,
156+
prefetchCache: {},
157+
selectionHistory: [],
152158
});
153159
} else if (
154160
Object.keys(props.columns).length !== Object.keys(this.props.columns).length ||
@@ -606,7 +612,20 @@ export default class DataBrowser extends React.Component {
606612

607613
setSelectedObjectId(selectedObjectId) {
608614
if (this.state.selectedObjectId !== selectedObjectId) {
609-
this.setState({ selectedObjectId });
615+
const index = this.props.data?.findIndex(obj => obj.id === selectedObjectId);
616+
this.setState(
617+
prevState => {
618+
const history = [...prevState.selectionHistory];
619+
if (index !== undefined && index > -1) {
620+
history.push(index);
621+
}
622+
if (history.length > 3) {
623+
history.shift();
624+
}
625+
return { selectedObjectId, selectionHistory: history };
626+
},
627+
() => this.handlePrefetch()
628+
);
610629
}
611630
}
612631

@@ -627,6 +646,88 @@ export default class DataBrowser extends React.Component {
627646
window.localStorage?.setItem(BROWSER_SHOW_ROW_NUMBER, show);
628647
}
629648

649+
getPrefetchSettings() {
650+
const config =
651+
this.props.classwiseCloudFunctions?.[
652+
`${this.props.app.applicationId}${this.props.appName}`
653+
]?.[this.props.className]?.[0];
654+
return {
655+
prefetchObjects: config?.prefetchObjects || 0,
656+
prefetchStale: config?.prefetchStale || 0,
657+
};
658+
}
659+
660+
handlePrefetch() {
661+
const { prefetchObjects } = this.getPrefetchSettings();
662+
if (!prefetchObjects) {
663+
return;
664+
}
665+
const history = this.state.selectionHistory;
666+
if (history.length < 3) {
667+
return;
668+
}
669+
const [a, b, c] = history.slice(-3);
670+
if (a + 1 === b && b + 1 === c) {
671+
for (
672+
let i = 1;
673+
i <= prefetchObjects && c + i < this.props.data.length;
674+
i++
675+
) {
676+
const objId = this.props.data[c + i].id;
677+
if (!this.state.prefetchCache[objId]) {
678+
this.prefetchObject(objId);
679+
}
680+
}
681+
}
682+
}
683+
684+
prefetchObject(objectId) {
685+
const { className, app } = this.props;
686+
const cloudCodeFunction =
687+
this.props.classwiseCloudFunctions?.[
688+
`${app.applicationId}${this.props.appName}`
689+
]?.[className]?.[0]?.cloudCodeFunction;
690+
if (!cloudCodeFunction) {
691+
return;
692+
}
693+
const params = {
694+
object: Parse.Object.extend(className)
695+
.createWithoutData(objectId)
696+
.toPointer(),
697+
};
698+
const options = { useMasterKey: true };
699+
Parse.Cloud.run(cloudCodeFunction, params, options).then(result => {
700+
this.setState(prev => ({
701+
prefetchCache: {
702+
...prev.prefetchCache,
703+
[objectId]: { data: result, timestamp: Date.now() },
704+
},
705+
}));
706+
});
707+
}
708+
709+
handleCallCloudFunction(objectId, className, appId) {
710+
const { prefetchCache } = this.state;
711+
const { prefetchStale } = this.getPrefetchSettings();
712+
const cached = prefetchCache[objectId];
713+
if (
714+
cached &&
715+
(!prefetchStale || (Date.now() - cached.timestamp) / 1000 < prefetchStale)
716+
) {
717+
this.props.setAggregationPanelData(cached.data);
718+
this.props.setLoadingInfoPanel(false);
719+
} else {
720+
if (cached) {
721+
this.setState(prev => {
722+
const n = { ...prev.prefetchCache };
723+
delete n[objectId];
724+
return { prefetchCache: n };
725+
});
726+
}
727+
this.props.callCloudFunction(objectId, className, appId);
728+
}
729+
}
730+
630731
handleColumnsOrder(order, shouldReload) {
631732
this.setState({ order: [...order] }, () => {
632733
this.updatePreferences(order, shouldReload);
@@ -729,7 +830,7 @@ export default class DataBrowser extends React.Component {
729830
setCopyableValue={this.setCopyableValue}
730831
selectedObjectId={this.state.selectedObjectId}
731832
setSelectedObjectId={this.setSelectedObjectId}
732-
callCloudFunction={this.props.callCloudFunction}
833+
callCloudFunction={this.handleCallCloudFunction}
733834
setContextMenu={this.setContextMenu}
734835
freezeIndex={this.state.frozenColumnIndex}
735836
freezeColumns={this.freezeColumns}

0 commit comments

Comments
 (0)