diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index 9a05c4c48c..19c5020082 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -400,7 +400,8 @@ export default class BrowserCell extends Component { this.props.className, this.props.objectId, this.props.showNote, - this.props.onRefresh + this.props.reloadDataTableAfterScript ? this.props.onRefresh : null, + this.props.reloadDataTableAfterScript ? null : this.props.onRefreshObjects ); } diff --git a/src/components/BrowserMenu/BrowserMenu.react.js b/src/components/BrowserMenu/BrowserMenu.react.js index 5d97a9dd26..30ebd54cca 100644 --- a/src/components/BrowserMenu/BrowserMenu.react.js +++ b/src/components/BrowserMenu/BrowserMenu.react.js @@ -145,14 +145,12 @@ export default class BrowserMenu extends React.Component { const estimatedSubmenuWidth = 150; // Estimate for edge detection const openToLeft = parentRect.right + estimatedSubmenuWidth > window.innerWidth; this.setState({ open: true, openToLeft }); - this.props.setCurrent?.(null); // Notify parent that this submenu is now open (to close sibling submenus) this.props.onSubmenuOpen?.(this.props.childKey); }; } else { entryEvents.onClick = () => { this.setState({ open: true, openToLeft: false }); - this.props.setCurrent(null); }; } } diff --git a/src/components/BrowserRow/BrowserRow.react.js b/src/components/BrowserRow/BrowserRow.react.js index 5e443389f1..0ef142efde 100644 --- a/src/components/BrowserRow/BrowserRow.react.js +++ b/src/components/BrowserRow/BrowserRow.react.js @@ -185,6 +185,8 @@ export default class BrowserRow extends Component { onEditSelectedRow={onEditSelectedRow} showNote={this.props.showNote} onRefresh={this.props.onRefresh} + onRefreshObjects={this.props.onRefreshObjects} + reloadDataTableAfterScript={this.props.reloadDataTableAfterScript} scripts={this.props.scripts} handleCellClick={this.props.handleCellClick} selectedCells={this.props.selectedCells} diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index 505478f3ee..0daab4e259 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -185,6 +185,8 @@ class Browser extends DashboardView { processedScripts: 0, + reloadDataTableAfterScript: window.localStorage?.getItem('reloadDataTableAfterScript') === 'true', + rowCheckboxDragging: false, draggedRowSelection: false, @@ -235,6 +237,8 @@ class Browser extends DashboardView { this.showExecuteScriptRowsDialog = this.showExecuteScriptRowsDialog.bind(this); this.confirmExecuteScriptRows = this.confirmExecuteScriptRows.bind(this); this.cancelExecuteScriptRowsDialog = this.cancelExecuteScriptRowsDialog.bind(this); + this.refreshObjects = this.refreshObjects.bind(this); + this.toggleReloadDataTableAfterScript = this.toggleReloadDataTableAfterScript.bind(this); this.confirmAttachSelectedRows = this.confirmAttachSelectedRows.bind(this); this.cancelAttachSelectedRows = this.cancelAttachSelectedRows.bind(this); this.showCloneSelectedRowsDialog = this.showCloneSelectedRowsDialog.bind(this); @@ -1229,6 +1233,34 @@ class Browser extends DashboardView { return true; } + async refreshObjects(objectIds) { + const { useMasterKey } = this.state; + const className = this.props.params.className; + const query = new Parse.Query(className); + query.containedIn('objectId', objectIds); + this.excludeFields(query, className); + try { + const freshObjects = await query.find({ useMasterKey }); + const freshMap = {}; + freshObjects.forEach(obj => { + freshMap[obj.id] = obj; + }); + this.setState(prevState => ({ + data: prevState.data.map(obj => freshMap[obj.id] || obj), + })); + } catch (e) { + this.showNote(e.message, true); + } + } + + toggleReloadDataTableAfterScript() { + this.setState(prevState => { + const newValue = !prevState.reloadDataTableAfterScript; + window.localStorage?.setItem('reloadDataTableAfterScript', newValue); + return { reloadDataTableAfterScript: newValue }; + }); + } + async fetchParseData(source, filters) { if (this.currentQuery) { this.currentQuery.cancel(); @@ -2196,10 +2228,18 @@ class Browser extends DashboardView { totalErrorCount > 0 ); } - this.setState( - { selection: {}, showExecuteScriptRowsDialog: false }, - () => this.refresh() - ); + const objectIds = objects.map(obj => obj.id); + if (this.state.reloadDataTableAfterScript) { + this.setState( + { selection: {}, showExecuteScriptRowsDialog: false }, + () => this.refresh() + ); + } else { + this.setState( + { selection: {}, showExecuteScriptRowsDialog: false }, + () => this.dataBrowserRef.current?.handleRefreshObjects(objectIds) + ); + } } catch (e) { this.showNote(e.message, true); console.log(`Could not run ${script.title}: ${e}`); @@ -2815,6 +2855,9 @@ class Browser extends DashboardView { onExport={this.showExport} onChangeCLP={this.handleCLPChange} onRefresh={this.refresh} + onRefreshObjects={this.refreshObjects} + reloadDataTableAfterScript={this.state.reloadDataTableAfterScript} + toggleReloadDataTableAfterScript={this.toggleReloadDataTableAfterScript} onAttachRows={this.showAttachRowsDialog} onAttachSelectedRows={this.showAttachSelectedRowsDialog} onExecuteScriptRows={this.showExecuteScriptRowsDialog} diff --git a/src/dashboard/Data/Browser/BrowserTable.react.js b/src/dashboard/Data/Browser/BrowserTable.react.js index 79a509a2c8..548505ff52 100644 --- a/src/dashboard/Data/Browser/BrowserTable.react.js +++ b/src/dashboard/Data/Browser/BrowserTable.react.js @@ -198,6 +198,8 @@ export default class BrowserTable extends React.Component { markRequiredFieldRow={this.props.markRequiredFieldRow} showNote={this.props.showNote} onRefresh={this.props.onRefresh} + onRefreshObjects={this.props.onRefreshObjects} + reloadDataTableAfterScript={this.props.reloadDataTableAfterScript} scripts={this.context.scripts} selectedCells={this.props.selectedCells} handleCellClick={this.props.handleCellClick} @@ -383,6 +385,8 @@ export default class BrowserTable extends React.Component { onEditSelectedRow={this.props.onEditSelectedRow} showNote={this.props.showNote} onRefresh={this.props.onRefresh} + onRefreshObjects={this.props.onRefreshObjects} + reloadDataTableAfterScript={this.props.reloadDataTableAfterScript} scripts={this.context.scripts} selectedCells={this.props.selectedCells} handleCellClick={this.props.handleCellClick} diff --git a/src/dashboard/Data/Browser/BrowserToolbar.react.js b/src/dashboard/Data/Browser/BrowserToolbar.react.js index 4513fb04ee..5498338b8c 100644 --- a/src/dashboard/Data/Browser/BrowserToolbar.react.js +++ b/src/dashboard/Data/Browser/BrowserToolbar.react.js @@ -105,6 +105,8 @@ const BrowserToolbar = ({ toggleGraphPanel, isGraphPanelVisible, runScriptShortcut, + reloadDataTableAfterScript, + toggleReloadDataTableAfterScript, }) => { const selectionLength = Object.keys(selection).length; const isPendingEditCloneRows = editCloneRows && editCloneRows.length > 0; @@ -626,6 +628,25 @@ const BrowserToolbar = ({ onClick={() => onExecuteScriptRows(selection)} shortcut={runScriptShortcut} /> + + + {reloadDataTableAfterScript && ( + + )} + Reload all rows after run + + } + onClick={() => toggleReloadDataTableAfterScript()} + />
{menu} diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index d347873879..a6ad9b418b 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -201,6 +201,9 @@ export default class DataBrowser extends React.Component { optionKeyPressed: false, // Whether the Option/Alt key is currently pressed (pauses auto-scroll) }; + // Flag to skip panel clearing in componentDidUpdate during selective object refresh + this._skipPanelClear = false; + this.handleResizeDiv = this.handleResizeDiv.bind(this); this.handleResizeStart = this.handleResizeStart.bind(this); this.handleResizeStop = this.handleResizeStop.bind(this); @@ -209,6 +212,7 @@ export default class DataBrowser extends React.Component { this.handleHeaderDragDrop = this.handleHeaderDragDrop.bind(this); this.handleResize = this.handleResize.bind(this); this.handleRefresh = this.handleRefresh.bind(this); + this.handleRefreshObjects = this.handleRefreshObjects.bind(this); this.togglePanelVisibility = this.togglePanelVisibility.bind(this); this.setCurrent = this.setCurrent.bind(this); this.setEditing = this.setEditing.bind(this); @@ -459,33 +463,25 @@ export default class DataBrowser extends React.Component { ); if (shouldClearPanels) { - // Clear panel data and selection to show "No object selected" - this.props.setAggregationPanelData({}); - this.props.setLoadingInfoPanel(false); - this.setState({ - selectedObjectId: undefined, - showAggregatedData: true, // Keep true to show "No object selected" message - multiPanelData: {}, - displayedObjectIds: [], - prefetchCache: {}, // Clear cache to prevent memory leak - }); - } - - if ( - this.state.current === null && - this.state.selectedObjectId !== undefined && - prevState.selectedObjectId !== undefined - ) { - this.setState({ - selectedObjectId: undefined, - showAggregatedData: true, // Keep true to show "No object selected" message - }); - this.props.setAggregationPanelData({}); - if (this.props.errorAggregatedData != {}) { - this.props.setErrorAggregatedData({}); + if (this._skipPanelClear) { + this._skipPanelClear = false; + } else { + // Clear panel data and selection to show "No object selected" + this.props.setAggregationPanelData({}); + this.props.setLoadingInfoPanel(false); + this.setState({ + selectedObjectId: undefined, + showAggregatedData: true, // Keep true to show "No object selected" message + multiPanelData: {}, + displayedObjectIds: [], + prefetchCache: {}, // Clear cache to prevent memory leak + }); } } + // Note: We intentionally do NOT clear selectedObjectId when current becomes null. + // Clicking toolbar menus sets current=null, but the info panel should persist. + if (this.state.current && this.state.current !== prevState.current) { if (this.state.current.col !== this.state.lastSelectedCol) { this.setState({ lastSelectedCol: this.state.current.col }); @@ -676,6 +672,50 @@ export default class DataBrowser extends React.Component { await this.props.onRefresh(); } + async handleRefreshObjects(objectIds) { + // Clear prefetch cache for the affected objects + if (this.state.isPanelVisible) { + const newPrefetchCache = { ...this.state.prefetchCache }; + objectIds.forEach(id => { + delete newPrefetchCache[id]; + }); + + // Clear multi-panel data for affected objects + const newMultiPanelData = { ...this.state.multiPanelData }; + objectIds.forEach(id => { + delete newMultiPanelData[id]; + }); + + this.setState({ prefetchCache: newPrefetchCache, multiPanelData: newMultiPanelData }); + + // Re-fetch info panel data for affected objects that are currently displayed + const appId = this.props.app.applicationId; + const className = this.props.className; + + if (this.state.selectedObjectId && objectIds.includes(this.state.selectedObjectId)) { + this.props.callCloudFunction(this.state.selectedObjectId, className, appId); + } + + if (this.state.panelCount > 1) { + this.state.displayedObjectIds.forEach(displayedId => { + if (objectIds.includes(displayedId)) { + this.fetchDataForMultiPanel(displayedId); + } + }); + } + } + + // Set flag to prevent componentDidUpdate from clearing panels when data prop changes + this._skipPanelClear = true; + + // Refresh the table data for just these objects + try { + await this.props.onRefreshObjects(objectIds); + } finally { + this._skipPanelClear = false; + } + } + togglePanelVisibility() { const newVisibility = !this.state.isPanelVisible; this.setState({ isPanelVisible: newVisibility }); @@ -1301,7 +1341,8 @@ export default class DataBrowser extends React.Component { className, objectId, this.props.showNote, - this.props.onRefresh + this.props.reloadDataTableAfterScript ? this.props.onRefresh : null, + this.props.reloadDataTableAfterScript ? null : this.handleRefreshObjects ); } }, @@ -2658,6 +2699,7 @@ export default class DataBrowser extends React.Component { isGraphPanelVisible={this.state.isGraphPanelVisible && !!this.state.graphConfig} graphPanelWidth={this.state.graphPanelWidth} {...other} + onRefreshObjects={this.handleRefreshObjects} /> {this.state.isPanelVisible && (