11/* global React ReactDOM */
22import { sfConn , apiVersion } from "./inspector.js" ;
3- import { copyToClipboard , downloadCsvFile } from "./utils.js" ;
3+ import { copyToClipboard , downloadCsvFile , getStandardObjectNameField } from "./utils.js" ;
44/* global initButton */
55import { getObjectSetupLinks , getFieldSetupLinks } from "./setup-links.js" ;
66import { PageHeader } from "./components/PageHeader.js" ;
@@ -10,6 +10,66 @@ import AgentforceModal from "./components/AgentforceModal.js";
1010// Constants
1111const GET_FIELD_USAGE_LABEL = "Get field usage" ;
1212
13+ /**
14+ * Builds a SOQL query to fetch a record with lookup name fields.
15+ * Uses STANDARD_OBJECT_NAME_FIELDS to only add relationship fields when the referenced object has a name field.
16+ * @param {Object } sobjectDescribe - Object describe from REST API
17+ * @param {string } recordId - Record Id to fetch
18+ * @returns {{query: string, lookupFieldMap: Array<{fieldName: string, relationshipPath: string, nameField: string}>} }
19+ */
20+ function buildRecordQueryWithLookupNames ( sobjectDescribe , recordId ) {
21+ let selectFields = [ ] ;
22+ const lookupFieldMap = [ ] ;
23+
24+ for ( const field of sobjectDescribe . fields ) {
25+ if ( ! field . name || field . name === "attributes" ) {
26+ continue ;
27+ }
28+ selectFields . push ( field . name ) ;
29+
30+ if ( field . type === "reference" && field . relationshipName && field . referenceTo && field . referenceTo . length === 1 ) {
31+ const referencedObject = field . referenceTo [ 0 ] ;
32+ const nameField = getStandardObjectNameField ( referencedObject ) ;
33+ if ( nameField !== null ) {
34+ const relationshipField = nameField === "N/A" ? "Name" : nameField ;
35+ selectFields . push ( field . relationshipName + "." + relationshipField ) ;
36+ lookupFieldMap . push ( { fieldName : field . name , relationshipPath : field . relationshipName , nameField : relationshipField } ) ;
37+ }
38+ }
39+ }
40+
41+ const query = "SELECT " + selectFields . join ( ", " ) + " FROM " + sobjectDescribe . name + " WHERE Id = '" + recordId + "'" ;
42+ return { query, lookupFieldMap} ;
43+ }
44+
45+ /**
46+ * Flattens a SOQL record result and extracts lookup names into lookupNames map.
47+ * @param {Object } record - Single record from SOQL query (records[0])
48+ * @param {Array } lookupFieldMap - From buildRecordQueryWithLookupNames
49+ * @returns {{flatRecord: Object, lookupNames: Object} }
50+ */
51+ function flattenSoqlRecordWithLookupNames ( record , lookupFieldMap ) {
52+ const flatRecord = { } ;
53+ const lookupNames = { } ;
54+
55+ for ( const key in record ) {
56+ if ( key === "attributes" ) {
57+ continue ;
58+ }
59+ const value = record [ key ] ;
60+ if ( value && typeof value === "object" && value . attributes ) {
61+ const lookupInfo = lookupFieldMap . find ( l => l . relationshipPath === key ) ;
62+ if ( lookupInfo && value [ lookupInfo . nameField ] != null ) {
63+ lookupNames [ lookupInfo . fieldName ] = value [ lookupInfo . nameField ] ;
64+ }
65+ } else {
66+ flatRecord [ key ] = value ;
67+ }
68+ }
69+
70+ return { flatRecord, lookupNames} ;
71+ }
72+
1373class Model {
1474 constructor ( sfHost ) {
1575 this . reactCallback = null ;
@@ -49,6 +109,7 @@ class Model {
49109 this . popupTmpReactElement = undefined ;
50110 this . popupReactElement = undefined ;
51111 this . recordName ;
112+ this . lookupNames = { } ; // Maps lookup field name -> display name (e.g. AccountId -> "Acme Corp")
52113 let trialExpDate = localStorage . getItem ( sfHost + "_trialExpirationDate" ) ;
53114 if ( localStorage . getItem ( sfHost + "_isSandbox" ) != "true" && ( ! trialExpDate || trialExpDate === "null" ) ) {
54115 //change background color for production
@@ -407,19 +468,26 @@ Structure your response clearly with appropriate headings.`;
407468 }
408469 setRecordData ( recordDataPromise ) {
409470 this . spinFor ( "retrieving record" , recordDataPromise . then ( res => {
410- for ( let name in res ) {
471+ let recordData = res ;
472+ if ( res . recordData && res . lookupNames ) {
473+ this . lookupNames = res . lookupNames ;
474+ recordData = res . recordData ;
475+ } else {
476+ this . lookupNames = { } ;
477+ }
478+ for ( let name in recordData ) {
411479 if ( name != "attributes" ) {
412- this . fieldRows . getRow ( name ) . dataTypedValue = res [ name ] ;
480+ this . fieldRows . getRow ( name ) . dataTypedValue = recordData [ name ] ;
413481 }
414482 }
415483 this . fieldRows . resortRows ( ) ;
416- this . recordData = res ;
484+ this . recordData = recordData ;
417485 this . fieldRows . showHideColumn ( true , "value" ) ;
418486 this . spinFor (
419487 "describing layout" ,
420488 this . sobjectDescribePromise . then ( sobjectDescribe => {
421489 if ( sobjectDescribe . urls . layouts ) {
422- return sfConn . rest ( sobjectDescribe . urls . layouts + "/" + ( res . RecordTypeId || "012000000000000AAA" ) ) ;
490+ return sfConn . rest ( sobjectDescribe . urls . layouts + "/" + ( recordData . RecordTypeId || "012000000000000AAA" ) ) ;
423491 }
424492 return undefined ;
425493 } ) . then ( layoutDescribe => {
@@ -475,6 +543,7 @@ Structure your response clearly with appropriate headings.`;
475543 }
476544 this . recordData = null ;
477545 this . layoutInfo = null ;
546+ this . lookupNames = { } ;
478547 }
479548 startLoading ( ) {
480549
@@ -498,9 +567,20 @@ Structure your response clearly with appropriate headings.`;
498567 this . childRows . resortRows ( ) ;
499568 } ) ) ;
500569
501- // Fetch record data using record retrieve call
570+ // Fetch record data using SOQL query (includes lookup names when referenced object has a name field)
502571 if ( this . recordId ) {
503- this . setRecordData ( sfConn . rest ( "/services/data/v" + apiVersion + "/" + ( this . useToolingApi ? "tooling/" : "" ) + "sobjects/" + this . sobjectName + "/" + this . recordId ) ) ;
572+ const recordPromise = this . sobjectDescribePromise . then ( sobjectDescribe => {
573+ const { query, lookupFieldMap} = buildRecordQueryWithLookupNames ( sobjectDescribe , this . recordId ) ;
574+ const queryUrl = "/services/data/v" + apiVersion + "/" + ( this . useToolingApi ? "tooling/" : "" ) + "query/?q=" + encodeURIComponent ( query ) ;
575+ return sfConn . rest ( queryUrl ) . then ( res => {
576+ if ( ! res . records || res . records . length === 0 ) {
577+ throw new Error ( "Record not found" ) ;
578+ }
579+ const { flatRecord, lookupNames} = flattenSoqlRecordWithLookupNames ( res . records [ 0 ] , lookupFieldMap ) ;
580+ return { recordData : flatRecord , lookupNames} ;
581+ } ) ;
582+ } ) ;
583+ this . setRecordData ( recordPromise ) ;
504584 }
505585
506586 // Fetch fields using a Tooling API call, which returns fields not readable by the current user, but fails if the user does not have access to the Tooling API.
@@ -1387,6 +1467,9 @@ class FieldRow extends TableRow {
13871467 }
13881468 return false ;
13891469 }
1470+ lookupDisplayValue ( ) {
1471+ return this . rowList . model . lookupNames ?. [ this . fieldName ] ?? null ;
1472+ }
13901473 idLink ( ) {
13911474 return "https://" + this . rowList . model . sfHost + "/" + this . dataTypedValue ;
13921475 }
@@ -2281,9 +2364,10 @@ class FieldValueCell extends React.Component {
22812364 )
22822365 ) ;
22832366 } else if ( row . isId ( ) ) {
2367+ const lookupTitle = row . lookupDisplayValue ( ) ? row . lookupDisplayValue ( ) : null ;
22842368 return h ( "td" , { className : col . className , onDoubleClick : this . onTryEdit } ,
22852369 h ( "div" , { className : "pop-menu-container" } ,
2286- h ( "div" , { className : "sfir-inspect-table-text quick-select" } , h ( "a" , { href : row . idLink ( ) /*used to show visited color*/ , onClick : this . onRecordIdClick } , row . dataStringValue ( ) ) ) ,
2370+ h ( "div" , { className : "sfir-inspect-table-text quick-select" } , h ( "a" , { href : row . idLink ( ) /*used to show visited color*/ , onClick : this . onRecordIdClick , title : lookupTitle } , row . dataStringValue ( ) ) ) ,
22872371 row . recordIdPop == null ? null : h ( "div" , { className : "slds-dropdown slds-dropdown_left slds-dropdown_actions pop-menu" } ,
22882372 h ( "ul" , { className : "slds-dropdown__list" } ,
22892373 row . recordIdPop . map ( link =>
0 commit comments