Skip to content

Commit

Permalink
Parent record switcher (#81)
Browse files Browse the repository at this point in the history
* Added test class for parent picklist
* Added ability to select parent field
* Standardised comments
* Added parent record property
* Add parent record property
* Added support for Parent Field Property
* Custom rule for parameters
* Refresh types when Parent Field changes
* Prettier updates
* Made inaccessible field clearer
* Added code coverage
* Added dot notations for test coverage
* prettier updates
Co-authored-by: David Norris <[email protected]>
  • Loading branch information
deejay-hub authored Sep 25, 2020
1 parent fe2056c commit 2e0f04b
Show file tree
Hide file tree
Showing 10 changed files with 347 additions and 35 deletions.
11 changes: 10 additions & 1 deletion .pmdruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
<description>Default ruleset used by the Code Climate Engine for Salesforce.com Apex</description>

<rule ref="rulesets/apex/quickstart.xml/ExcessiveClassLength" deprecated="true" />
<rule ref="rulesets/apex/quickstart.xml/ExcessiveParameterList" deprecated="true" />
<rule ref="rulesets/apex/quickstart.xml/ExcessiveParameterList" deprecated="true">
<priority>3</priority>
<properties>
<property name="minimum" value="5" />
<!-- relevant for Code Climate output only -->
<property name="cc_categories" value="Complexity" />
<property name="cc_remediation_points_multiplier" value="50" />
<property name="cc_block_highlighting" value="false" />
</properties>
</rule>
<rule ref="rulesets/apex/quickstart.xml/ExcessivePublicCount" deprecated="true" />
<rule ref="rulesets/apex/quickstart.xml/NcssConstructorCount" deprecated="true" />
<!--<rule ref="rulesets/apex/quickstart.xml/NcssMethodCount" deprecated="true" />-->
Expand Down
59 changes: 59 additions & 0 deletions force-app/main/default/classes/TimelineParentPicklist.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* @description Supporting Apex to get all supported parent object fields for the Timeline Lightning Web Component
* This allows the component to plot for different fields and contexts (e.g. shows the Contact timeline on a Case)
*/
global with sharing class TimelineParentPicklist extends VisualEditor.DynamicPickList { //NOPMD
VisualEditor.DesignTimePageContext context;

/**
* @description Apex class constructot passing context from the Lightning Web Component
* @param context The DesignTimePageContext to get Entity Name and Page Type
*/
global TimelineParentPicklist(VisualEditor.DesignTimePageContext context) {
this.context = context;
}

global override VisualEditor.DataRow getDefaultValue() {
//objectLabel = System.Label.Timeline_Label_Files;
String objectLabel = ((SObject) (Type.forName('Schema.' + String.valueOf(this.context.entityName))
.newInstance()))
.getSObjectType()
.getDescribe()
.getLabel();
VisualEditor.DataRow defaultValue = new VisualEditor.DataRow(
'Use This ' + objectLabel,
'Default_Picklist_Value'
);
return defaultValue;
}

global override VisualEditor.DynamicPickListRows getValues() {
VisualEditor.DynamicPickListRows myValues = new VisualEditor.DynamicPickListRows();
myValues.addRow(getDefaultValue());

Schema.DescribeSObjectResult describeSobjects = ((SObject) (Type.forName('Schema.' + this.context.entityName)
.newInstance()))
.getSObjectType()
.getDescribe();

Map<String, Schema.SObjectField> myFields = describeSobjects.fields.getMap();

for (String field : myFields.keySet()) {
Schema.DescribeFieldResult currentField = myFields.get(field).getDescribe();

if (
currentField.isAccessible() &&
currentField.isNamePointing() == false &&
currentField.getLabel() != 'Master Record ID' &&
(String.valueOf(currentField.getReferenceTo()) == '(Lead)' ||
String.valueOf(currentField.getReferenceTo()) == '(Contact)' ||
String.valueOf(currentField.getReferenceTo()) == '(Account)' ||
String.valueOf(currentField.getReferenceTo()) == '(Opportunity)' ||
String.valueOf(currentField.getReferenceTo()) == '(Case)')
) {
myValues.addRow(new VisualEditor.DataRow(currentField.getLabel(), field));
}
}
return myValues;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>49.0</apiVersion>
<status>Active</status>
</ApexClass>
39 changes: 39 additions & 0 deletions force-app/main/default/classes/TimelineParentPicklist_Test.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @description Supporting test class to check the Timeline picklist
* 1 - Does the picklist return the correct default values
* 2 - Does the picklist return at least 1 value for the Contact standard object
*/
@isTest(seeAllData=false)
private with sharing class TimelineParentPicklist_Test {
@isTest
static void testTimelinePicklistDefaultValue() {
VisualEditor.DesignTimePageContext context = new VisualEditor.DesignTimePageContext();
context.entityName = 'Contact';

TimelineParentPicklist timeline = new TimelineParentPicklist(context);

Test.startTest();
VisualEditor.DataRow defaultValue = timeline.getDefaultValue();
Test.stopTest();

System.assertEquals(
'Use This Contact',
defaultValue.getLabel(),
'Timeline Parent Picklist default value incorrect'
);
}

@isTest
static void testTimelinePicklistValues() {
VisualEditor.DesignTimePageContext context = new VisualEditor.DesignTimePageContext();
context.entityName = 'Contact';

TimelineParentPicklist timeline = new TimelineParentPicklist(context);

Test.startTest();
VisualEditor.DynamicPickListRows picklistValues = timeline.getValues();
Test.stopTest();

System.assert(picklistValues.size() > 0, 'No parent picklist values found for Contact');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>49.0</apiVersion>
<status>Active</status>
</ApexClass>
101 changes: 85 additions & 16 deletions force-app/main/default/classes/TimelineService.cls
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ public with sharing class TimelineService {
/**
* @description Return all child record types for the parent record in context
* @param parentObjectId Id of the parent object used as the basis for the query
* @param parentFieldName maps API name to use if different from current record
* @return A map of the API name and label or each child record type to plot on the timeline
*/
@AuraEnabled(cacheable=true)
public static Map<String, String> getTimelineTypes(String parentObjectId) {
public static Map<String, String> getTimelineTypes(String parentObjectId, String parentFieldName) {
try {
String parentObjectType = String.valueOf(Id.valueOf(parentObjectId).getSobjectType());
if (parentObjectType == 'Account' && isPersonAccount(parentObjectId)) {
parentObjectType = 'PersonAccount';
}
Map<String, String> parentDetails = getParentDetails(parentObjectId, parentFieldName);

String parentObjectType = parentDetails.get('ConfigObjectType');

String queryTimelineConfiguration =
'SELECT Active__c, ' +
Expand Down Expand Up @@ -57,6 +57,8 @@ public with sharing class TimelineService {
}

return mapOfTimelineTypes;
} catch (TimelineSetupException e) {
throw new AuraHandledException(e.getMessage());
} catch (Exception e) {
throw new AuraHandledException(e.getMessage() + ' : ' + e.getStackTraceString());
}
Expand All @@ -68,20 +70,21 @@ public with sharing class TimelineService {
* @param parentObjectId The id of the parent record
* @param earliestRange The number of historical years to include in the query
* @param latestRange The number of years in the future to include in the query
* @param parentFieldName maps API name to use if different from current record
* @return A map of API Object Names and their corresponding translated labels
*/
public static List<Map<String, String>> getTimelineRecords(
String parentObjectId,
String earliestRange,
String latestRange
String latestRange,
String parentFieldName
) {
try {
String parentObjectType = String.valueOf(Id.valueOf(parentObjectId).getSobjectType());
String parentConfigType = parentObjectType;
Map<String, String> parentDetails = getParentDetails(parentObjectId, parentFieldName);

if (parentObjectType == 'Account' && isPersonAccount(parentObjectId)) {
parentConfigType = 'PersonAccount';
}
String parentObjectType = parentDetails.get('QueryObjectType');
String parentConfigType = parentDetails.get('ConfigObjectType');
parentObjectId = parentDetails.get('Id');

earliestRange = String.ValueOf((Decimal.ValueOf(earliestRange) * 12).intValue());
latestRange = String.ValueOf((Decimal.ValueOf(latestRange) * 12).intValue());
Expand Down Expand Up @@ -188,9 +191,6 @@ public with sharing class TimelineService {
}

String relationship = tcr.relationshipName;
if (tcr.relationshipName.contains('Person') && !tcr.relationshipName.contains('__pr')) {
relationship = tcr.relationshipName.substringAfter('Person');
}

innerQuery =
innerQuery +
Expand Down Expand Up @@ -306,8 +306,10 @@ public with sharing class TimelineService {
}
}
return listOfTimelineData;
} catch (Exception e) {
} catch (TimelineSetupException e) {
throw new AuraHandledException(e.getMessage());
} catch (Exception e) {
throw new AuraHandledException(e.getMessage() + ' : ' + e.getStackTraceString());
}
}

Expand Down Expand Up @@ -443,7 +445,7 @@ public with sharing class TimelineService {
}
}
} else {
fieldValue = '🔒 [' + fieldLabel + ']';
fieldValue = '![' + fieldLabel + ']!';
}

if (fieldValue != null && fieldValue.length() > 255) {
Expand All @@ -468,6 +470,73 @@ public with sharing class TimelineService {
return false;
}

private static Map<String, String> getParentDetails(String recordId, String field) {
Map<String, String> parentDetails = new Map<String, String>();

String parentObjectType = String.valueOf(Id.valueOf(recordId).getSobjectType());
String parentConfigType = parentObjectType;
String fieldValue;

if (field == 'Default_Picklist_Value') {
fieldValue = recordId;
} else {
String queryParentId = 'SELECT Id, ' + field + ' FROM ' + parentObjectType + ' Where Id =:recordId';

List<SObject> newDefaultRecord = Database.query(queryParentId); //NOPMD

Schema.DescribeSObjectResult describeSobjects = ((SObject) (Type.forName('Schema.' + parentObjectType)
.newInstance()))
.getSObjectType()
.getDescribe();

String fieldLabel = String.valueOf(describeSobjects.fields.getMap().get(field).getDescribe().getLabel());

SObjectAccessDecision securityDecision = Security.stripInaccessible(AccessType.READABLE, newDefaultRecord);

Set<String> strippedFields = securityDecision.getRemovedFields().get(parentObjectType);

if (strippedFields != null && strippedFields.contains(field)) {
String errorMsg =
'We can\'t display the timeline. You do not have access to the related field. ' +
'Ask your Administrator for help. ' +
'(' +
fieldLabel +
')';

throw new TimelineSetupException('{"type": "No-Access", "message": "' + errorMsg + '"}');
}

SObject secureObject = securityDecision.getRecords()[0];

fieldValue = String.valueOf(secureObject.get(field));

if (fieldValue == null || fieldValue == '') {
String errorMsg =
'We can\'t display the timeline. The field requested has no value. ' +
'Populate a value and try again. ' +
'(' +
fieldLabel +
')';

throw new TimelineSetupException('{"type": "No-Data", "message": "' + errorMsg + '"}');
}

parentObjectType = String.valueOf(Id.valueOf(fieldValue).getSobjectType());
}

if (parentObjectType == 'Account' && isPersonAccount(fieldValue)) {
parentConfigType = 'PersonAccount';
} else {
parentConfigType = parentObjectType;
}

parentDetails.put('Id', fieldValue);
parentDetails.put('QueryObjectType', parentObjectType);
parentDetails.put('ConfigObjectType', parentConfigType);

return parentDetails;
}

private class TimelineRecord { //NOPMD
private String relationshipName;
private String parentObject;
Expand Down
Loading

0 comments on commit 2e0f04b

Please sign in to comment.