Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get correct compact layout for sObject #53

Merged
merged 9 commits into from
Nov 15, 2023
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
9 changes: 9 additions & 0 deletions src/commands/wizard/lwcGenerationCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { InstructionsWebviewProvider } from '../../webviews/instructions';
import { UEMParser } from '../../utils/uemParser';
import { WorkspaceUtils } from '../../utils/workspaceUtils';
import { CommonUtils } from '@salesforce/lwc-dev-mobile-core';
import { OrgUtils } from '../../utils/orgUtils';
import * as fs from 'fs';
import * as path from 'path';

Expand Down Expand Up @@ -82,6 +83,14 @@ export class LwcGenerationCommand {
if (callback) {
const quickActionStatus =
await LwcGenerationCommand.checkForExistingQuickActions();

for (const key in quickActionStatus.sobjects) {
const layoutFields =
await OrgUtils.getCompactLayoutFieldsForSObject(
key
);
}

callback(quickActionStatus);
}
}
Expand Down
126 changes: 126 additions & 0 deletions src/test/suite/utils/orgUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,132 @@ suite('Org Utils Test Suite', () => {
assert.equal(sobject.labelPlural, 'Labels');
});

test('Returns SYSTEM compact layout', async () => {
// Simplified all compact layout data structure
const allCompactLayouts = {
defaultCompactLayoutId: null,
recordTypeCompactLayoutMappings: [
{
available: true,
compactLayoutId: null,
compactLayoutName: 'SYSTEM',
recordTypeId: '012000000000000AAA',
recordTypeName: 'Master',
urls: {
compactLayout:
'/services/data/v59.0/sobjects/Contact/describe/compactLayouts/012000000000000AAA'
}
}
]
};

// Simplified compact layout data structure
const compactLayout = {
fieldItems: [
{
editableForNew: true,
editableForUpdate: true,
label: 'Name'
},
{
editableForNew: true,
editableForUpdate: true,
label: 'Title'
},
{
editableForNew: false,
editableForUpdate: false,
label: 'Contact Owner'
}
],
id: null,
label: 'System Default',
name: 'SYSTEM',
objectType: 'Contact'
};

sinon
.stub(OrgUtils, 'getCompactLayoutsForSObject')
.returns(Promise.resolve(allCompactLayouts));
sinon
.stub(OrgUtils, 'getCompactLayoutForSObject')
.returns(Promise.resolve(compactLayout));

const result =
await OrgUtils.getCompactLayoutFieldsForSObject('Contact');

assert.equal(result, compactLayout.fieldItems);
});

test('Returns Contact compact layout', async () => {
// Simplified all compact layout data structure
const allCompactLayouts = {
defaultCompactLayoutId: '123456789',
recordTypeCompactLayoutMappings: [
{
available: true,
compactLayoutId: null,
compactLayoutName: 'SYSTEM',
recordTypeId: '012000000000000AAA',
recordTypeName: 'Master',
urls: {
compactLayout:
'/services/data/v59.0/sobjects/Contact/describe/compactLayouts/012000000000000AAA'
}
},
{
available: true,
compactLayoutId: '123456789',
compactLayoutName: 'Mobile layout',
recordTypeId: '012000000000000BBB',
recordTypeName: 'Contact',
urls: {
compactLayout:
'/services/data/v59.0/sobjects/Contact/describe/compactLayouts/012000000000000BBB'
}
}
]
};

// Simplified compact layout data structure
const compactLayout = {
fieldItems: [
{
editableForNew: true,
editableForUpdate: true,
label: 'Name'
},
{
editableForNew: true,
editableForUpdate: true,
label: 'Title'
},
{
editableForNew: false,
editableForUpdate: false,
label: 'Contact Owner'
}
],
id: null,
label: 'Mobile layout',
name: 'Mobile layout',
objectType: 'Contact'
};

sinon
.stub(OrgUtils, 'getCompactLayoutsForSObject')
.returns(Promise.resolve(allCompactLayouts));
sinon
.stub(OrgUtils, 'getCompactLayoutForSObject')
.withArgs('Contact', '012000000000000BBB')
.returns(Promise.resolve(compactLayout));

const result =
await OrgUtils.getCompactLayoutFieldsForSObject('Contact');

assert.equal(result, compactLayout.fieldItems);
});

test('Returns list of fields for given sObject', async () => {
const sobjectFields: FieldType[] = [
buildField('City', 'string', 'Label'),
Expand Down
103 changes: 103 additions & 0 deletions src/utils/orgUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,28 @@ export interface Field {
label: string;
type: string;
}

export interface CompactLayoutField {
editableForNew: boolean;
editableForUpdate: boolean;
label: string;
}
Comment on lines +22 to +26
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dbreese We don't have to use this interface. I added so that I can cast to this before returning to the caller so LWC generating code can have defined fields in the elements of returned array of sObject's fields. Otherwise, you might need to do lot of casting as I do in getCompactLayoutFieldsForSObject.


export type SObjectCompactLayoutMapping = {
compactLayoutId: string | null;
compactLayoutName: string;
recordTypeId: string;
};

export type SObjectCompactLayouts = {
defaultCompactLayoutId: string | null;
recordTypeCompactLayoutMappings: SObjectCompactLayoutMapping[];
};

export type SObjectCompactLayout = {
fieldItems: CompactLayoutField[];
};

export class OrgUtils {
public static async getSobjects(): Promise<SObject[]> {
try {
Expand Down Expand Up @@ -65,6 +87,87 @@ export class OrgUtils {
}
}

public static async getCompactLayoutsForSObject(
sObjectName: string
): Promise<SObjectCompactLayouts> {
const org = await Org.create();
const conn = org.getConnection();

const result = await conn.request<SObjectCompactLayouts>(
`/services/data/v59.0/sobjects/${sObjectName}/describe/compactLayouts`
);

return Promise.resolve(result);
}

public static async getCompactLayoutForSObject(
sObjectName: string,
recordTypeId: string
): Promise<SObjectCompactLayout> {
const org = await Org.create();
const conn = org.getConnection();

const result = await conn.request<SObjectCompactLayout>(
`/services/data/v59.0/sobjects/${sObjectName}/describe/compactLayouts/${recordTypeId}`
);

return Promise.resolve(result);
}

public static async getCompactLayoutFieldsForSObject(
sObjectName: string
): Promise<CompactLayoutField[]> {
try {
const fields: CompactLayoutField[] = [];

// Get all the compact layouts associated to this sObject first
let result = await this.getCompactLayoutsForSObject(sObjectName);

if (result) {
// sObject can have multiple compact layouts associated with it. Get the default.
const defaultCompactLayoutId = result.defaultCompactLayoutId;
result['defaultCompactLayoutId'];

// Mapping table
const recordTypeCompactLayoutMappings =
result.recordTypeCompactLayoutMappings;

// ID of compact layout need to be normalized
const recordTypeCompactLayoutMapping =
recordTypeCompactLayoutMappings.find((element) => {
if (defaultCompactLayoutId) {
return (
element.compactLayoutId ===
defaultCompactLayoutId
);
} else {
// defaultCompactLayoutId can be null when a compact layout is not assigned.
// In that case sObject will always have one default layout called SYSTEM.
// So use that instead.
return element.compactLayoutName === 'SYSTEM';
}
});

if (recordTypeCompactLayoutMapping) {
// With the compact layout ID mapped back to the recordType ID, make another network request to get the
// exact compact layout info.
const compactLayout = await this.getCompactLayoutForSObject(
sObjectName,
recordTypeCompactLayoutMapping.recordTypeId
);

if (compactLayout) {
return compactLayout.fieldItems;
}
}
}

return Promise.resolve(fields);
} catch (error) {
return Promise.reject(error);
}
}

public static async getDefaultUser(): Promise<string> {
const aggregator = await ConfigAggregator.create();

Expand Down