From ecdf28014569abde84a2529daebfe75ad9ff8033 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Thu, 17 Jul 2025 08:45:46 +0200 Subject: [PATCH 1/7] feat: prefetch info panel data --- README.md | 6 +- src/dashboard/Data/Browser/Browser.react.js | 2 + .../Data/Browser/DataBrowser.react.js | 105 +++++++++++++++++- 3 files changed, 110 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f93f49f368..5ded3b97be 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,8 @@ Parse Dashboard is continuously tested with the most recent releases of Node.js | `infoPanel[*].title` | String | no | - | `User Details` | The panel title. | | `infoPanel[*].classes` | Array<String> | no | - | `["_User"]` | The classes for which the info panel should be displayed. | | `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. | +| `infoPanel[*].prefetchObjects` | Number | yes | `0` | `2` | Number of rows to prefetch when browsing sequential rows. +| `infoPanel[*].prefetchStale` | Number | yes | `0` | `10` | Duration in seconds after which prefetched data is discarded. | `apps.scripts` | Array<Object> | yes | `[]` | `[{ ... }, { ... }]` | The scripts that can be executed for that app. | | `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. | | `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 { "title": "User Details", "classes": ["_User"], - "cloudCodeFunction": "getUserDetails" + "cloudCodeFunction": "getUserDetails", + "prefetchObjects": 2, + "prefetchStale": 10 } ] } diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index e0d5b4693d..88255ba0ac 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -461,6 +461,8 @@ class Browser extends DashboardView { title: panel.title, cloudCodeFunction: panel.cloudCodeFunction, classes: panel.classes, + prefetchObjects: panel.prefetchObjects || 0, + prefetchStale: panel.prefetchStale || 0, }); }); }); diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index 48e1f66836..ea262c9281 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -13,6 +13,7 @@ import * as ColumnPreferences from 'lib/ColumnPreferences'; import { dateStringUTC } from 'lib/DateUtils'; import getFileName from 'lib/getFileName'; import React from 'react'; +import Parse from 'parse'; import { ResizableBox } from 'react-resizable'; import styles from './Databrowser.scss'; @@ -106,6 +107,8 @@ export default class DataBrowser extends React.Component { showAggregatedData: true, frozenColumnIndex: -1, showRowNumber: storedRowNumber, + prefetchCache: {}, + selectionHistory: [], }; this.handleResizeDiv = this.handleResizeDiv.bind(this); @@ -122,6 +125,7 @@ export default class DataBrowser extends React.Component { this.setShowAggregatedData = this.setShowAggregatedData.bind(this); this.setCopyableValue = this.setCopyableValue.bind(this); this.setSelectedObjectId = this.setSelectedObjectId.bind(this); + this.handleCallCloudFunction = this.handleCallCloudFunction.bind(this); this.setContextMenu = this.setContextMenu.bind(this); this.freezeColumns = this.freezeColumns.bind(this); this.unfreezeColumns = this.unfreezeColumns.bind(this); @@ -149,6 +153,8 @@ export default class DataBrowser extends React.Component { firstSelectedCell: null, selectedData: [], frozenColumnIndex: -1, + prefetchCache: {}, + selectionHistory: [], }); } else if ( Object.keys(props.columns).length !== Object.keys(this.props.columns).length || @@ -606,7 +612,20 @@ export default class DataBrowser extends React.Component { setSelectedObjectId(selectedObjectId) { if (this.state.selectedObjectId !== selectedObjectId) { - this.setState({ selectedObjectId }); + const index = this.props.data?.findIndex(obj => obj.id === selectedObjectId); + this.setState( + prevState => { + const history = [...prevState.selectionHistory]; + if (index !== undefined && index > -1) { + history.push(index); + } + if (history.length > 3) { + history.shift(); + } + return { selectedObjectId, selectionHistory: history }; + }, + () => this.handlePrefetch() + ); } } @@ -627,6 +646,88 @@ export default class DataBrowser extends React.Component { window.localStorage?.setItem(BROWSER_SHOW_ROW_NUMBER, show); } + getPrefetchSettings() { + const config = + this.props.classwiseCloudFunctions?.[ + `${this.props.app.applicationId}${this.props.appName}` + ]?.[this.props.className]?.[0]; + return { + prefetchObjects: config?.prefetchObjects || 0, + prefetchStale: config?.prefetchStale || 0, + }; + } + + handlePrefetch() { + const { prefetchObjects } = this.getPrefetchSettings(); + if (!prefetchObjects) { + return; + } + const history = this.state.selectionHistory; + if (history.length < 3) { + return; + } + const [a, b, c] = history.slice(-3); + if (a + 1 === b && b + 1 === c) { + for ( + let i = 1; + i <= prefetchObjects && c + i < this.props.data.length; + i++ + ) { + const objId = this.props.data[c + i].id; + if (!this.state.prefetchCache[objId]) { + this.prefetchObject(objId); + } + } + } + } + + prefetchObject(objectId) { + const { className, app } = this.props; + const cloudCodeFunction = + this.props.classwiseCloudFunctions?.[ + `${app.applicationId}${this.props.appName}` + ]?.[className]?.[0]?.cloudCodeFunction; + if (!cloudCodeFunction) { + return; + } + const params = { + object: Parse.Object.extend(className) + .createWithoutData(objectId) + .toPointer(), + }; + const options = { useMasterKey: true }; + Parse.Cloud.run(cloudCodeFunction, params, options).then(result => { + this.setState(prev => ({ + prefetchCache: { + ...prev.prefetchCache, + [objectId]: { data: result, timestamp: Date.now() }, + }, + })); + }); + } + + handleCallCloudFunction(objectId, className, appId) { + const { prefetchCache } = this.state; + const { prefetchStale } = this.getPrefetchSettings(); + const cached = prefetchCache[objectId]; + if ( + cached && + (!prefetchStale || (Date.now() - cached.timestamp) / 1000 < prefetchStale) + ) { + this.props.setAggregationPanelData(cached.data); + this.props.setLoadingInfoPanel(false); + } else { + if (cached) { + this.setState(prev => { + const n = { ...prev.prefetchCache }; + delete n[objectId]; + return { prefetchCache: n }; + }); + } + this.props.callCloudFunction(objectId, className, appId); + } + } + handleColumnsOrder(order, shouldReload) { this.setState({ order: [...order] }, () => { this.updatePreferences(order, shouldReload); @@ -729,7 +830,7 @@ export default class DataBrowser extends React.Component { setCopyableValue={this.setCopyableValue} selectedObjectId={this.state.selectedObjectId} setSelectedObjectId={this.setSelectedObjectId} - callCloudFunction={this.props.callCloudFunction} + callCloudFunction={this.handleCallCloudFunction} setContextMenu={this.setContextMenu} freezeIndex={this.state.frozenColumnIndex} freezeColumns={this.freezeColumns} From 8c1c12b92a8e8abdf1e73543641fbbfbeec16baf Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Thu, 17 Jul 2025 11:39:39 +0200 Subject: [PATCH 2/7] fix: enable prefetching when navigating with arrow keys --- .../Data/Browser/DataBrowser.react.js | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index ea262c9281..c7d61aa340 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -275,7 +275,7 @@ export default class DataBrowser extends React.Component { if (this.props.errorAggregatedData != {}) { this.props.setErrorAggregatedData({}); } - this.props.callCloudFunction( + this.handleCallCloudFunction( this.state.selectedObjectId, this.props.className, this.props.app.applicationId @@ -478,19 +478,19 @@ export default class DataBrowser extends React.Component { // Up - standalone (move to the previous row) // or with ctrl/meta (excel style - move to the first row) let prevObjectID = this.state.selectedObjectId; + const newRow = e.ctrlKey || e.metaKey ? 0 : Math.max(this.state.current.row - 1, 0); this.setState({ current: { - row: e.ctrlKey || e.metaKey ? 0 : Math.max(this.state.current.row - 1, 0), + row: newRow, col: this.state.current.col, }, }); - this.setState({ - selectedObjectId: this.props.data[this.state.current.row].id, - showAggregatedData: true, - }); - if (prevObjectID !== this.state.selectedObjectId && this.state.isPanelVisible) { - this.props.callCloudFunction( - this.state.selectedObjectId, + const newObjectId = this.props.data[newRow].id; + this.setSelectedObjectId(newObjectId); + this.setState({ showAggregatedData: true }); + if (prevObjectID !== newObjectId && this.state.isPanelVisible) { + this.handleCallCloudFunction( + newObjectId, this.props.className, this.props.app.applicationId ); @@ -519,23 +519,23 @@ export default class DataBrowser extends React.Component { // Down - standalone (move to the next row) // or with ctrl/meta (excel style - move to the last row) prevObjectID = this.state.selectedObjectId; + const newRow = + e.ctrlKey || e.metaKey + ? this.props.data.length - 1 + : Math.min(this.state.current.row + 1, this.props.data.length - 1); this.setState({ current: { - row: - e.ctrlKey || e.metaKey - ? this.props.data.length - 1 - : Math.min(this.state.current.row + 1, this.props.data.length - 1), + row: newRow, col: this.state.current.col, }, }); - this.setState({ - selectedObjectId: this.props.data[this.state.current.row].id, - showAggregatedData: true, - }); - if (prevObjectID !== this.state.selectedObjectId && this.state.isPanelVisible) { - this.props.callCloudFunction( - this.state.selectedObjectId, + const newObjectIdDown = this.props.data[newRow].id; + this.setSelectedObjectId(newObjectIdDown); + this.setState({ showAggregatedData: true }); + if (prevObjectID !== newObjectIdDown && this.state.isPanelVisible) { + this.handleCallCloudFunction( + newObjectIdDown, this.props.className, this.props.app.applicationId ); From 13ab40dd122d9033ddaea6a8297fe1b7fa8fb7af Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Thu, 17 Jul 2025 11:45:44 +0200 Subject: [PATCH 3/7] fix key case scopes --- .../Data/Browser/DataBrowser.react.js | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index c7d61aa340..43196dcc78 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -12,8 +12,8 @@ import BrowserToolbar from 'dashboard/Data/Browser/BrowserToolbar.react'; import * as ColumnPreferences from 'lib/ColumnPreferences'; import { dateStringUTC } from 'lib/DateUtils'; import getFileName from 'lib/getFileName'; -import React from 'react'; import Parse from 'parse'; +import React from 'react'; import { ResizableBox } from 'react-resizable'; import styles from './Databrowser.scss'; @@ -446,7 +446,7 @@ export default class DataBrowser extends React.Component { switch (e.keyCode) { case 8: - case 46: + case 46: { // Backspace or Delete const colName = this.state.order[this.state.current.col].name; const col = this.props.columns[colName]; @@ -455,7 +455,8 @@ export default class DataBrowser extends React.Component { } e.preventDefault(); break; - case 37: + } + case 37: { // Left - standalone (move to the next visible column on the left) // or with ctrl/meta (excel style - move to the first visible column) @@ -474,10 +475,11 @@ export default class DataBrowser extends React.Component { }); e.preventDefault(); break; - case 38: + } + case 38: { // Up - standalone (move to the previous row) // or with ctrl/meta (excel style - move to the first row) - let prevObjectID = this.state.selectedObjectId; + const prevObjectID = this.state.selectedObjectId; const newRow = e.ctrlKey || e.metaKey ? 0 : Math.max(this.state.current.row - 1, 0); this.setState({ current: { @@ -497,7 +499,8 @@ export default class DataBrowser extends React.Component { } e.preventDefault(); break; - case 39: + } + case 39: { // Right - standalone (move to the next visible column on the right) // or with ctrl/meta (excel style - move to the last visible column) this.setState({ @@ -515,10 +518,11 @@ export default class DataBrowser extends React.Component { }); e.preventDefault(); break; - case 40: + } + case 40: { // Down - standalone (move to the next row) // or with ctrl/meta (excel style - move to the last row) - prevObjectID = this.state.selectedObjectId; + const prevObjectID = this.state.selectedObjectId; const newRow = e.ctrlKey || e.metaKey ? this.props.data.length - 1 @@ -543,7 +547,8 @@ export default class DataBrowser extends React.Component { e.preventDefault(); break; - case 67: // C + } + case 67: { // C if ((e.ctrlKey || e.metaKey) && this.state.copyableValue !== undefined) { copy(this.state.copyableValue); // Copies current cell value to clipboard if (this.props.showNote) { @@ -552,7 +557,8 @@ export default class DataBrowser extends React.Component { e.preventDefault(); } break; - case 32: // Space + } + case 32: { // Space // Only handle space if not editing and there's a current row selected if (!this.state.editing && this.state.current?.row >= 0) { const rowId = this.props.data[this.state.current.row].id; @@ -561,12 +567,14 @@ export default class DataBrowser extends React.Component { e.preventDefault(); } break; - case 13: // Enter (enable editing) + } + case 13: { // Enter (enable editing) if (!this.state.editing && this.state.current) { this.setEditing(true); e.preventDefault(); } break; + } } } From d9adee5c5dc634b7c66c60ea135236859d72fcc4 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Thu, 17 Jul 2025 13:35:36 +0200 Subject: [PATCH 4/7] fix: Prefetch cache grows indefinitely (#2916) --- .../Data/Browser/DataBrowser.react.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index 43196dcc78..a1dbeedf6d 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -666,10 +666,24 @@ export default class DataBrowser extends React.Component { } handlePrefetch() { - const { prefetchObjects } = this.getPrefetchSettings(); + const { prefetchObjects, prefetchStale } = this.getPrefetchSettings(); if (!prefetchObjects) { return; } + + const cache = { ...this.state.prefetchCache }; + if (prefetchStale) { + const now = Date.now(); + Object.keys(cache).forEach(key => { + if ((now - cache[key].timestamp) / 1000 >= prefetchStale) { + delete cache[key]; + } + }); + } + if (Object.keys(cache).length !== Object.keys(this.state.prefetchCache).length) { + this.setState({ prefetchCache: cache }); + } + const history = this.state.selectionHistory; if (history.length < 3) { return; @@ -682,7 +696,7 @@ export default class DataBrowser extends React.Component { i++ ) { const objId = this.props.data[c + i].id; - if (!this.state.prefetchCache[objId]) { + if (!cache[objId]) { this.prefetchObject(objId); } } From 4067c5b84f4317fe9a9aba5a502841dfcdb50f3a Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Thu, 17 Jul 2025 14:02:15 +0200 Subject: [PATCH 5/7] Update README.md --- README.md | 55 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 5ded3b97be..0a9d3aef4f 100644 --- a/README.md +++ b/README.md @@ -61,15 +61,17 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https - [Data Browser](#data-browser) - [Filters](#filters) - [Info Panel](#info-panel) - - [Segments](#segments) - - [Text Item](#text-item) - - [Key-Value Item](#key-value-item) - - [Table Item](#table-item) - - [Image Item](#image-item) - - [Video Item](#video-item) - - [Audio Item](#audio-item) - - [Button Item](#button-item) - - [Panel Item](#panel-item) + - [Response](#response) + - [Segments](#segments) + - [Text Item](#text-item) + - [Key-Value Item](#key-value-item) + - [Table Item](#table-item) + - [Image Item](#image-item) + - [Video Item](#video-item) + - [Audio Item](#audio-item) + - [Button Item](#button-item) + - [Panel Item](#panel-item) + - [Prefetching](#prefetching) - [Freeze Columns](#freeze-columns) - [Browse as User](#browse-as-user) - [Change Pointer Key](#change-pointer-key) @@ -140,8 +142,8 @@ Parse Dashboard is continuously tested with the most recent releases of Node.js | `infoPanel[*].title` | String | no | - | `User Details` | The panel title. | | `infoPanel[*].classes` | Array<String> | no | - | `["_User"]` | The classes for which the info panel should be displayed. | | `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. | -| `infoPanel[*].prefetchObjects` | Number | yes | `0` | `2` | Number of rows to prefetch when browsing sequential rows. -| `infoPanel[*].prefetchStale` | Number | yes | `0` | `10` | Duration in seconds after which prefetched data is discarded. +| `infoPanel[*].prefetchObjects` | Number | yes | `0` | `2` | Number of next rows to prefetch when browsing sequential rows. For example, `2` means the next 2 rows will be fetched in advance. | +| `infoPanel[*].prefetchStale` | Number | yes | `0` | `10` | Duration in seconds after which prefetched data is discarded as stale. | | `apps.scripts` | Array<Object> | yes | `[]` | `[{ ... }, { ... }]` | The scripts that can be executed for that app. | | `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. | | `apps.scripts.classes` | Array<String> | no | - | `['_User']` | The classes of Parse Objects for which the scripts can be executed. | @@ -886,7 +888,9 @@ The following example dashboard configuration shows an info panel for the `_User The Cloud Code Function receives the selected object in the payload and returns a response that can include various items. -#### Segments +#### Response + +##### Segments The info panel can contain multiple segments to display different groups of information. @@ -922,7 +926,7 @@ Example: The items array can include various types of content such as text, key-value pairs, tables, images, videos, audios, and buttons. Each type offers a different way to display information within the info panel, allowing for a customizable and rich user experience. Below is a detailed explanation of each type. -#### Text Item +##### Text Item A simple text field. @@ -942,7 +946,7 @@ Example: } ``` -#### Key-Value Item +##### Key-Value Item A text item that consists of a key and a value. The value can optionally be linked to a URL. @@ -1013,7 +1017,7 @@ const item = { } ``` -#### Table Item +##### Table Item A table with columns and rows to display data in a structured format. @@ -1055,7 +1059,7 @@ Example: } ``` -#### Image Item +##### Image Item An image to be displayed in the panel. @@ -1075,7 +1079,7 @@ Example: } ``` -#### Video Item +##### Video Item A video to be displayed in the panel. @@ -1095,7 +1099,7 @@ Example: } ``` -#### Audio Item +##### Audio Item An audio file to be played in the panel. @@ -1115,7 +1119,7 @@ Example: } ``` -#### Button Item +##### Button Item A button that triggers an action when clicked. @@ -1150,7 +1154,7 @@ Example: } ``` -#### Panel Item +##### Panel Item A sub-panel whose data is loaded on-demand by expanding the item. @@ -1172,6 +1176,17 @@ Example: } ``` +#### Prefetching + +To reduce the time for info panel data to appear, data can be prefetched. + +| Parameter | Type | Optional | Default | Example | Description | +|--------------------------------|--------|----------|---------|---------|-----------------------------------------------------------------------------------------------------------------------------------| +| `infoPanel[*].prefetchObjects` | Number | yes | `0` | `2` | Number of next rows to prefetch when browsing sequential rows. For example, `2` means the next 2 rows will be fetched in advance. | +| `infoPanel[*].prefetchStale` | Number | yes | `0` | `10` | Duration in seconds after which prefetched data is discarded as stale. | + +Prefetching is particularly useful when navigating through lists of objects. To optimize performance and avoid unnecessary data loading, prefetching is triggered only after the user has moved through 3 consecutive rows using the keyboard down-arrow key. + ### Freeze Columns ▶️ *Core > Browser > Freeze column* From 0ebd007c5159745fce6d270476f823155e4dc9f1 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Thu, 17 Jul 2025 14:04:12 +0200 Subject: [PATCH 6/7] add prefetch error handling --- src/dashboard/Data/Browser/DataBrowser.react.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index a1dbeedf6d..c6b1794301 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -725,6 +725,8 @@ export default class DataBrowser extends React.Component { [objectId]: { data: result, timestamp: Date.now() }, }, })); + }).catch(error => { + console.error(`Failed to prefetch object ${objectId}:`, error); }); } From 4c382b37ccea295dd75ae3837d5fd6442a2e13f9 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Thu, 17 Jul 2025 14:14:03 +0200 Subject: [PATCH 7/7] fix: Info panel reloads despite prefetched data on cell click (#2918) --- README.md | 2 +- .../BrowserCell/BrowserCell.react.js | 18 +----------------- .../Data/Browser/DataBrowser.react.js | 18 +++++++++++++++++- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 0a9d3aef4f..080e3c00cd 100644 --- a/README.md +++ b/README.md @@ -1185,7 +1185,7 @@ To reduce the time for info panel data to appear, data can be prefetched. | `infoPanel[*].prefetchObjects` | Number | yes | `0` | `2` | Number of next rows to prefetch when browsing sequential rows. For example, `2` means the next 2 rows will be fetched in advance. | | `infoPanel[*].prefetchStale` | Number | yes | `0` | `10` | Duration in seconds after which prefetched data is discarded as stale. | -Prefetching is particularly useful when navigating through lists of objects. To optimize performance and avoid unnecessary data loading, prefetching is triggered only after the user has moved through 3 consecutive rows using the keyboard down-arrow key. +Prefetching is particularly useful when navigating through lists of objects. To optimize performance and avoid unnecessary data loading, prefetching is triggered only after the user has moved through 3 consecutive rows using the keyboard down-arrow key or by mouse click. ### Freeze Columns diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index a020879efc..ff054ed9cb 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -567,10 +567,6 @@ export default class BrowserCell extends Component { current, onEditChange, setCopyableValue, - selectedObjectId, - setSelectedObjectId, - callCloudFunction, - isPanelVisible, onPointerCmdClick, row, col, @@ -580,7 +576,6 @@ export default class BrowserCell extends Component { markRequiredFieldRow, handleCellClick, selectedCells, - setShowAggregatedData, } = this.props; const classes = [...this.state.classes]; @@ -653,18 +648,7 @@ export default class BrowserCell extends Component { onPointerCmdClick(value); } else { setCopyableValue(hidden ? undefined : this.copyableValue); - if (selectedObjectId !== this.props.objectId) { - setShowAggregatedData(true); - setSelectedObjectId(this.props.objectId); - if ( - this.props.objectId && - isPanelVisible && - ((e.shiftKey && !this.props.firstSelectedCell) || !e.shiftKey) - ) { - callCloudFunction(this.props.objectId, this.props.className, this.props.appId); - } - } - handleCellClick(e, row, col); + handleCellClick(e, row, col, this.props.objectId); } }} onDoubleClick={() => { diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index c6b1794301..5fda24e448 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -758,10 +758,26 @@ export default class DataBrowser extends React.Component { }); } - handleCellClick(event, row, col) { + handleCellClick(event, row, col, objectId) { const { firstSelectedCell } = this.state; const clickedCellKey = `${row}-${col}`; + if (this.state.selectedObjectId !== objectId) { + this.setShowAggregatedData(true); + this.setSelectedObjectId(objectId); + if ( + objectId && + this.state.isPanelVisible && + ((event.shiftKey && !firstSelectedCell) || !event.shiftKey) + ) { + this.handleCallCloudFunction( + objectId, + this.props.className, + this.props.app.applicationId + ); + } + } + if (event.shiftKey && firstSelectedCell) { const [firstRow, firstCol] = firstSelectedCell.split('-').map(Number); const [lastRow, lastCol] = clickedCellKey.split('-').map(Number);