Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/components/BrowserCell/BrowserCell.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}

Expand Down
2 changes: 0 additions & 2 deletions src/components/BrowserMenu/BrowserMenu.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/components/BrowserRow/BrowserRow.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
51 changes: 47 additions & 4 deletions src/dashboard/Data/Browser/Browser.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ class Browser extends DashboardView {

processedScripts: 0,

reloadDataTableAfterScript: window.localStorage?.getItem('reloadDataTableAfterScript') === 'true',

rowCheckboxDragging: false,
draggedRowSelection: false,

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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}`);
Expand Down Expand Up @@ -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}
Expand Down
4 changes: 4 additions & 0 deletions src/dashboard/Data/Browser/BrowserTable.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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}
Expand Down
21 changes: 21 additions & 0 deletions src/dashboard/Data/Browser/BrowserToolbar.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ const BrowserToolbar = ({
toggleGraphPanel,
isGraphPanelVisible,
runScriptShortcut,
reloadDataTableAfterScript,
toggleReloadDataTableAfterScript,
}) => {
const selectionLength = Object.keys(selection).length;
const isPendingEditCloneRows = editCloneRows && editCloneRows.length > 0;
Expand Down Expand Up @@ -626,6 +628,25 @@ const BrowserToolbar = ({
onClick={() => onExecuteScriptRows(selection)}
shortcut={runScriptShortcut}
/>
<Separator />
<MenuItem
disableMouseDown={true}
text={
<span>
{reloadDataTableAfterScript && (
<Icon
name="check"
width={12}
height={12}
fill="#ffffffff"
className="menuCheck"
/>
)}
Reload all rows after run
</span>
}
onClick={() => toggleReloadDataTableAfterScript()}
/>
</BrowserMenu>
<div className={styles.toolbarSeparator} />
{menu}
Expand Down
95 changes: 69 additions & 26 deletions src/dashboard/Data/Browser/DataBrowser.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -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
);
}
},
Expand Down Expand Up @@ -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 && (
<ResizableBox
Expand Down Expand Up @@ -2910,7 +2952,8 @@ export default class DataBrowser extends React.Component {
this.state.selectedScript.className,
this.state.selectedScript.objectId,
this.props.showNote,
this.props.onRefresh
this.props.reloadDataTableAfterScript ? this.props.onRefresh : null,
this.props.reloadDataTableAfterScript ? null : this.handleRefreshObjects
);
this.setState({ showScriptConfirmationDialog: false, selectedScript: null });
}}
Expand Down
11 changes: 8 additions & 3 deletions src/lib/ScriptUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ export function getValidScripts(scripts, className, field) {
* @param {string} className - The Parse class name
* @param {string} objectId - The object ID
* @param {Function} showNote - Callback to show notification
* @param {Function} onRefresh - Callback to refresh data
* @param {Function} onRefresh - Callback to refresh all data
* @param {Function} onRefreshObjects - Callback to refresh specific objects by IDs
*/
export async function executeScript(script, className, objectId, showNote, onRefresh) {
export async function executeScript(script, className, objectId, showNote, onRefresh, onRefreshObjects) {
try {
const object = Parse.Object.extend(className).createWithoutData(objectId);
const response = await Parse.Cloud.run(
Expand All @@ -71,7 +72,11 @@ export async function executeScript(script, className, objectId, showNote, onRef
showNote?.(
response || `Ran script "${script.title}" on "${className}" object "${object.id}".`
);
onRefresh?.();
if (onRefreshObjects) {
onRefreshObjects([objectId]);
} else {
onRefresh?.();
}
} catch (e) {
showNote?.(e.message, true);
console.error(`Could not run ${script.title}:`, e);
Expand Down
Loading