From acb4398013cc7e36972e5e4920eba9e44092ef85 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Fri, 23 Jan 2026 02:52:59 +0100
Subject: [PATCH] fix
---
.../BrowserCell/BrowserCell.react.js | 54 +-------------
.../StringEditor/StringEditor.react.js | 41 ++++++++---
.../Data/Browser/BrowserTable.react.js | 1 +
.../Data/Browser/DataBrowser.react.js | 49 +++++++++----
src/dashboard/Data/Browser/Editor.react.js | 8 +-
src/lib/RelatedRecordsUtils.js | 73 +++++++++++++++++++
6 files changed, 148 insertions(+), 78 deletions(-)
create mode 100644 src/lib/RelatedRecordsUtils.js
diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js
index e4b03e1911..9a05c4c48c 100644
--- a/src/components/BrowserCell/BrowserCell.react.js
+++ b/src/components/BrowserCell/BrowserCell.react.js
@@ -10,6 +10,7 @@ import { List, Map } from 'immutable';
import { dateStringUTC } from 'lib/DateUtils';
import getFileName from 'lib/getFileName';
import { getValidScripts, executeScript } from 'lib/ScriptUtils';
+import { buildRelatedTextFieldsMenuItem } from 'lib/RelatedRecordsUtils';
import Parse from 'parse';
import Pill from 'components/Pill/Pill.react';
import React, { Component } from 'react';
@@ -521,60 +522,11 @@ export default class BrowserCell extends Component {
// Only show for String type cells with a non-empty value
// Exclude objectId field - it uses getRelatedObjectsContextMenuOption() for pointer-based lookups
- if (type !== 'String' || field === 'objectId' || !value || typeof value !== 'string' || value.trim() === '') {
+ if (type !== 'String' || field === 'objectId') {
return;
}
- const relatedRecordsMenuItem = {
- text: 'Related records',
- items: [],
- };
-
- // Group fields by class name for hierarchical navigation
- schema.data
- .get('classes')
- .sortBy((_v, k) => k)
- .forEach((cl, className) => {
- const classFields = [];
-
- cl.forEach((column, field) => {
- if (column.type !== 'String') {
- return;
- }
- // Exclude objectId - it's a special field referenced by pointers, not strings
- if (field === 'objectId') {
- return;
- }
- // Exclude hidden/sensitive fields
- if (field === 'password' && className === '_User') {
- return;
- }
- if (field === 'sessionToken' && (className === '_User' || className === '_Session')) {
- return;
- }
- classFields.push({
- text: field,
- callback: () => {
- onPointerClick({
- className,
- id: value,
- field,
- });
- },
- });
- });
-
- if (classFields.length > 0) {
- // Sort fields alphabetically
- classFields.sort((a, b) => a.text.localeCompare(b.text));
- relatedRecordsMenuItem.items.push({
- text: className,
- items: classFields,
- });
- }
- });
-
- return relatedRecordsMenuItem.items.length ? relatedRecordsMenuItem : undefined;
+ return buildRelatedTextFieldsMenuItem(schema, value, onPointerClick);
}
/**
diff --git a/src/components/StringEditor/StringEditor.react.js b/src/components/StringEditor/StringEditor.react.js
index f8c2745442..c4067a3558 100644
--- a/src/components/StringEditor/StringEditor.react.js
+++ b/src/components/StringEditor/StringEditor.react.js
@@ -63,15 +63,15 @@ export default class StringEditor extends React.Component {
}
handleContextMenu(e) {
- const { setContextMenu, arrayConfigParams, onAddToArrayConfig } = this.props;
+ const { setContextMenu, arrayConfigParams, onAddToArrayConfig, getRelatedRecordsMenuItem } = this.props;
// Only show custom context menu when Alt key is held
if (!e.altKey) {
return;
}
- // Check if there are array config params available
- if (!arrayConfigParams || arrayConfigParams.length === 0 || !onAddToArrayConfig || !setContextMenu) {
+ // Check if setContextMenu is available
+ if (!setContextMenu) {
return;
}
@@ -84,13 +84,12 @@ export default class StringEditor extends React.Component {
return;
}
- // Prevent default context menu
- e.preventDefault();
- e.stopPropagation();
-
// Build context menu items
- const menuItems = [
- {
+ const menuItems = [];
+
+ // Add "Add to config parameter" option if available
+ if (arrayConfigParams && arrayConfigParams.length > 0 && onAddToArrayConfig) {
+ menuItems.push({
text: 'Add to config parameter...',
items: arrayConfigParams.map(param => ({
text: param.name,
@@ -98,8 +97,28 @@ export default class StringEditor extends React.Component {
onAddToArrayConfig(param.name, selectedText);
},
})),
- },
- ];
+ });
+ }
+
+ // Add "Related records" option if available (using selected text)
+ if (getRelatedRecordsMenuItem) {
+ const relatedRecordsItem = getRelatedRecordsMenuItem(selectedText);
+ if (relatedRecordsItem) {
+ if (menuItems.length > 0) {
+ menuItems.push({ type: 'separator' });
+ }
+ menuItems.push(relatedRecordsItem);
+ }
+ }
+
+ // Only show context menu if there are items
+ if (menuItems.length === 0) {
+ return;
+ }
+
+ // Prevent default context menu
+ e.preventDefault();
+ e.stopPropagation();
setContextMenu(e.pageX, e.pageY, menuItems);
}
diff --git a/src/dashboard/Data/Browser/BrowserTable.react.js b/src/dashboard/Data/Browser/BrowserTable.react.js
index d8c716657f..79a509a2c8 100644
--- a/src/dashboard/Data/Browser/BrowserTable.react.js
+++ b/src/dashboard/Data/Browser/BrowserTable.react.js
@@ -493,6 +493,7 @@ export default class BrowserTable extends React.Component {
setContextMenu={this.props.setContextMenu}
arrayConfigParams={this.props.arrayConfigParams}
onAddToArrayConfig={this.props.onAddToArrayConfig}
+ getRelatedRecordsMenuItem={this.props.getRelatedRecordsMenuItem}
/>
);
}
diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js
index 0a47bae695..7b2a55f0eb 100644
--- a/src/dashboard/Data/Browser/DataBrowser.react.js
+++ b/src/dashboard/Data/Browser/DataBrowser.react.js
@@ -25,6 +25,7 @@ import styles from './Databrowser.scss';
import KeyboardShortcutsManager, { matchesShortcut } from 'lib/KeyboardShortcutsPreferences';
import AggregationPanel from '../../../components/AggregationPanel/AggregationPanel';
+import { buildRelatedTextFieldsMenuItem } from '../../../lib/RelatedRecordsUtils';
const BROWSER_SHOW_ROW_NUMBER = 'browserShowRowNumber';
const AGGREGATION_PANEL_VISIBLE = 'aggregationPanelVisible';
@@ -1165,19 +1166,13 @@ export default class DataBrowser extends React.Component {
return;
}
- // Get array config params from props
- const arrayParams = this.props.arrayConfigParams || [];
-
- if (arrayParams.length === 0) {
- return;
- }
-
- // Prevent default context menu
- event.preventDefault();
-
// Build context menu items
- const menuItems = [
- {
+ const menuItems = [];
+
+ // Add "Add to config parameter" option if available
+ const arrayParams = this.props.arrayConfigParams || [];
+ if (arrayParams.length > 0) {
+ menuItems.push({
text: 'Add to config parameter',
items: arrayParams.map(param => ({
text: param.name,
@@ -1187,8 +1182,29 @@ export default class DataBrowser extends React.Component {
}
},
})),
- },
- ];
+ });
+ }
+
+ // Add "Related records" option if available (search text in String fields)
+ const relatedRecordsItem = buildRelatedTextFieldsMenuItem(
+ this.props.schema,
+ selectedText,
+ this.props.onPointerCmdClick
+ );
+ if (relatedRecordsItem) {
+ if (menuItems.length > 0) {
+ menuItems.push({ type: 'separator' });
+ }
+ menuItems.push(relatedRecordsItem);
+ }
+
+ // Only show context menu if there are items
+ if (menuItems.length === 0) {
+ return;
+ }
+
+ // Prevent default context menu
+ event.preventDefault();
this.setContextMenu(event.pageX, event.pageY, menuItems);
}
@@ -2032,6 +2048,11 @@ export default class DataBrowser extends React.Component {
setSelectedObjectId={this.setSelectedObjectId}
callCloudFunction={this.handleCallCloudFunction}
setContextMenu={this.setContextMenu}
+ getRelatedRecordsMenuItem={(textValue) => buildRelatedTextFieldsMenuItem(
+ this.props.schema,
+ textValue,
+ this.props.onPointerCmdClick
+ )}
freezeIndex={this.state.frozenColumnIndex}
freezeColumns={this.freezeColumns}
unfreezeColumns={this.unfreezeColumns}
diff --git a/src/dashboard/Data/Browser/Editor.react.js b/src/dashboard/Data/Browser/Editor.react.js
index 60aee64476..0a5ea3716d 100644
--- a/src/dashboard/Data/Browser/Editor.react.js
+++ b/src/dashboard/Data/Browser/Editor.react.js
@@ -16,7 +16,7 @@ import decode from 'parse/lib/browser/decode';
import React from 'react';
import StringEditor from 'components/StringEditor/StringEditor.react';
-const Editor = ({ top, left, type, targetClass, value, readonly, width, onCommit, onCancel, setContextMenu, arrayConfigParams, onAddToArrayConfig }) => {
+const Editor = ({ top, left, type, targetClass, value, readonly, width, onCommit, onCancel, setContextMenu, arrayConfigParams, onAddToArrayConfig, getRelatedRecordsMenuItem }) => {
let content = null;
if (type === 'String') {
content = (
@@ -31,6 +31,7 @@ const Editor = ({ top, left, type, targetClass, value, readonly, width, onCommit
setContextMenu={setContextMenu}
arrayConfigParams={arrayConfigParams}
onAddToArrayConfig={onAddToArrayConfig}
+ getRelatedRecordsMenuItem={getRelatedRecordsMenuItem}
/>
);
} else if (type === 'Array' || type === 'Object') {
@@ -53,6 +54,7 @@ const Editor = ({ top, left, type, targetClass, value, readonly, width, onCommit
setContextMenu={setContextMenu}
arrayConfigParams={arrayConfigParams}
onAddToArrayConfig={onAddToArrayConfig}
+ getRelatedRecordsMenuItem={getRelatedRecordsMenuItem}
/>
);
} else if (type === 'Polygon') {
@@ -95,6 +97,7 @@ const Editor = ({ top, left, type, targetClass, value, readonly, width, onCommit
setContextMenu={setContextMenu}
arrayConfigParams={arrayConfigParams}
onAddToArrayConfig={onAddToArrayConfig}
+ getRelatedRecordsMenuItem={getRelatedRecordsMenuItem}
/>
);
} else if (type === 'Date') {
@@ -109,6 +112,7 @@ const Editor = ({ top, left, type, targetClass, value, readonly, width, onCommit
setContextMenu={setContextMenu}
arrayConfigParams={arrayConfigParams}
onAddToArrayConfig={onAddToArrayConfig}
+ getRelatedRecordsMenuItem={getRelatedRecordsMenuItem}
/>
);
} else {
@@ -137,7 +141,7 @@ const Editor = ({ top, left, type, targetClass, value, readonly, width, onCommit
);
}
};
- content =