Skip to content

Commit

Permalink
Merge pull request #1105 from jetstreamapp/bug/1102-include-child-rel…
Browse files Browse the repository at this point in the history
…ationship-name-sobj-export

Include child relationship names on sobject export
  • Loading branch information
paustint authored Dec 8, 2024
2 parents d75f84b + 6e1ab45 commit 1ac7255
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 31 deletions.
28 changes: 19 additions & 9 deletions libs/features/sobject-export/src/SObjectExport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,25 @@ import { applicationCookieState, fromJetstreamEvents, selectedOrgState } from '@
import localforage from 'localforage';
import { Fragment, FunctionComponent, useEffect, useRef, useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { ExportHeaderOption, ExportOptions, ExportWorksheetLayout, SavedExportOptions } from './sobject-export-types';
import { getAttributes, getSobjectMetadata, prepareExport } from './sobject-export-utils';
import {
ExportHeaderOption,
ExportOptions,
ExportWorksheetLayout,
SavedExportOptions,
SobjectExportFieldName,
} from './sobject-export-types';
import { getAttributes, getChildRelationshipNames, getSobjectMetadata, prepareExport } from './sobject-export-utils';

const HEIGHT_BUFFER = 170;
const FIELD_ATTRIBUTES: ListItem[] = getAttributes().map(({ label, name, description }) => ({
const FIELD_ATTRIBUTES: ListItem<SobjectExportFieldName>[] = getAttributes().map(({ label, name, description, tertiaryLabel }) => ({
id: name,
label: `${label} (${name})`,
value: name,
secondaryLabel: description,
tertiaryLabel,
}));

const DEFAULT_SELECTION = [
const DEFAULT_SELECTION: SobjectExportFieldName[] = [
'calculatedFormula',
'createable',
'custom',
Expand Down Expand Up @@ -81,7 +88,7 @@ export const SObjectExport: FunctionComponent<SObjectExportProps> = () => {

const [sobjects, setSobjects] = useState<Maybe<DescribeGlobalSObjectResult[]>>();
const [selectedSObjects, setSelectedSObjects] = useState<string[]>([]);
const [selectedAttributes, setSelectedAttributes] = useState<string[]>([]);
const [selectedAttributes, setSelectedAttributes] = useState<SobjectExportFieldName[]>([]);

const [exportDataModalOpen, setExportDataModalOpen] = useState<boolean>(false);
const [exportDataModalData, setExportDataModalData] = useState<Record<string, any[]>>({});
Expand All @@ -108,7 +115,7 @@ export const SObjectExport: FunctionComponent<SObjectExportProps> = () => {
}
}
if (results?.fields) {
setSelectedAttributes(results.fields);
setSelectedAttributes(results.fields as SobjectExportFieldName[]);
} else {
setSelectedAttributes([...DEFAULT_SELECTION]);
}
Expand Down Expand Up @@ -149,7 +156,10 @@ export const SObjectExport: FunctionComponent<SObjectExportProps> = () => {
setLoading(true);
setErrorMessage(null);
const metadataResults = await getSobjectMetadata(selectedOrg, selectedSObjects);
const output = prepareExport(metadataResults, selectedAttributes, options);
const sobjectsWithChildRelationships = selectedAttributes.includes('childRelationshipName')
? await getChildRelationshipNames(selectedOrg, metadataResults)
: {};
const output = prepareExport(metadataResults, sobjectsWithChildRelationships, selectedAttributes, options);

if (options.saveAsDefaultSelection) {
try {
Expand Down Expand Up @@ -248,12 +258,12 @@ export const SObjectExport: FunctionComponent<SObjectExportProps> = () => {
descriptorSingular: 'field attribute',
descriptorPlural: 'field attributes',
}}
items={FIELD_ATTRIBUTES}
items={FIELD_ATTRIBUTES as ListItem[]}
selectedItems={selectedAttributes}
allowRefresh
lastRefreshed="Reset to default"
onRefresh={resetAttributesToDefault}
onSelected={setSelectedAttributes}
onSelected={(items) => setSelectedAttributes(items as SobjectExportFieldName[])}
/>
</div>
<div className="slds-p-horizontal_x-small">
Expand Down
5 changes: 4 additions & 1 deletion libs/features/sobject-export/src/sobject-export-types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { DescribeSObjectResult, Field, Maybe } from '@jetstream/types';
import { ChildRelationship, DescribeSObjectResult, Field, Maybe } from '@jetstream/types';

export type SobjectExportFieldName =
| keyof Field
| 'childRelationshipName'
| 'dataTranslationEnabled'
| 'autoNumber'
| 'aiPredictionField'
Expand All @@ -13,7 +14,9 @@ export interface SobjectExportField {
name: SobjectExportFieldName;
label: string;
description?: string;
tertiaryLabel?: string;
getterFn?: (value: any) => string;
childRelationshipGetterFn?: (field: Field, sobjectsWithChildRelationships: Record<string, Record<string, ChildRelationship>>) => string;
}

export interface SavedExportOptions {
Expand Down
77 changes: 70 additions & 7 deletions libs/features/sobject-export/src/sobject-export-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { logger } from '@jetstream/shared/client-logger';
import { describeSObject } from '@jetstream/shared/data';
import { splitArrayToMaxSize } from '@jetstream/shared/utils';
import { ApiResponse, DescribeSObjectResult, SalesforceOrgUi } from '@jetstream/types';
import { logErrorToRollbar } from '@jetstream/shared/ui-utils';
import { getErrorMessageAndStackObj, splitArrayToMaxSize } from '@jetstream/shared/utils';
import { ApiResponse, ChildRelationship, DescribeSObjectResult, Field, SalesforceOrgUi } from '@jetstream/types';
import isFunction from 'lodash/isFunction';
import isString from 'lodash/isString';
import { ExportOptions, SobjectExportField, SobjectFetchResult } from './sobject-export-types';
Expand Down Expand Up @@ -39,8 +41,46 @@ export async function getSobjectMetadata(org: SalesforceOrgUi, selectedSobjects:
});
}

export async function getChildRelationshipNames(
selectedOrg: SalesforceOrgUi,
metadataResults: SobjectFetchResult[]
): Promise<Record<string, Record<string, ChildRelationship>>> {
try {
// Get Parent SObject names from all relationship fields and remove duplicates
const relatedSobjects = Array.from(
new Set(
metadataResults.flatMap(
(item) =>
item.metadata?.fields
.filter((field) => field.type === 'reference' && field.referenceTo?.length === 1)
.flatMap((field) => field.referenceTo || []) || []
)
)
);
// Fetch all parent sobject metadata (hopefully from cache for many of them) and reduce into map for easy lookup
const sobjectsWithChildRelationships = await getSobjectMetadata(selectedOrg, relatedSobjects).then((results) =>
results.reduce((sobjectsWithChildRelationships: Record<string, Record<string, ChildRelationship>>, { metadata, sobject }) => {
sobjectsWithChildRelationships[sobject] = (metadata?.childRelationships || []).reduce(
(acc: Record<string, ChildRelationship>, childRelationship) => {
acc[childRelationship.field] = childRelationship;
return acc;
},
{}
);
return sobjectsWithChildRelationships;
}, {})
);
return sobjectsWithChildRelationships;
} catch (ex) {
logger.warn('Error getting child relationship names for sobject export', ex);
logErrorToRollbar('Error getting child relationship names for sobject export', getErrorMessageAndStackObj(ex));
return {};
}
}

export function prepareExport(
sobjectMetadata: SobjectFetchResult[],
sobjectsWithChildRelationships: Record<string, Record<string, ChildRelationship>>,
selectedAttributes: string[],
options: ExportOptions
): Record<string, any[]> {
Expand All @@ -62,16 +102,18 @@ export function prepareExport(
rowsBySobject[sobject] =
metadata?.fields
.filter((field) => (options.includesStandardFields ? true : field.custom))
.flatMap((field: any) => {
.flatMap((field: Field) => {
const obj = { 'Object Name': sobject } as any;
selectedAttributeFields.forEach(({ name, label, getterFn }) => {
selectedAttributeFields.forEach(({ name, label, getterFn, childRelationshipGetterFn: relationshipGetterFn }) => {
const _label = options.headerOption === 'label' ? label : name;
// TODO: transform as required
const value = field[name as keyof Field];

if (isFunction(getterFn)) {
obj[_label] = getterFn(field[name]);
obj[_label] = getterFn(value);
} else if (isFunction(relationshipGetterFn)) {
obj[_label] = relationshipGetterFn(field, sobjectsWithChildRelationships);
} else {
obj[_label] = field[name];
obj[_label] = value;
}
});
return obj;
Expand Down Expand Up @@ -192,6 +234,27 @@ export function getAttributes(): SobjectExportField[] {
label: 'Calculated Formula',
description: 'Formula definition. Only populated if field type is Formula.',
},
{
name: 'childRelationshipName',
label: 'Child Relationship Name',
description: 'Child relationship name(s) for lookup field.',
childRelationshipGetterFn: (field: Field, sobjectsWithChildRelationships: Record<string, Record<string, ChildRelationship>>) => {
const relatedSObjects = field.referenceTo || [];
if (relatedSObjects.length === 0) {
return '';
}
return relatedSObjects
.map((relatedSObject) => {
const childRelationship = sobjectsWithChildRelationships[relatedSObject]?.[field.name]?.relationshipName;
if (childRelationship) {
return childRelationship;
}
return null;
})
.filter(Boolean)
.join(', ');
},
},
{
name: 'controllerName',
label: 'Controller Name',
Expand Down
18 changes: 4 additions & 14 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11678,20 +11678,10 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"

caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001335, caniuse-lite@^1.0.30001349, caniuse-lite@^1.0.30001370, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001503, caniuse-lite@^1.0.30001541, caniuse-lite@^1.0.30001565, caniuse-lite@^1.0.30001587:
version "1.0.30001636"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz"
integrity sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==

caniuse-lite@^1.0.30001579:
version "1.0.30001639"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001639.tgz#972b3a6adeacdd8f46af5fc7f771e9639f6c1521"
integrity sha512-eFHflNTBIlFwP2AIKaYuBQN/apnUoKNhBdza8ZnW/h2di4LCZ4xFqYlxUxo+LQ76KFI1PGcC1QDxMbxTZpSCAg==

caniuse-lite@^1.0.30001646:
version "1.0.30001657"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001657.tgz#29fd504bffca719d1c6b63a1f6f840be1973a660"
integrity sha512-DPbJAlP8/BAXy3IgiWmZKItubb3TYGP0WscQQlVGIfT4s/YlFYVuJgyOsQNP7rJRChx/qdMeLJQJP0Sgg2yjNA==
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001335, caniuse-lite@^1.0.30001349, caniuse-lite@^1.0.30001370, caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001503, caniuse-lite@^1.0.30001541, caniuse-lite@^1.0.30001565, caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001646:
version "1.0.30001687"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz"
integrity sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==

caseless@~0.12.0:
version "0.12.0"
Expand Down

0 comments on commit 1ac7255

Please sign in to comment.