diff --git a/force-app/main/default/classes/CacheListViewConfig.cls b/force-app/main/default/classes/CacheListViewConfig.cls index b1d0298..45e390a 100644 --- a/force-app/main/default/classes/CacheListViewConfig.cls +++ b/force-app/main/default/classes/CacheListViewConfig.cls @@ -1,47 +1,47 @@ -/** - * @description : - * @author : tom@ansleyllc.com - * @last modified on : 10-16-2022 - * @last modified by : -**/ -public with sharing class CacheListViewConfig { - - public static List_View_Config__c get(String objectName, String listViewName) - { - List_View_Config__c result = null; - - String key = CacheHelper.getValidKey(objectName + CacheHelper.SPLITTER + listViewName); - System.debug(LoggingLevel.DEBUG, 'CacheListViews.get(' + key + ')'); - if (key.length() < 50) //stupid restriction on key size!! - { - result = (List_View_Config__c) CacheHelper.PARTITION.get(key); - if (result == null) - { - System.debug(LoggingLevel.DEBUG, 'RESULT IS NULL'); - result = getData(objectName, listViewName); - System.debug(LoggingLevel.DEBUG, 'RESULT IS NOW ' + result); - if (result != null) - CacheHelper.PARTITION.put(key, result); - } else { - System.debug(LoggingLevel.DEBUG, 'RESULT NOT NULL - ' + result); - } - } else { - result = getData(objectName, listViewName); - } - return result; - } - - public static void remove(String objectName, String listViewName) - { - String key = CacheHelper.getValidKey(objectName + CacheHelper.SPLITTER + listViewName); - System.debug(LoggingLevel.DEBUG, 'CacheListViews.remove(' + key + ')'); - if (key.length() < 50) - CacheHelper.PARTITION.remove(key); - } - - private static List_View_Config__c getData(String objectName, String listViewName) - { - return ListViewConfigHelper.getListViewConfigCache(objectName, listViewName); - } - +/** + * @description : + * @author : tom@ansleyllc.com + * @last modified on : 10-16-2022 + * @last modified by : +**/ +public with sharing class CacheListViewConfig { + + public static List_View_Config__c get(String objectName, String listViewName) + { + List_View_Config__c result = null; + + String key = CacheHelper.getValidKey(objectName + CacheHelper.SPLITTER + listViewName); + System.debug(LoggingLevel.DEBUG, 'CacheListViews.get(' + key + ')'); + if (key.length() < 50) //stupid restriction on key size!! + { + result = (List_View_Config__c) CacheHelper.PARTITION.get(key); + if (result == null) + { + System.debug(LoggingLevel.DEBUG, 'RESULT IS NULL'); + result = getData(objectName, listViewName); + System.debug(LoggingLevel.DEBUG, 'RESULT IS NOW ' + result); + if (result != null) + CacheHelper.PARTITION.put(key, result); + } else { + System.debug(LoggingLevel.DEBUG, 'RESULT NOT NULL - ' + result); + } + } else { + result = getData(objectName, listViewName); + } + return result; + } + + public static void remove(String objectName, String listViewName) + { + String key = CacheHelper.getValidKey(objectName + CacheHelper.SPLITTER + listViewName); + System.debug(LoggingLevel.DEBUG, 'CacheListViews.remove(' + key + ')'); + if (key.length() < 50) + CacheHelper.PARTITION.remove(key); + } + + private static List_View_Config__c getData(String objectName, String listViewName) + { + return ListViewConfigHelper.getListViewConfigCache(objectName, listViewName); + } + } \ No newline at end of file diff --git a/force-app/main/default/classes/HelperSchema.cls b/force-app/main/default/classes/HelperSchema.cls index a9f9cbb..327aa2d 100644 --- a/force-app/main/default/classes/HelperSchema.cls +++ b/force-app/main/default/classes/HelperSchema.cls @@ -1,1448 +1,1448 @@ -/** - * @File Name : HelperSchema.cls - * @Description : - * @Author : tom@ansleyllc.com - * @Group : - * @Last Modified By : tom@ansleyllc.com - * @Last Modified On : 07-30-2024 - * @Modification Log : - * Ver Date Author Modification - * 1.0 06/11/2020 tom@ansleyllc.com Initial Version - * 2.0 07/27/2020 tom@ansleyllc.com Added getAllObjectNames(), beefed up getValueForField() to correctly handle null values, added isEditable field to the field wrapper. - * 3.0 08-02-2021 tom@ansleyllc.com Added getFieldData(Map, String, String) method for Tooling API - * 4.0 08-19-2021 tom@ansleyllc.com Added getObjectPluralName(String) method, added getFieldLabel(String, String) - * 5.0 10-19-2021 tom@ansleyllc.com Added getAllObjectsByName() method, resolved null pointer exception with getObjectLabel(String) - * 5.0 10-27-2021 tom@ansleyllc.com Added getSFDCFieldLabel() method, resolved issue where getting column labels from first row and no data returns empty column name - * 6.0 12-15-2021 tom@ansleyllc.com Removed old methods for checking object accessibility. - * 7.0 12-15-2021 tom@ansleyllc.com Added getClassType() method. -**/ -public with sharing class HelperSchema { - - public static Map objectDescribeByName = new Map(); - public static Map objectDescribeByPrefix = new Map(); - public static Map> objectDescribeFieldsByObjectNameAndKey = new Map>(); - public static Map fieldLabels = new Map(); - public static Map validFieldNames = new Map(); - public static Set objectPermissions = new Set(); - public static Map fieldLookupObjs = new Map(); - public static Map fieldRelType = new Map(); - - public static final String LABEL = 'label'; - public static final String NAME = 'name'; - public static final String DOMAIN_NAME = [SELECT NamespacePrefix FROM Organization LIMIT 1].NamespacePrefix + '__'; - - public static final String ACCESSIBLE = 'accessible'; - public static final String CREATABLE = 'creatable'; - public static final String DELETABLE = 'deletable'; - public static final String MERGEABLE = 'mergeable'; - public static final String QUERYABLE = 'queryable'; - public static final String SEARCHABLE = 'searchable'; - public static final String UNDELETABLE = 'undeletable'; - public static final String UPDATEABLE = 'updateable'; - - public static final String REL_TYPE_FIELD = 'field'; - public static final String REL_TYPE_CORE_ID = 'coreid'; - public static final String REL_TYPE_LOOKUP = 'lookup'; - public static final String REL_TYPE_CHILD_REL = 'childrel'; - - public static final String SYS_VAR_HAS_NAMESPACE = 'HasNameSpace'; - public static final String SLVE_NAMESPACE = 'simpli_lv_ent'; - - /** - * @description Method to determine if a package has been installed on the calling org. The response is placed into Cache. - * @author tom@ansleyllc.com | 07-01-2024 - * @param namespace the namespace of the package being checked for. - * @return Boolean true = installed, false = not installed - - System.debug(LOggingLevel.DEBUG, 'Is Installed - ' + HelperSchema.isPackageInstalled(HelperSchema.SLVE_NAMESPACE)); - **/ - public static Boolean isPackageInstalled(String namespace) - { - Boolean hasNameSpace = null; - Object response = CacheSystemConfig.get(SYS_VAR_HAS_NAMESPACE); - if (response == null) - { - List packages = [SELECT Id FROM PackageLicense WHERE NamespacePrefix = :namespace]; - - if (packages.isEmpty()) - hasNameSpace = false; - else - hasNameSpace = true; - - CacheSystemConfig.put(SYS_VAR_HAS_NAMESPACE, hasNameSpace); - } else { - hasNameSpace = (Boolean) response; - } - - return hasNameSpace; - } - - public static Map getAllObjectsByName() - { - Map describe = Schema.getGlobalDescribe(); - - return describe; - } - - /* - System.debug(LoggingLevel.DEBUG, 'RESULT - ' + HelperSchema.getHasRecordTypes('Account')); - System.debug(LoggingLevel.DEBUG, 'RESULT - ' + HelperSchema.getHasRecordTypes('Opportunity')); - System.debug(LoggingLevel.DEBUG, 'RESULT - ' + HelperSchema.getHasRecordTypes('Contact')); - System.debug(LoggingLevel.DEBUG, 'RESULT - ' + HelperSchema.getHasRecordTypes('simpli_lv__List_View__c')); - System.debug(LoggingLevel.DEBUG, 'RESULT - ' + HelperSchema.getHasRecordTypes('Task')); - System.debug(LoggingLevel.DEBUG, 'RESULT - ' + HelperSchema.getHasRecordTypes('Event')); - */ - public static Boolean getHasRecordTypes(String obj) - { - Boolean result = false; - Schema.DescribeSObjectResult objSchema = getObjectSchema(obj); - - for (Schema.RecordTypeInfo rtInfo: objSchema.getRecordTypeInfos()) - { - if (rtInfo.isActive() && !rtInfo.isMaster()) - { - result = true; - break; - } - } - - return result; - } - - public static Id getMasterRecordType(String obj) - { - String recordTypeId = null; - Schema.DescribeSObjectResult objSchema = getObjectSchema(obj); - - for (Schema.RecordTypeInfo rtInfo: objSchema.getRecordTypeInfos()) - { - if (rtInfo.isActive() && rtInfo.isMaster()) - { - recordTypeId = rtInfo.getRecordTypeId(); - break; - } - } - - return recordTypeId; - } - - public static String getRecordTypeId(String recordId) - { - String objApiName = HelperSchema.getObjectTypeFromId(recordId); - - if (getHasRecordTypes(objApiName)) - return getRecordTypeId(Database.query('SELECT Id, RecordTypeId FROM ' + objApiName + ' WHERE Id = :recordId')); - else - return getRecordTypeId(Database.query('SELECT Id FROM ' + objApiName + ' WHERE Id = :recordId')); - } - - /* - HelperSchema.getRecordTypeId - */ - public static String getRecordTypeId(SObject obj) - { - String recordTypeId = null; - String objApiName = obj.getSObjectType().getDescribe().getName(); - Schema.DescribeSObjectResult objSchema = getObjectSchema(objApiName); - - try { - if (getHasRecordTypes(objApiName)) - { - recordTypeId = (String) obj.get('RecordTypeId'); - System.debug(LoggingLevel.DEBUG, 'Using PROVIDED record type Id ' + recordTypeId); - if (String.isEmpty(recordTypeId)) - { - recordTypeId = getMasterRecordType(objApiName); - System.debug(LoggingLevel.DEBUG, 'Null record type so falling back to MASTER record type Id ' + recordTypeId); - } - } else { - recordTypeId = getMasterRecordType(objApiName); - System.debug(LoggingLevel.DEBUG, 'Using MASTER record type Id ' + recordTypeId); - } - } catch (Exception e) { - System.debug(LoggingLevel.DEBUG, 'Record type id field does not exist for ' + obj.getSObjectType().getDescribe().getName()); - recordTypeId = getMasterRecordType(objApiName); - System.debug(LoggingLevel.DEBUG, 'Using MASTER record type Id ' + recordTypeId); - } - - System.debug(LoggingLevel.DEBUG, 'Output recordTypeId - ' + recordTypeId); - return recordTypeId; - - } - - /* - * Method to get an SObject's type from its Id. - */ - public static String getSObjectTypeFromId(Id id) - { - return id.getSObjectType().getDescribe().getName(); - } - - /* - * Method to return the object that a given objects field looks up to. For example, if the - * obj = User and the field = ProfileId the value returned will be "Profile" - System.debug(LoggingLevel.FINE, 'RESULT - ' + HelperSchema.getFieldLookupObject('Account', 'LastModifiedById')); - - */ - public static String getFieldLookupObject(String obj, String field) - { - System.debug(LoggingLevel.DEBUG, 'Starting getFieldLookupObject(' + obj + ',' + field + ')'); - - String objString = null; - - String key = obj + ':' + field; - - objString = fieldLookupObjs.get(key); - if (!String.isEmpty(objString)) - { - System.debug(LoggingLevel.DEBUG, 'USED CACHE(getFieldLookupObject)'); - return objString; - } - - Schema.SObjectField fieldSchema = getFieldByKey(obj, field, NAME); - - if (fieldSchema == null) throw new ListViewException('Cannot find provided field - ' + obj + '.' + field + '. Please ensure to use field API name, not label name'); - - Schema.DescribeFieldResult fDescribe = fieldSchema.getDescribe(); - - //if (fDescribe != null) throw new ListViewException('FDescribe - ' + fDescribe); - - if (!fDescribe.getReferenceTo().isEmpty()) - objString = fDescribe.getReferenceTo()[0].getDescribe().getName(); - else { - objString = obj; - } - - fieldLookupObjs.put(key, objString); - - return objString; - - } - - public static String getFieldRelationshipType(String obj, String field) - { - System.debug(LoggingLevel.DEBUG, 'Starting getFieldRelationshipType(' + obj + ',' + field + ')'); - - obj = obj.replace('__r', '__c'); - - String relType = null; - String key = obj + ':' + field; - - relType = fieldRelType.get(key); - if (!String.isEmpty(relType)) - { - System.debug(LoggingLevel.DEBUG, 'USED CACHE(getFieldRelationshipType)'); - return relType; - } - - //see if there is a regular field name - Schema.SObjectField sField = getFieldByKey(obj, field, NAME); - - if (sField != null) - { - relType = REL_TYPE_FIELD; - - } else { - - //see if there is a field with an Id at the end. i.e. ContactId - sField = getFieldByKey(obj, field + 'Id', NAME); - - if (sField != null) - { - relType = REL_TYPE_CORE_ID; - - } else { - - //see if there is a lookup field name - sField = getFieldByKey(obj, field.replace('__r', '__c'), NAME); - - if (sField != null) - { - relType = REL_TYPE_LOOKUP; - } else { - List relationships = getObjectSchema(obj).getChildRelationships(); - - for (Schema.ChildRelationship relationship: relationships) - { - if (relationship.getRelationshipName() == field) - { - relType = REL_TYPE_CHILD_REL; - break; - } - } - } - } - } - - fieldRelType.put(key, relType); - return relType; - - } - - /* - * Method to return a describe result for a given object and field. - System.debug(LoggingLevel.DEBUG, 'RESULT - ' + HelperSchema.getFieldDescribeResult('Account','simpli_lv__Test_Text_Rich__c')); - */ - public static Schema.DescribeFieldResult getFieldDescribeResult(String obj, String field) - { - - System.debug(LoggingLevel.FINE, 'Starting getFieldDescribeResult(' + obj + ', ' + field + ')'); - Schema.DescribeFieldResult result = null; - - //see if there is a regular field name - Schema.SObjectField sField = getFieldByKey(obj, field, NAME); - - //see if there is a field with an Id at the end. i.e. ContactId - if (sField == null) - sField = getFieldByKey(obj, field + 'Id', NAME); - - //see if there is a lookup field name - if (sField == null) - sField = getFieldByKey(obj, field.removeEnd('__r') + '__c', NAME); - - if (sField != null) - result = sField.getDescribe(); - - //see if we are working with a relationship field - if (result == null) - { - List relationships = getObjectSchema(obj).getChildRelationships(); - - for (Schema.ChildRelationship relationship: relationships) - { - if (relationship.getRelationshipName() == field) - { - result = relationship.getField().getDescribe(); - break; - } - } - } - - System.debug(LoggingLevel.FINE, 'Ending getFieldDescribeResult(' + result + ')'); - - return result; - } - - /** - * @description Method to get a map of all object names and API names - * @author tom@ansleyllc.com | 07-07-2021 - **/ - public static Map getAllObjectNames() - { - Map objMap = new Map(); - for (Schema.SObjectType o : Schema.getGlobalDescribe().values() ) - { - Schema.DescribeSObjectResult objResult = o.getDescribe(); - if (!objResult.getLabel().contains('__MISSING LABEL__') //REALLY? - && !objResult.getLabel().contains('__History') - && !objResult.getLabel().contains('__ChangeEvent') - && !objResult.getLabel().contains('__Share')) - objMap.put(objResult.getName(), objResult.getLabel()); - } - - return objMap; - } - - /* - * Method that returns the describe result of an SObject. - - System.debug(LoggingLevel.FINE, 'RESULT - ' + HelperSchema.getObjectSchema('simpli_lv__List_View_Org_Wide_Setting__mdt')); - System.debug(LoggingLevel.FINE, 'RESULT - ' + HelperSchema.getObjectSchema('Bogus')); - Schema.DescribeSObjectResult objDescribeSobject = HelperSchema.getObjectSchema('Event')); - Map mapFields = objDescribeSobject.fields.getMap(); - List lstPickListValues = mapFields.get(strPicklistField).getDescribe().getPickListValues(); - for (Schema.PicklistEntry objPickList : lstPickListValues) { - System.debug('Value = ' +objPickList.getValue() +' , Label = ' +objPickList.getLabel()); - } - */ - public static Schema.DescribeSObjectResult getObjectSchema(String name) - { - Schema.DescribeSObjectResult result = null; - Schema.SObjectType objType = objectDescribeByName.get(name); - if (objType == null) - { - initSObjectSchema(name, false); - objType = objectDescribeByName.get(name); - if (objType != null) - result = objType.getDescribe(); - } else { - result = objType.getDescribe(); - } - return result; - } - - public static String getObjectPluralName(String objName) - { - return getObjectSchema(objName).getLabelPlural(); - } - - public static String getObjectLabelCache(String objName) - { - Schema.DescribeSObjectResult result = getObjectSchema(objName); - if (result != null) - return result.getLabel(); - else - return ''; - } - - /* - System.debug(LoggingLevel.FINE, 'FAKE RESULT - ' + HelperSchema.isObject('Fake Object')); - System.debug(LoggingLevel.FINE, 'FAKE RESULT - ' + HelperSchema.isObject('ApexLog')); - */ - public static Boolean isObject(String objName) - { - Boolean isObject = false; - - if (objectDescribeByName.get(objName) == null) - { - initSObjectSchema(objName, false); - if (objectDescribeByName.get(objName) != null) - isObject = true; - } else { - isObject = true; - } - return isObject; - } - - /* - * Returns the API object type for a provided lookup field name. Currently - * this method does not work with hierarchical field names - * - * Method will return an empty string if the field is not a lookup field. - * - System.debug(LoggingLevel.FINE, 'RESULT 1 - ' + HelperSchema.getObjectTypeForField('Contact', 'Name')); - System.debug(LoggingLevel.FINE, 'RESULT 2 - ' + HelperSchema.getObjectTypeForField('Contact', 'AccountId')); - */ - public static String getObjectTypeForField(String obj, String field) - { - String objType = ''; - - Schema.SObjectField fieldSchema = getFieldByKey(obj, field, NAME); - - List sObjTypes = fieldSchema.getDescribe().getReferenceTo(); - - if (!sObjTypes.isEmpty()) - objType = sObjTypes[0].getDescribe().getName(); - - System.debug(LoggingLevel.DEBUG, 'Type from field - ' + obj + '/' + field + ' - ' + objType); - return objType; - } - - /* - * Returns the API object name based on a provided Id. Record IDs are prefixed - * with three-character codes that specify the type of the object (for example, - * accounts have a prefix of 001 and opportunities have a prefix of 006). - - System.debug(LoggingLevel.DEBUG, 'RESULT - ' + HelperSchema.getObjectTypeFromId('00G3h000000GElpEAG')); //Group - */ - public static String getObjectTypeFromId(Id objId) - { - if (String.isEmpty(objId)) - return 'BAD ID'; - - if (objectDescribeByPrefix.isEmpty()) - initObjectDescribeByPrefix(); - - String objType = objectDescribeByPrefix.get(String.valueOf(objId).substring(0,3)).getDescribe().getName(); - System.debug(LoggingLevel.DEBUG, 'Type from id result - ' + objId + '/' + objType); - return objType; - } - - public static String getObjectType(SObject obj) - { - return obj.getSObjectType().getDescribe().getName(); - } - - public static Schema.SObjectField getFieldSchema(String obj, String field) - { - return getFieldByKey(obj, field, NAME); - } - - public static String getFieldLabel(String obj, String field) - { - String label = ''; - Schema.SObjectField objField = getFieldByKey(obj, field, NAME); - - if (objField != null) - label = objField.getDescribe().getLabel(); - - return label; - - } - - public static Schema.DisplayType getFieldType(String obj, String field) - { - - Schema.DisplayType fieldType = null; - - Schema.SObjectField objField = getFieldByKey(obj, field, NAME); - - if (objField != null) - fieldType = objField.getDescribe().getType(); - - return fieldType; - - } - - /* - * Method which, given an object type and field will convert the provided value into the - * correct object based on the provided fields type. - */ - public static Object getValueForField(String obj, String field, String value) - { - System.debug(LoggingLevel.FINE, 'Starting getValueForField - ' + obj + '/' + field + '/' + value); - Object objValue = null; - Schema.DisplayType displayType = getFieldType(obj, field); - - if (displayType != null) - { - if (displayType == Schema.DisplayType.Boolean) return Boolean.valueOf(value); - else if (displayType == Schema.DisplayType.Double) { - if (String.isEmpty(value)) return null; - objValue = Double.valueOf(value); - } - else if (displayType == Schema.DisplayType.Base64) return Blob.valueOf(value); - else if (displayType == Schema.DisplayType.Currency) { - if (String.isEmpty(value)) return null; - objValue = Double.valueOf(value); - } - else if (displayType == Schema.DisplayType.Date) { - if (String.isEmpty(value)) return null; - objValue = Date.valueOf(value); - } - else if (displayType == Schema.DisplayType.Integer) { - if (String.isEmpty(value)) return null; - objValue = Integer.valueOf(value); - } - else if (displayType == Schema.DisplayType.Percent) { - if (String.isEmpty(value)) return null; - objValue = Double.valueOf(value); - } - else if (displayType == Schema.DisplayType.DateTime) { - if (String.isEmpty(value)) return null; - objValue = DateTime.valueOfGmt(value.replace('T', ' ').remove('.000Z')); //2021-07-21 20:45:00 - } - else if (displayType == Schema.DisplayType.Time) { - if (String.isEmpty(value)) return null; - objValue = Time.newInstance(Integer.valueOf(value.substring(0, 2)), - Integer.valueOf(value.substring(3, 5)), - Integer.valueOf(value.substring(6, 8)), - Integer.valueOf(value.substring(9).removeEnd('Z'))); //03:15:00.000 - } - else objValue = value; - } - System.debug(LoggingLevel.FINE, 'Finished getValueForField - ' + obj + '/' + field + '/' + objValue); - - return objValue; - } - - /* - * Method which, given an object API name and a field label or name returns the schema of the field. - System.debug(LoggingLevel.FINE, 'Field Data - ' + HelperSchema.getFieldByKey('Account', 'LastModifiedBy', HelperSchema.NAME)); - System.debug(LoggingLevel.FINE, 'Field Data - ' + HelperSchema.getFieldByKey('Account', 'LastModifiedBy', HelperSchema.NAME)); - */ - public static Schema.SObjectField getFieldByKey(String obj, String key, String keyType) - { - System.debug(LoggingLevel.DEBUG, 'Starting getFieldByKey(' + obj + ', ' + key + ', ' + keyType + ')'); - //get the fields of the object in question - Map fieldsByKeyType = objectDescribeFieldsByObjectNameAndKey.get(obj + ':' + keyType); - - //if we cannot find the data we initialize and try again.....this is to reduce load - if (fieldsByKeyType == null) - { - initSObjectFieldSchema(obj, keyType); - fieldsByKeyType = objectDescribeFieldsByObjectNameAndKey.get(obj + ':' + keyType); - } - - if (fieldsByKeyType == null) - { - initSObjectSchema(obj, true); - fieldsByKeyType = objectDescribeFieldsByObjectNameAndKey.get(obj + ':' + keyType); - } - - //get the field by key - Schema.SObjectField field = fieldsByKeyType.get(key.toLowerCase()); - - return field; - - } - - /* - * Method which given an object API name returns those fields that are available for the object. - * The returned map has its keys based on either the fields Label or Name, depending on the - * provided keyType. - System.debug(LoggingLevel.FINE, 'RESULT - ' + HelperSchema.getFieldsForObject('simpli_lv__List_View_Org_Wide_Setting__mdt', HelperSchema.NAME)); - Map fields = HelperSchema.getFieldsForObject('Account', HelperSchema.NAME); - for (Schema.SObjectField field: fields.values()) - System.debug(LoggingLevel.FINE, 'FIELD - ' + field); - */ - public static Map getFieldsForObject(String obj, String keyType) - { - initSObjectFieldSchema(obj, keyType); - - //get the fields of the object in question - Map fieldsByKeyType = objectDescribeFieldsByObjectNameAndKey.get(obj + ':' + keyType); - - if (fieldsByKeyType == null) - { - initSObjectSchema(obj, true); - fieldsByKeyType = objectDescribeFieldsByObjectNameAndKey.get(obj + ':' + keyType); - } - - return fieldsByKeyType; - - } - - /* - * Debugging method to show fields by key type - */ - @TestVisible - private static String getObjectFieldDebug(String obj, String keyType, Map fieldsByKeyType) - { - String debug = '\n-----------------------------------------------\n'; - debug += 'OBJ - ' + obj + '\n'; - debug += 'KEY TYPE - ' + keyType + '\n\n'; - for (String key: fieldsByKeyType.keySet()) - debug += 'KEY - ' + key + ', VALUE - ' + fieldsByKeyType.get(key) + '\n'; - debug += '-----------------------------------------------\n'; - - return debug; - } - - /* - * Method to retrieve the field label for a given object and API field name. - System.debug(LoggingLevel.DEBUG, 'Result ' + HelperSchema.getSFDCFieldLabel('simpli_lv__List_View_Action_Parameter__c','List_View_Action__r.LastModifiedBy.Name')); - System.debug(LoggingLevel.DEBUG, 'Result ' + HelperSchema.getSFDCFieldLabel('Account','User.Alias')); - */ - public static String getSFDCFieldLabel(String objName, String sfdcFieldName) - { - if (objName == null || sfdcFieldName == null) throw new ListViewException('An object (' + objName + ') and field (' + sfdcFieldName + ') name must be provided when using getSFDCFieldLabel() method'); - String label = ''; - - String key = objName + ':' + sfdcFieldName; - try { - label = fieldLabels.get(key); - if (!String.isEmpty(label)) - { - System.debug(LoggingLevel.DEBUG, 'USED CACHE(getSFDCFieldLabel)'); - return label; - } - - //scrub the field of weird stuff like spaces and toLabel() etc. - sfdcFieldName = scrubFieldName(sfdcFieldName); - - //split field into parts. - List fieldHierarchy = sfdcFieldName.split('\\.'); - System.debug(LoggingLevel.DEBUG, 'Field Hierarchy - ' + objName + '/' + fieldHierarchy); - - String objType = objName; - Schema.DescribeSObjectResult objDescribe = HelperSchema.getObjectSchema(objType); - - while (fieldHierarchy.size() > 1) { - - String currentField = fieldHierarchy.remove(0); - System.debug(LoggingLevel.DEBUG, 'Current objType/field - ' + objType + '/' + currentField); - String relType = getFieldRelationshipType(objType, currentField); - System.debug(LoggingLevel.DEBUG, 'Relationship type - ' + relType); - - if (relType == REL_TYPE_CHILD_REL) - { - List relationships = getObjectSchema(objType).getChildRelationships(); - - for (Schema.ChildRelationship relationship: relationships) - { - if (relationship.getRelationshipName() == currentField) - { - objType = relationship.getChildSObject().getDescribe().getName(); - break; - } - } - - } else if (relType == REL_TYPE_CORE_ID) - { - objType = getFieldLookupObject(objType.replace('__r', '__c'), currentField + 'Id'); - - } else if (relType == REL_TYPE_LOOKUP) - { - objType = getFieldLookupObject(objType.replace('__r', '__c'), currentField.removeEnd('__r') + '__c'); - - } else { - objType = currentField; - } - - } - - String currentField = fieldHierarchy.remove(0); - System.debug(LoggingLevel.DEBUG, 'Final objType/field - ' + objType + '/' + currentField); - - Schema.SObjectField objField = HelperSchema.getFieldByKey(objType, currentField, NAME); - if (objField != null) - label = objField.getDescribe().getLabel(); - - } catch (Exception e) { - System.debug(LoggingLevel.DEBUG, ListViewException.getExtendedString(e)); - ListViewErrorHelper.createFutureUsageError('Exception during HelperSchema.getSFDCFieldLabel(' + objName + ',' + sfdcFieldName + ') ' + ListViewException.getExtendedString(e)); - label = ''; - } - - fieldLabels.put(key, label); - System.debug(LoggingLevel.DEBUG, 'Field label result for ' + objName + '/' + sfdcFieldName + ' is ' + label); - return label; - } - - /* - * Method to determine if a given object and field name is valid. - System.debug(LoggingLevel.FINE, 'Result ' + HelperSchema.isValidSFDCFieldName('Contact', 'Email')); - */ - public static Boolean isValidSFDCFieldName(String objName, String sfdcFieldName) - { - if (objName == null || sfdcFieldName == null) return false; - Boolean isValid = false; - String key = objName + ':' + sfdcFieldName; - try { - - isValid = HelperSchema.validFieldNames.get(key); - if (isValid != null) - { - System.debug(LoggingLevel.DEBUG, 'USED CACHE(isValidSFDCFieldName)'); - return isValid; - } else { - isValid = false; - } - - //scrub the field of weird stuff like spaces and toLabel() etc. - sfdcFieldName = scrubFieldName(sfdcFieldName); - - //split field into parts. - List fieldHierarchy = sfdcFieldName.split('\\.'); - System.debug(LoggingLevel.FINE, 'Field Hierarchy - ' + fieldHierarchy); - - String objType = objName; - Schema.DescribeSObjectResult objDescribe = HelperSchema.getObjectSchema(objType); - objType = objDescribe.getName(); //it could have changed if we are working with a domain. i.e. it could go from List_View_Config_Condition__c --> simpli_lv__List_View_Config_Condition__c - - while (fieldHierarchy.size() > 1) { - - String currentField = fieldHierarchy.remove(0); - System.debug(LoggingLevel.DEBUG, 'Current objType/field - ' + objType + '/' + currentField); - String relType = getFieldRelationshipType(objType, currentField); - System.debug(LoggingLevel.DEBUG, 'Relationship type - ' + relType); - - if (relType == REL_TYPE_CHILD_REL) - { - List relationships = getObjectSchema(objType).getChildRelationships(); - - for (Schema.ChildRelationship relationship: relationships) - { - if (relationship.getRelationshipName() == currentField) - { - objType = relationship.getChildSObject().getDescribe().getName(); - break; - } - } - - } else if (relType == REL_TYPE_CORE_ID) - { - objType = getFieldLookupObject(objType, currentField + 'Id'); - - } else if (relType == REL_TYPE_LOOKUP) - { - objType = getFieldLookupObject(objType, currentField.removeEnd('__r') + '__c'); - - } else { - objType = currentField; - } - - } - - String currentField = fieldHierarchy.remove(0); - System.debug(LoggingLevel.DEBUG, 'Final objType/field - ' + objType + '/' + currentField); - - Schema.SObjectField objField = HelperSchema.getFieldByKey(objType, currentField, NAME); - if (objField != null) - isValid = true; - - - } catch (Exception e) { - ListViewErrorHelper.createFutureUsageError('Exception during HelperSchema.isValidSFDCFieldName(' + objName + ',' + sfdcFieldName + ') ' + ListViewException.getExtendedString(e)); - isValid = false; - } - - validFieldNames.put(key, isValid); - System.debug(LoggingLevel.FINE, 'Field validation result for ' + objName + '/' + sfdcFieldName + ' is ' + isValid); - return isValid; - - } - - /* - * Method to determine if a given object name is valid. - */ - public static Boolean isValidSFDCObjectName(String sfdcObjectName) - { - Boolean isValid = true; - try { - List objName = new List{sfdcObjectName}; - Schema.describeSObjects(objName); - } catch (Exception e) { - ListViewErrorHelper.createFutureUsageError('Exception during HelperSchema.isValidSFDCObjectName(' + sfdcObjectName + ') ' + ListViewException.getExtendedString(e)); - isValid = false; - } - - return isValid; - - } - - /* - * Method which given an sobject and a field name will retrieve - * the value for the field as well as field information. This data - * is returned in a structure which includes the following data - - * - * objValue - a pointer to the SObject holding the field - * name - the API name of the field - * label - the label of the field - * type - the type of the field holding the data value - * value - the value of the field. - - * - Account acc = [SELECT Name, Owner.Name, Owner.Alias, Owner.Profile.Name, Case__r.Status FROM Account WHERE Name LIKE 'Express%' LIMIT 1]; - HelperSchema.FieldData d = HelperSchema.getFieldData(acc, 'Case__r.Status'); - System.debug(LoggingLevel.FINE, 'Account Name - ' + d.name + ', ' + d.label + ', ' + d.getType() + ', ' + d.value); - * - */ - public static FieldData getFieldData(SObject obj, String fieldName) - { - System.debug(LoggingLevel.FINE, 'Starting getFieldData(' + fieldName + ', ' + obj + ')'); - FieldData data = null; - String objValueId = null; - String label = ''; - - try { - - fieldName = scrubFieldName(fieldName); - - if (obj == null || fieldName == null || fieldName == '') - throw new ListViewException('HelperSchema.getFieldData() called with empty values. obj = ' + obj + ', fieldName = ' + fieldName); - - List fieldHierarchy = fieldName.split('\\.'); - - System.debug(LoggingLevel.FINE, 'Field Hierarchy - ' + fieldHierarchy); - - SObject currentObj = obj; - - String objType = getObjectType(currentObj); - - for (String field: fieldHierarchy) - { - - //if we are at the last field then get the value and return - if (fieldHierarchy.indexOf(field) == fieldHierarchy.size()-1) - { - //get the object type - System.debug(LoggingLevel.FINE, 'Object Type - ' + objType); - System.debug(LoggingLevel.FINE, 'Field - ' + field); - - - //get the field describe - Schema.DescribeFieldResult fieldDesc = getFieldDescribeResult(objType, field); - - //if we do not have a field describe it could be because there is no data in the relationship - if (fieldDesc == null) { - data = new FieldData(field, label, Schema.DisplayType.STRING, '', currentObj); - System.debug(LoggingLevel.ERROR, 'Cannot find field with name ' + field + ' for object of type ' + objType); - - //if there is a field describe then get the label and set the data. - } else { - - label = fieldDesc.getLabel(); - - //if we have a lookup field then determine if we add the object name to the label - String addObjName = ListViewConfigHelper.getOrgWideConfigParam('AddObjNameOnLookupLabels'); - if (addObjName == ListViewHelper.TTRUE && fieldName.contains('.')) - label = objType + ' ' + fieldDesc.getLabel(); - - data = new FieldData(field, label, fieldDesc.getType(), currentObj.get(field), currentObj); - data.parentObjType = getObjectType(currentObj); - //data.objValueId = objValueId; - data.isEditable = fieldDesc.isUpdateable(); - - if (fieldDesc.isHtmlFormatted()) - data.isHTML = true; - } - - //if we are not at the last field then get the fields object - } else { - System.debug(LoggingLevel.FINE, 'Not at last field. Current field - ' + field); - Boolean isSet = false; - - System.debug(LoggingLevel.FINE, 'old currentObj - ' + currentObj); - - Map fieldsToValue = currentObj.getPopulatedFieldsAsMap(); - - //if there is a value for the field - if (fieldsToValue.get(field) != null) - { - Object tmpObj = fieldsToValue.get(field); - - if (tmpObj instanceof List) - currentObj = ((List) tmpObj)[0]; - else if (tmpObj instanceof SObject) - currentObj = (SObject) tmpObj; - - //if there is no value - } else { - System.debug(LoggingLevel.FINE, 'No field value for name ' + field); - //if we are trying to retrieve column data we need to get the label....so we get the label if we can. - if (label == '') - label = getSFDCFieldLabel(objType, fieldName); - data = new FieldData(field, label, Schema.DisplayType.STRING, '', currentObj); - return data; - } - - System.debug(LoggingLevel.FINE, 'new currentObj - ' + currentObj); - - if (currentObj != null) - { - isSet = true; - objType = getObjectType(currentObj); - - //only provide the Id value if there are only two fields in the hierarchy. i.e. CreatedBy.Name - if (objValueId == null && fieldHierarchy.size() == 2) - { - objValueId = String.valueOf(currentObj.get('Id')); - } - - } else { - //we might have a currentObj with no data so it bugs out. But, if this is the case - //and we are trying to retrieve column data that is bad....so we get the label if we can. - if (label == '') - label = getSFDCFieldLabel(objType, fieldName); - data = new FieldData(field, label, Schema.DisplayType.STRING, '', null); - data.isChildRel = true; - return data; - } - - } - } - } catch (Exception e) { - System.debug(LoggingLevel.ERROR, e.getMessage() + ' - ' + e.getStackTraceString()); - //ListViewErrorHelper.createFutureUsageError('Exception during HelperSchema.getFieldData(' + obj + ',' + fieldName + ') ' + ListViewException.getExtendedString(e)); - data = null; - } - - if (data != null) - System.debug(LoggingLevel.FINE, data.getDebugString()); - - return data; - } - - /** - * @description Method which, given a field name and an object returns the field data. - * This method is used for JSON structures returned via API's. For regular - * SObjects the getFieldData(SObject, String) method should be used. - * @author tom@ansleyllc.com | 08-01-2021 - * @param structure the structure holding the data necessary to find the field value - * @param fieldName the field name - * @return FieldData - **/ - public static FieldData getFieldData(Map structure, String fieldName, String objType) - { - System.debug(LoggingLevel.FINE, 'Starting getFieldData(' + fieldName + ', ' + objType + ', ' + structure + ')'); - - FieldData data = null; - String objValueId = null; - String label = ''; - - try { - - fieldName = scrubFieldName(fieldName); - - if (structure == null || fieldName == null || fieldName == '') - throw new ListViewException('HelperSchema.getFieldData() called with empty values. obj = ' + structure + ', fieldName = ' + fieldName); - - List fieldHierarchy = fieldName.split('\\.'); - - System.debug(LoggingLevel.FINE, 'Field Hierarchy - ' + fieldHierarchy); - - Map currentStructure = structure; - String currentParentObjType = objType; - - for (String field: fieldHierarchy) - { - - //if we are at the last field then get the value and return - if (fieldHierarchy.indexOf(field) == fieldHierarchy.size()-1) - { - System.debug(LoggingLevel.FINE, 'Field - ' + field); - System.debug(LoggingLevel.FINE, 'Value - ' + currentStructure.get(field)); - System.debug(LoggingLevel.FINE, 'Parent Obj - ' + currentParentObjType); - - data = new FieldData(field, currentStructure.get(field)); - data.parentObjType = currentParentObjType; - - //get the Id if this is a lookup to another object - if (currentStructure.get('attributes') != null) - { - System.debug(LoggingLevel.FINE, 'We have attribs!'); - Map attribs = (Map) currentStructure.get('attributes'); - String url = (String) attribs.get(ListViewHelper.TYPE_URL); - data.objValueId = url.substringAfterLast('/'); - System.debug(LoggingLevel.FINE, 'Id - ' + data.objValueId); - } - - //if we are not at the last field then get the fields object - } else { - System.debug(LoggingLevel.FINE, 'Old structure - ' + currentStructure); - currentStructure = (Map) currentStructure.get(field); - currentParentObjType = field; - System.debug(LoggingLevel.FINE, 'New structure - ' + currentStructure); - } - } - } catch (Exception e) { - ListViewErrorHelper.createFutureUsageError('Exception during HelperSchema.getFieldData(' + structure + ',' + fieldName + ') ' + ListViewException.getExtendedString(e)); - data = null; - } - - if (data != null) - System.debug(LoggingLevel.FINE, data.getDebugString()); - - return data; - } - - public static String scrubFieldNameForSOQL(String fieldName) - { - if (fieldName == null) return fieldName; - //remove whitespace - fieldName = fieldName.deleteWhitespace(); - - //remove toLabel keyword - if (fieldName.contains('toLabel(')) fieldName = fieldName.substringBetween('(', ')'); - - return fieldName; - } - - public static String scrubFieldName(String fieldName) - { - if (fieldName == null) return fieldName; - //remove whitespace - fieldName = fieldName.deleteWhitespace(); - - //remove toLabel keyword - if (fieldName.contains('toLabel(')) fieldName = fieldName.substringBetween('(', ')'); - else if (fieldName.contains('convertCurrency(')) fieldName = fieldName.substringBetween('(', ')'); - - return fieldName; - } - - public static Map getPicklistMap(String obj, String field) - { - Map lstPickvals = new Map(); - - Schema.DescribeSObjectResult sObjectDesc = HelperSchema.getObjectSchema(obj); - - Map fields = sObjectDesc.fields.getMap(); - List pickListValues = fields.get(field).getDescribe().getPickListValues(); - for (Schema.PicklistEntry a : pickListValues) - { - lstPickvals.put(a.getLabel(), a.getValue()); - } - - return lstPickvals; - - } - - /* - Found at https://glyntalkssalesforce.blogspot.com/2018/08/dependent-picklist-values-in-apex.html - HelperString.debug(HelperSchema.getDependentPicklistValues('Account', 'simpli_lv__Controlled_Picklist__c'), 'VALUES'); - */ - public static Map> getDependentPicklistValues(String sObjName, String fieldName) - { - return getDependentPicklistValues(Schema.getGlobalDescribe().get(sObjName).getDescribe().fields.getMap().get(fieldName)); - } - - //public static List getDependentPicklistValues(String sObjName, String fieldName, String recordTypeId) - //{ - // return getDependentPicklistValues(Schema.getGlobalDescribe().get(sObjName).getDescribe().fields.getMap().get(fieldName)); - //} - - /* - Found at https://glyntalkssalesforce.blogspot.com/2018/08/dependent-picklist-values-in-apex.html - */ - public static Map> getDependentPicklistValues(Schema.sObjectField dependToken) - { - System.debug(LoggingLevel.DEBUG, 'Depend Token - ' + dependToken); - Schema.DescribeFieldResult depend = dependToken.getDescribe(); - System.debug(LoggingLevel.DEBUG, 'Depend - ' + depend); - Schema.sObjectField controlToken = depend.getController(); - System.debug(LoggingLevel.DEBUG, 'Control Token - ' + controlToken); - if (controlToken == null) return null; - Schema.DescribeFieldResult control = controlToken.getDescribe(); - List controlEntries = - ( control.getType() == Schema.DisplayType.Boolean - ? null - : control.getPicklistValues() - ); - - String base64map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - Map> dependentPicklistValues = new Map>(); - for (Schema.PicklistEntry entry : depend.getPicklistValues()) - { - if (entry.isActive()) - { - System.debug(LoggingLevel.DEBUG, 'ENntry - ' + entry); - - List base64chars = String.valueOf(((Map) JSON.deserializeUntyped(JSON.serialize(entry))).get('validFor')).split(''); - for ( Integer index = 0; index < (controlEntries != null ? controlEntries.size() : 2); index++ ) - { - Object controlValue = - ( controlEntries == null - ? (Object) (index == 1) - : (Object) (controlEntries[ index ].isActive() ? controlEntries[ index ].getLabel() : null) - ); - Integer bitIndex = index / 6, bitShift = 5 - Math.mod( index, 6 ); - if ( controlValue == null - || (base64map.indexOf( base64chars[ bitIndex ] ) & (1 << bitShift)) == 0 - ) continue; - if ( !dependentPicklistValues.containsKey( controlValue ) ) - { - dependentPicklistValues.put( controlValue, new List() ); - } - dependentPicklistValues.get( controlValue ).add( entry.getLabel() ); - } - } - } - return dependentPicklistValues; - } - - - /* - * Method to determine if an SObject is createable. This is used - * for security to ensure user is allowed to create records for the given object. - */ - public static Boolean checkObjectCreateable(String objName, Boolean throwExc) - { - System.debug(LoggingLevel.FINE, 'checkObjectCreateable called with objName - ' + objName); - - if (getObjectSchema(objName).isCreateable()) { - return true; - } else { - if (throwExc) - throw new ListViewException('Records of type ' + objName + ' are not creatable by this user or does not exist in this org. Please check user permissions'); - return false; - } - } - - /* - * Method to determine if an SObject is createable. This is used - * for security to ensure user is allowed to create records for the given object. - */ - public static void checkObjectCreateable(String objName) - { - checkObjectCreateable(objName, true); - } - - /* - * Method to determine if an SObject is accessible. This is used - * for security to ensure user is allowed to create records for the given object. - */ - public static Boolean checkObjectAccessible(String objName, Boolean throwExc) - { - System.debug(LoggingLevel.FINE, 'checkObjectAccessible called with objName - ' + objName); - - //if we are using Tooling API the object might not be in the schema. - if (getObjectSchema(objName) != null) - { - if (getObjectSchema(objName).isAccessible()) { - return true; - } else { - if (throwExc) - throw new ListViewException('Records of type ' + objName + ' are not accessible. Please check user permissions'); - return false; - } - } - - return true; - } - - /* - * Method to determine if an SObject is accessible. This is used - * for security to ensure user is allowed to create records for the given object. - */ - public static void checkObjectAccessible(String objName) - { - checkObjectAccessible(objName, true); - } - - /* - * Method to determine if an SObject is createable. This is used - * for security to ensure user is allowed to create records for the given object. - */ - public static Boolean checkObjectUpdateable(String objName, Boolean throwExc) - { - System.debug(LoggingLevel.FINE, 'checkObjectUpdateable called with objName - ' + objName); - - if (getObjectSchema(objName).isUpdateable()) { - return true; - } else { - if (throwExc) - throw new ListViewException('Records of type ' + objName + ' are not updateable or do not exist. Please check user permissions'); - return false; - } - } - - /* - * Method to determine if an SObject is createable. This is used - * for security to ensure user is allowed to create records for the given object. - */ - public static void checkObjectUpdateable(String objName) - { - checkObjectUpdateable(objName, true); - } - - /* - * Method to determine if an SObject is deletable. This is used - * for security to ensure data being queried is deletable by the user. - */ - public static Boolean checkObjectDeletable(SObject obj, Boolean throwExc) - { - System.debug(LoggingLevel.FINE, 'checkObjectDeletable called with obj - ' + obj); - - if (obj == null || obj.Id == null) - throw new ListViewException('HelperSchema.checkObjectDeletable called with null Object or object with empty Id'); - - String objName = getSObjectTypeFromId(obj.Id); - - if (getObjectSchema(objName).isDeletable()) { - return true; - } else { - if (throwExc) - throw new ListViewException('Records of type ' + objName + ' are not deletable or do not exist. Please check user permissions'); - return false; - } - } - - /* - * Method to determine if an SObject is deletable. This is used - * for security to ensure data being queried is deletable by the user. - */ - public static void checkObjectDeletable(SObject obj) - { - checkObjectDeletable(obj, true); - } - - /* - * Method to determine if an SObject type is deletable. This is used - * for security to ensure data being queried is deletable by the user. - */ - public static Boolean checkObjectDeletable(String objName, Boolean throwExc) - { - System.debug(LoggingLevel.FINE, 'checkObjectDeletable called with objName - ' + objName); - - if (getObjectSchema(objName).isDeletable()) { - return true; - } else { - if (throwExc) - throw new ListViewException('Records of type ' + objName + ' are not deletable or do not exist. Please check user permissions'); - return false; - } - } - - /* - * Method to determine if an SObject type is deletable. This is used - * for security to ensure data being queried is deletable by the user. - */ - public static void checkObjectDeletable(String objName) - { - checkObjectDeletable(objName, true); - } - - public static String getDMLDecisionDebug(String objAPIName, SObjectAccessDecision dec) - { - String log = '\n\n--------------- Decision ---------------\n'; - log += 'Object - ' + objAPIName + '\n'; - for (Integer index: dec.getModifiedIndexes()) - log += index + ', '; - log.removeEnd(', '); - - for (String field: dec.getRemovedFields().keySet()) - log += field + dec.getRemovedFields().get(field) + '\n'; - - log += '----------------------------------------\n'; - - return log; - - } - - //------------------------------------------------ - // PRIVATE METHODS BELOW - //------------------------------------------------ - - private static void initObjectDescribeByPrefix() - { - objectDescribeByPrefix = new Map(); - Map describe = Schema.getGlobalDescribe(); - for(String s:describe.keyset()) - objectDescribeByPrefix.put(describe.get(s).getDescribe().getKeyPrefix(), describe.get(s)); - } - - private static void initSObjectFieldSchema(String obj, String keyType) - { - Schema.SObjectType objSchema = Schema.getGlobalDescribe().get(obj); - if (objSchema != null && objectDescribeFieldsByObjectNameAndKey.get(obj + ':' + keyType) == null) - { - System.debug(LoggingLevel.FINE, 'Found schema with name - ' + obj); - - - List fields = objSchema.getDescribe().fields.getMap().values(); - - Map fieldsByLabel = new Map(); - Map fieldsByName = new Map(); - - for (Schema.SObjectField objField: fields) - { - fieldsByName.put(objField.getDescribe().getName().toLowerCase(), objField); - fieldsByLabel.put(objField.getDescribe().getLabel().toLowerCase(), objField); - } - objectDescribeFieldsByObjectNameAndKey.put(obj + ':' + LABEL, fieldsByLabel); - objectDescribeFieldsByObjectNameAndKey.put(obj + ':' + NAME, fieldsByName); - - //if schema contain "simpli_lv" then remove and try again (this is the hack) - if (obj.contains(DOMAIN_NAME)) - { - objectDescribeByName.put(obj.removeStart(DOMAIN_NAME), objSchema); - objectDescribeFieldsByObjectNameAndKey.put(obj.removeStart(DOMAIN_NAME) + ':' + LABEL, fieldsByLabel); - objectDescribeFieldsByObjectNameAndKey.put(obj.removeStart(DOMAIN_NAME) + ':' + NAME, fieldsByName); - } - } - } - - private static void initSObjectSchema(String obj, Boolean includeFields) - { - initSObjectSchema(obj, true, includeFields); - } - - /* - * Method to initialize an sobject schema if it hasn't already been initialized. This has a small hack - * in it to detect if an object name being requested needs a domain prefix. If it does then it rerequests - * the schema. Once the schema is returned it places the schema into the map with both the domain prefix - * and without it. This hack ensures that tests will run in both the package dev org and other orgs. - * HelperSchema.initSObjectSchema('simpli_lv__List_View__c'); - * HelperSchema.initSObjectSchema(''); - */ - private static void initSObjectSchema(String obj, Boolean allowRecurring, Boolean includeFields) - { - //if this object has not been described yet get the data - if (objectDescribeByName.get(obj) == null) - { - System.debug(LoggingLevel.FINE, 'Trying to initialize schema with name - ' + obj); - Schema.SObjectType objSchema = Schema.getGlobalDescribe().get(obj); - if (objSchema != null) - { - System.debug(LoggingLevel.FINE, 'Found schema with name - ' + obj); - objectDescribeByName.put(obj, objSchema); - - if (obj.contains(DOMAIN_NAME)) - objectDescribeByName.put(obj.removeStart(DOMAIN_NAME), objSchema); - - if (includeFields) - { - initSObjectFieldSchema(obj, LABEL); - } - - } else if (allowRecurring) { - System.debug(LoggingLevel.FINE, 'NO schema with name - ' + obj); - initSObjectSchema(DOMAIN_NAME + obj, false, includeFields); - } - } - - } - - public static Type getClassType(String typeStr) - { - Type t = null; - Integer index = typeStr.indexOf('.'); - if (index != -1) { - // With namespace "ns.Type" - t = Type.forName(typeStr.substring(0, index), typeStr.substring(index + 1, typeStr.length())); - } - - if (t == null) { - // Just "Type" - t = Type.forName(null, typeStr); - } - - if (t == null) - throw new ListViewException('Provided type not found - ' + typeStr); - return t; - } - - //------------------------------------------------ - // INNER CLASSES - //------------------------------------------------ - - public class FieldData - { - - public SObject objValue {get; set;} - public String parentObjType {get; set;} - public String name {get; set;} - public String label {get; set;} - private Schema.DisplayType type {get; set;} - public Object value {get; set;} //holds the string value. i.e. CreatedBy.Name = "Tom Ansley" - public String objValueId {get; set;} //holds the id value if its a lookup. i.e. CreatedBy.Name = 0053h000000xrj3AAA - public Boolean isChildRel {get; set;} - public Boolean isEditable {get; set;} - public Boolean isHTML {get; set;} //identifies if its a rich text field - - public FieldData(String name, String label, Schema.DisplayType type, Object value, SObject objValue) - { - this.objValue = objValue; - this.name = name; - this.label = label; - this.type = type; - this.value = value; - this.isChildRel = false; - this.isEditable = false; - this.parentObjType = ''; - this.isHTML = false; - if (objValue != null) - objValueId = String.valueOf(objValue.get('Id')); - } - - public FieldData(String name, Object value) - { - this.name = name; - this.value = value; - this.isChildRel = false; - this.isEditable = false; - this.parentObjType = ''; - this.isHTML = false; - } - - public String getType() { - if (isHTML) - return 'rich textarea'; - else - return type.name().toLowerCase(); - } - - public String getDebugString() - { - String debug = '\n\n----------------\n'; - debug += 'objValue - ' + objValue + '\n'; - debug += 'parentObjType - ' + parentObjType + '\n'; - debug += 'name - ' + name + '\n'; - debug += 'label - ' + label + '\n'; - debug += 'type - ' + type + '\n'; - debug += 'value - ' + value + '\n'; - debug += 'objValueId - ' + objValueId + '\n'; - debug += 'isChildRel - ' + isChildRel + '\n'; - debug += 'isEditable - ' + isEditable + '\n'; - debug += 'isHTML - ' + isHTML + '\n'; - debug += '----------------\n'; - - return debug; - } - } - - +/** + * @File Name : HelperSchema.cls + * @Description : + * @Author : tom@ansleyllc.com + * @Group : + * @Last Modified By : tom@ansleyllc.com + * @Last Modified On : 07-30-2024 + * @Modification Log : + * Ver Date Author Modification + * 1.0 06/11/2020 tom@ansleyllc.com Initial Version + * 2.0 07/27/2020 tom@ansleyllc.com Added getAllObjectNames(), beefed up getValueForField() to correctly handle null values, added isEditable field to the field wrapper. + * 3.0 08-02-2021 tom@ansleyllc.com Added getFieldData(Map, String, String) method for Tooling API + * 4.0 08-19-2021 tom@ansleyllc.com Added getObjectPluralName(String) method, added getFieldLabel(String, String) + * 5.0 10-19-2021 tom@ansleyllc.com Added getAllObjectsByName() method, resolved null pointer exception with getObjectLabel(String) + * 5.0 10-27-2021 tom@ansleyllc.com Added getSFDCFieldLabel() method, resolved issue where getting column labels from first row and no data returns empty column name + * 6.0 12-15-2021 tom@ansleyllc.com Removed old methods for checking object accessibility. + * 7.0 12-15-2021 tom@ansleyllc.com Added getClassType() method. +**/ +public with sharing class HelperSchema { + + public static Map objectDescribeByName = new Map(); + public static Map objectDescribeByPrefix = new Map(); + public static Map> objectDescribeFieldsByObjectNameAndKey = new Map>(); + public static Map fieldLabels = new Map(); + public static Map validFieldNames = new Map(); + public static Set objectPermissions = new Set(); + public static Map fieldLookupObjs = new Map(); + public static Map fieldRelType = new Map(); + + public static final String LABEL = 'label'; + public static final String NAME = 'name'; + public static final String DOMAIN_NAME = [SELECT NamespacePrefix FROM Organization LIMIT 1].NamespacePrefix + '__'; + + public static final String ACCESSIBLE = 'accessible'; + public static final String CREATABLE = 'creatable'; + public static final String DELETABLE = 'deletable'; + public static final String MERGEABLE = 'mergeable'; + public static final String QUERYABLE = 'queryable'; + public static final String SEARCHABLE = 'searchable'; + public static final String UNDELETABLE = 'undeletable'; + public static final String UPDATEABLE = 'updateable'; + + public static final String REL_TYPE_FIELD = 'field'; + public static final String REL_TYPE_CORE_ID = 'coreid'; + public static final String REL_TYPE_LOOKUP = 'lookup'; + public static final String REL_TYPE_CHILD_REL = 'childrel'; + + public static final String SYS_VAR_HAS_NAMESPACE = 'HasNameSpace'; + public static final String SLVE_NAMESPACE = 'simpli_lv_ent'; + + /** + * @description Method to determine if a package has been installed on the calling org. The response is placed into Cache. + * @author tom@ansleyllc.com | 07-01-2024 + * @param namespace the namespace of the package being checked for. + * @return Boolean true = installed, false = not installed + + System.debug(LOggingLevel.DEBUG, 'Is Installed - ' + HelperSchema.isPackageInstalled(HelperSchema.SLVE_NAMESPACE)); + **/ + public static Boolean isPackageInstalled(String namespace) + { + Boolean hasNameSpace = null; + Object response = CacheSystemConfig.get(SYS_VAR_HAS_NAMESPACE); + if (response == null) + { + List packages = [SELECT Id FROM PackageLicense WHERE NamespacePrefix = :namespace]; + + if (packages.isEmpty()) + hasNameSpace = false; + else + hasNameSpace = true; + + CacheSystemConfig.put(SYS_VAR_HAS_NAMESPACE, hasNameSpace); + } else { + hasNameSpace = (Boolean) response; + } + + return hasNameSpace; + } + + public static Map getAllObjectsByName() + { + Map describe = Schema.getGlobalDescribe(); + + return describe; + } + + /* + System.debug(LoggingLevel.DEBUG, 'RESULT - ' + HelperSchema.getHasRecordTypes('Account')); + System.debug(LoggingLevel.DEBUG, 'RESULT - ' + HelperSchema.getHasRecordTypes('Opportunity')); + System.debug(LoggingLevel.DEBUG, 'RESULT - ' + HelperSchema.getHasRecordTypes('Contact')); + System.debug(LoggingLevel.DEBUG, 'RESULT - ' + HelperSchema.getHasRecordTypes('simpli_lv__List_View__c')); + System.debug(LoggingLevel.DEBUG, 'RESULT - ' + HelperSchema.getHasRecordTypes('Task')); + System.debug(LoggingLevel.DEBUG, 'RESULT - ' + HelperSchema.getHasRecordTypes('Event')); + */ + public static Boolean getHasRecordTypes(String obj) + { + Boolean result = false; + Schema.DescribeSObjectResult objSchema = getObjectSchema(obj); + + for (Schema.RecordTypeInfo rtInfo: objSchema.getRecordTypeInfos()) + { + if (rtInfo.isActive() && !rtInfo.isMaster()) + { + result = true; + break; + } + } + + return result; + } + + public static Id getMasterRecordType(String obj) + { + String recordTypeId = null; + Schema.DescribeSObjectResult objSchema = getObjectSchema(obj); + + for (Schema.RecordTypeInfo rtInfo: objSchema.getRecordTypeInfos()) + { + if (rtInfo.isActive() && rtInfo.isMaster()) + { + recordTypeId = rtInfo.getRecordTypeId(); + break; + } + } + + return recordTypeId; + } + + public static String getRecordTypeId(String recordId) + { + String objApiName = HelperSchema.getObjectTypeFromId(recordId); + + if (getHasRecordTypes(objApiName)) + return getRecordTypeId(Database.query('SELECT Id, RecordTypeId FROM ' + objApiName + ' WHERE Id = :recordId')); + else + return getRecordTypeId(Database.query('SELECT Id FROM ' + objApiName + ' WHERE Id = :recordId')); + } + + /* + HelperSchema.getRecordTypeId + */ + public static String getRecordTypeId(SObject obj) + { + String recordTypeId = null; + String objApiName = obj.getSObjectType().getDescribe().getName(); + Schema.DescribeSObjectResult objSchema = getObjectSchema(objApiName); + + try { + if (getHasRecordTypes(objApiName)) + { + recordTypeId = (String) obj.get('RecordTypeId'); + System.debug(LoggingLevel.DEBUG, 'Using PROVIDED record type Id ' + recordTypeId); + if (String.isEmpty(recordTypeId)) + { + recordTypeId = getMasterRecordType(objApiName); + System.debug(LoggingLevel.DEBUG, 'Null record type so falling back to MASTER record type Id ' + recordTypeId); + } + } else { + recordTypeId = getMasterRecordType(objApiName); + System.debug(LoggingLevel.DEBUG, 'Using MASTER record type Id ' + recordTypeId); + } + } catch (Exception e) { + System.debug(LoggingLevel.DEBUG, 'Record type id field does not exist for ' + obj.getSObjectType().getDescribe().getName()); + recordTypeId = getMasterRecordType(objApiName); + System.debug(LoggingLevel.DEBUG, 'Using MASTER record type Id ' + recordTypeId); + } + + System.debug(LoggingLevel.DEBUG, 'Output recordTypeId - ' + recordTypeId); + return recordTypeId; + + } + + /* + * Method to get an SObject's type from its Id. + */ + public static String getSObjectTypeFromId(Id id) + { + return id.getSObjectType().getDescribe().getName(); + } + + /* + * Method to return the object that a given objects field looks up to. For example, if the + * obj = User and the field = ProfileId the value returned will be "Profile" + System.debug(LoggingLevel.FINE, 'RESULT - ' + HelperSchema.getFieldLookupObject('Account', 'LastModifiedById')); + + */ + public static String getFieldLookupObject(String obj, String field) + { + System.debug(LoggingLevel.DEBUG, 'Starting getFieldLookupObject(' + obj + ',' + field + ')'); + + String objString = null; + + String key = obj + ':' + field; + + objString = fieldLookupObjs.get(key); + if (!String.isEmpty(objString)) + { + System.debug(LoggingLevel.DEBUG, 'USED CACHE(getFieldLookupObject)'); + return objString; + } + + Schema.SObjectField fieldSchema = getFieldByKey(obj, field, NAME); + + if (fieldSchema == null) throw new ListViewException('Cannot find provided field - ' + obj + '.' + field + '. Please ensure to use field API name, not label name'); + + Schema.DescribeFieldResult fDescribe = fieldSchema.getDescribe(); + + //if (fDescribe != null) throw new ListViewException('FDescribe - ' + fDescribe); + + if (!fDescribe.getReferenceTo().isEmpty()) + objString = fDescribe.getReferenceTo()[0].getDescribe().getName(); + else { + objString = obj; + } + + fieldLookupObjs.put(key, objString); + + return objString; + + } + + public static String getFieldRelationshipType(String obj, String field) + { + System.debug(LoggingLevel.DEBUG, 'Starting getFieldRelationshipType(' + obj + ',' + field + ')'); + + obj = obj.replace('__r', '__c'); + + String relType = null; + String key = obj + ':' + field; + + relType = fieldRelType.get(key); + if (!String.isEmpty(relType)) + { + System.debug(LoggingLevel.DEBUG, 'USED CACHE(getFieldRelationshipType)'); + return relType; + } + + //see if there is a regular field name + Schema.SObjectField sField = getFieldByKey(obj, field, NAME); + + if (sField != null) + { + relType = REL_TYPE_FIELD; + + } else { + + //see if there is a field with an Id at the end. i.e. ContactId + sField = getFieldByKey(obj, field + 'Id', NAME); + + if (sField != null) + { + relType = REL_TYPE_CORE_ID; + + } else { + + //see if there is a lookup field name + sField = getFieldByKey(obj, field.replace('__r', '__c'), NAME); + + if (sField != null) + { + relType = REL_TYPE_LOOKUP; + } else { + List relationships = getObjectSchema(obj).getChildRelationships(); + + for (Schema.ChildRelationship relationship: relationships) + { + if (relationship.getRelationshipName() == field) + { + relType = REL_TYPE_CHILD_REL; + break; + } + } + } + } + } + + fieldRelType.put(key, relType); + return relType; + + } + + /* + * Method to return a describe result for a given object and field. + System.debug(LoggingLevel.DEBUG, 'RESULT - ' + HelperSchema.getFieldDescribeResult('Account','simpli_lv__Test_Text_Rich__c')); + */ + public static Schema.DescribeFieldResult getFieldDescribeResult(String obj, String field) + { + + System.debug(LoggingLevel.FINE, 'Starting getFieldDescribeResult(' + obj + ', ' + field + ')'); + Schema.DescribeFieldResult result = null; + + //see if there is a regular field name + Schema.SObjectField sField = getFieldByKey(obj, field, NAME); + + //see if there is a field with an Id at the end. i.e. ContactId + if (sField == null) + sField = getFieldByKey(obj, field + 'Id', NAME); + + //see if there is a lookup field name + if (sField == null) + sField = getFieldByKey(obj, field.removeEnd('__r') + '__c', NAME); + + if (sField != null) + result = sField.getDescribe(); + + //see if we are working with a relationship field + if (result == null) + { + List relationships = getObjectSchema(obj).getChildRelationships(); + + for (Schema.ChildRelationship relationship: relationships) + { + if (relationship.getRelationshipName() == field) + { + result = relationship.getField().getDescribe(); + break; + } + } + } + + System.debug(LoggingLevel.FINE, 'Ending getFieldDescribeResult(' + result + ')'); + + return result; + } + + /** + * @description Method to get a map of all object names and API names + * @author tom@ansleyllc.com | 07-07-2021 + **/ + public static Map getAllObjectNames() + { + Map objMap = new Map(); + for (Schema.SObjectType o : Schema.getGlobalDescribe().values() ) + { + Schema.DescribeSObjectResult objResult = o.getDescribe(); + if (!objResult.getLabel().contains('__MISSING LABEL__') //REALLY? + && !objResult.getLabel().contains('__History') + && !objResult.getLabel().contains('__ChangeEvent') + && !objResult.getLabel().contains('__Share')) + objMap.put(objResult.getName(), objResult.getLabel()); + } + + return objMap; + } + + /* + * Method that returns the describe result of an SObject. + + System.debug(LoggingLevel.FINE, 'RESULT - ' + HelperSchema.getObjectSchema('simpli_lv__List_View_Org_Wide_Setting__mdt')); + System.debug(LoggingLevel.FINE, 'RESULT - ' + HelperSchema.getObjectSchema('Bogus')); + Schema.DescribeSObjectResult objDescribeSobject = HelperSchema.getObjectSchema('Event')); + Map mapFields = objDescribeSobject.fields.getMap(); + List lstPickListValues = mapFields.get(strPicklistField).getDescribe().getPickListValues(); + for (Schema.PicklistEntry objPickList : lstPickListValues) { + System.debug('Value = ' +objPickList.getValue() +' , Label = ' +objPickList.getLabel()); + } + */ + public static Schema.DescribeSObjectResult getObjectSchema(String name) + { + Schema.DescribeSObjectResult result = null; + Schema.SObjectType objType = objectDescribeByName.get(name); + if (objType == null) + { + initSObjectSchema(name, false); + objType = objectDescribeByName.get(name); + if (objType != null) + result = objType.getDescribe(); + } else { + result = objType.getDescribe(); + } + return result; + } + + public static String getObjectPluralName(String objName) + { + return getObjectSchema(objName).getLabelPlural(); + } + + public static String getObjectLabelCache(String objName) + { + Schema.DescribeSObjectResult result = getObjectSchema(objName); + if (result != null) + return result.getLabel(); + else + return ''; + } + + /* + System.debug(LoggingLevel.FINE, 'FAKE RESULT - ' + HelperSchema.isObject('Fake Object')); + System.debug(LoggingLevel.FINE, 'FAKE RESULT - ' + HelperSchema.isObject('ApexLog')); + */ + public static Boolean isObject(String objName) + { + Boolean isObject = false; + + if (objectDescribeByName.get(objName) == null) + { + initSObjectSchema(objName, false); + if (objectDescribeByName.get(objName) != null) + isObject = true; + } else { + isObject = true; + } + return isObject; + } + + /* + * Returns the API object type for a provided lookup field name. Currently + * this method does not work with hierarchical field names + * + * Method will return an empty string if the field is not a lookup field. + * + System.debug(LoggingLevel.FINE, 'RESULT 1 - ' + HelperSchema.getObjectTypeForField('Contact', 'Name')); + System.debug(LoggingLevel.FINE, 'RESULT 2 - ' + HelperSchema.getObjectTypeForField('Contact', 'AccountId')); + */ + public static String getObjectTypeForField(String obj, String field) + { + String objType = ''; + + Schema.SObjectField fieldSchema = getFieldByKey(obj, field, NAME); + + List sObjTypes = fieldSchema.getDescribe().getReferenceTo(); + + if (!sObjTypes.isEmpty()) + objType = sObjTypes[0].getDescribe().getName(); + + System.debug(LoggingLevel.DEBUG, 'Type from field - ' + obj + '/' + field + ' - ' + objType); + return objType; + } + + /* + * Returns the API object name based on a provided Id. Record IDs are prefixed + * with three-character codes that specify the type of the object (for example, + * accounts have a prefix of 001 and opportunities have a prefix of 006). + + System.debug(LoggingLevel.DEBUG, 'RESULT - ' + HelperSchema.getObjectTypeFromId('00G3h000000GElpEAG')); //Group + */ + public static String getObjectTypeFromId(Id objId) + { + if (String.isEmpty(objId)) + return 'BAD ID'; + + if (objectDescribeByPrefix.isEmpty()) + initObjectDescribeByPrefix(); + + String objType = objectDescribeByPrefix.get(String.valueOf(objId).substring(0,3)).getDescribe().getName(); + System.debug(LoggingLevel.DEBUG, 'Type from id result - ' + objId + '/' + objType); + return objType; + } + + public static String getObjectType(SObject obj) + { + return obj.getSObjectType().getDescribe().getName(); + } + + public static Schema.SObjectField getFieldSchema(String obj, String field) + { + return getFieldByKey(obj, field, NAME); + } + + public static String getFieldLabel(String obj, String field) + { + String label = ''; + Schema.SObjectField objField = getFieldByKey(obj, field, NAME); + + if (objField != null) + label = objField.getDescribe().getLabel(); + + return label; + + } + + public static Schema.DisplayType getFieldType(String obj, String field) + { + + Schema.DisplayType fieldType = null; + + Schema.SObjectField objField = getFieldByKey(obj, field, NAME); + + if (objField != null) + fieldType = objField.getDescribe().getType(); + + return fieldType; + + } + + /* + * Method which, given an object type and field will convert the provided value into the + * correct object based on the provided fields type. + */ + public static Object getValueForField(String obj, String field, String value) + { + System.debug(LoggingLevel.FINE, 'Starting getValueForField - ' + obj + '/' + field + '/' + value); + Object objValue = null; + Schema.DisplayType displayType = getFieldType(obj, field); + + if (displayType != null) + { + if (displayType == Schema.DisplayType.Boolean) return Boolean.valueOf(value); + else if (displayType == Schema.DisplayType.Double) { + if (String.isEmpty(value)) return null; + objValue = Double.valueOf(value); + } + else if (displayType == Schema.DisplayType.Base64) return Blob.valueOf(value); + else if (displayType == Schema.DisplayType.Currency) { + if (String.isEmpty(value)) return null; + objValue = Double.valueOf(value); + } + else if (displayType == Schema.DisplayType.Date) { + if (String.isEmpty(value)) return null; + objValue = Date.valueOf(value); + } + else if (displayType == Schema.DisplayType.Integer) { + if (String.isEmpty(value)) return null; + objValue = Integer.valueOf(value); + } + else if (displayType == Schema.DisplayType.Percent) { + if (String.isEmpty(value)) return null; + objValue = Double.valueOf(value); + } + else if (displayType == Schema.DisplayType.DateTime) { + if (String.isEmpty(value)) return null; + objValue = DateTime.valueOfGmt(value.replace('T', ' ').remove('.000Z')); //2021-07-21 20:45:00 + } + else if (displayType == Schema.DisplayType.Time) { + if (String.isEmpty(value)) return null; + objValue = Time.newInstance(Integer.valueOf(value.substring(0, 2)), + Integer.valueOf(value.substring(3, 5)), + Integer.valueOf(value.substring(6, 8)), + Integer.valueOf(value.substring(9).removeEnd('Z'))); //03:15:00.000 + } + else objValue = value; + } + System.debug(LoggingLevel.FINE, 'Finished getValueForField - ' + obj + '/' + field + '/' + objValue); + + return objValue; + } + + /* + * Method which, given an object API name and a field label or name returns the schema of the field. + System.debug(LoggingLevel.FINE, 'Field Data - ' + HelperSchema.getFieldByKey('Account', 'LastModifiedBy', HelperSchema.NAME)); + System.debug(LoggingLevel.FINE, 'Field Data - ' + HelperSchema.getFieldByKey('Account', 'LastModifiedBy', HelperSchema.NAME)); + */ + public static Schema.SObjectField getFieldByKey(String obj, String key, String keyType) + { + System.debug(LoggingLevel.DEBUG, 'Starting getFieldByKey(' + obj + ', ' + key + ', ' + keyType + ')'); + //get the fields of the object in question + Map fieldsByKeyType = objectDescribeFieldsByObjectNameAndKey.get(obj + ':' + keyType); + + //if we cannot find the data we initialize and try again.....this is to reduce load + if (fieldsByKeyType == null) + { + initSObjectFieldSchema(obj, keyType); + fieldsByKeyType = objectDescribeFieldsByObjectNameAndKey.get(obj + ':' + keyType); + } + + if (fieldsByKeyType == null) + { + initSObjectSchema(obj, true); + fieldsByKeyType = objectDescribeFieldsByObjectNameAndKey.get(obj + ':' + keyType); + } + + //get the field by key + Schema.SObjectField field = fieldsByKeyType.get(key.toLowerCase()); + + return field; + + } + + /* + * Method which given an object API name returns those fields that are available for the object. + * The returned map has its keys based on either the fields Label or Name, depending on the + * provided keyType. + System.debug(LoggingLevel.FINE, 'RESULT - ' + HelperSchema.getFieldsForObject('simpli_lv__List_View_Org_Wide_Setting__mdt', HelperSchema.NAME)); + Map fields = HelperSchema.getFieldsForObject('Account', HelperSchema.NAME); + for (Schema.SObjectField field: fields.values()) + System.debug(LoggingLevel.FINE, 'FIELD - ' + field); + */ + public static Map getFieldsForObject(String obj, String keyType) + { + initSObjectFieldSchema(obj, keyType); + + //get the fields of the object in question + Map fieldsByKeyType = objectDescribeFieldsByObjectNameAndKey.get(obj + ':' + keyType); + + if (fieldsByKeyType == null) + { + initSObjectSchema(obj, true); + fieldsByKeyType = objectDescribeFieldsByObjectNameAndKey.get(obj + ':' + keyType); + } + + return fieldsByKeyType; + + } + + /* + * Debugging method to show fields by key type + */ + @TestVisible + private static String getObjectFieldDebug(String obj, String keyType, Map fieldsByKeyType) + { + String debug = '\n-----------------------------------------------\n'; + debug += 'OBJ - ' + obj + '\n'; + debug += 'KEY TYPE - ' + keyType + '\n\n'; + for (String key: fieldsByKeyType.keySet()) + debug += 'KEY - ' + key + ', VALUE - ' + fieldsByKeyType.get(key) + '\n'; + debug += '-----------------------------------------------\n'; + + return debug; + } + + /* + * Method to retrieve the field label for a given object and API field name. + System.debug(LoggingLevel.DEBUG, 'Result ' + HelperSchema.getSFDCFieldLabel('simpli_lv__List_View_Action_Parameter__c','List_View_Action__r.LastModifiedBy.Name')); + System.debug(LoggingLevel.DEBUG, 'Result ' + HelperSchema.getSFDCFieldLabel('Account','User.Alias')); + */ + public static String getSFDCFieldLabel(String objName, String sfdcFieldName) + { + if (objName == null || sfdcFieldName == null) throw new ListViewException('An object (' + objName + ') and field (' + sfdcFieldName + ') name must be provided when using getSFDCFieldLabel() method'); + String label = ''; + + String key = objName + ':' + sfdcFieldName; + try { + label = fieldLabels.get(key); + if (!String.isEmpty(label)) + { + System.debug(LoggingLevel.DEBUG, 'USED CACHE(getSFDCFieldLabel)'); + return label; + } + + //scrub the field of weird stuff like spaces and toLabel() etc. + sfdcFieldName = scrubFieldName(sfdcFieldName); + + //split field into parts. + List fieldHierarchy = sfdcFieldName.split('\\.'); + System.debug(LoggingLevel.DEBUG, 'Field Hierarchy - ' + objName + '/' + fieldHierarchy); + + String objType = objName; + Schema.DescribeSObjectResult objDescribe = HelperSchema.getObjectSchema(objType); + + while (fieldHierarchy.size() > 1) { + + String currentField = fieldHierarchy.remove(0); + System.debug(LoggingLevel.DEBUG, 'Current objType/field - ' + objType + '/' + currentField); + String relType = getFieldRelationshipType(objType, currentField); + System.debug(LoggingLevel.DEBUG, 'Relationship type - ' + relType); + + if (relType == REL_TYPE_CHILD_REL) + { + List relationships = getObjectSchema(objType).getChildRelationships(); + + for (Schema.ChildRelationship relationship: relationships) + { + if (relationship.getRelationshipName() == currentField) + { + objType = relationship.getChildSObject().getDescribe().getName(); + break; + } + } + + } else if (relType == REL_TYPE_CORE_ID) + { + objType = getFieldLookupObject(objType.replace('__r', '__c'), currentField + 'Id'); + + } else if (relType == REL_TYPE_LOOKUP) + { + objType = getFieldLookupObject(objType.replace('__r', '__c'), currentField.removeEnd('__r') + '__c'); + + } else { + objType = currentField; + } + + } + + String currentField = fieldHierarchy.remove(0); + System.debug(LoggingLevel.DEBUG, 'Final objType/field - ' + objType + '/' + currentField); + + Schema.SObjectField objField = HelperSchema.getFieldByKey(objType, currentField, NAME); + if (objField != null) + label = objField.getDescribe().getLabel(); + + } catch (Exception e) { + System.debug(LoggingLevel.DEBUG, ListViewException.getExtendedString(e)); + ListViewErrorHelper.createFutureUsageError('Exception during HelperSchema.getSFDCFieldLabel(' + objName + ',' + sfdcFieldName + ') ' + ListViewException.getExtendedString(e)); + label = ''; + } + + fieldLabels.put(key, label); + System.debug(LoggingLevel.DEBUG, 'Field label result for ' + objName + '/' + sfdcFieldName + ' is ' + label); + return label; + } + + /* + * Method to determine if a given object and field name is valid. + System.debug(LoggingLevel.FINE, 'Result ' + HelperSchema.isValidSFDCFieldName('Contact', 'Email')); + */ + public static Boolean isValidSFDCFieldName(String objName, String sfdcFieldName) + { + if (objName == null || sfdcFieldName == null) return false; + Boolean isValid = false; + String key = objName + ':' + sfdcFieldName; + try { + + isValid = HelperSchema.validFieldNames.get(key); + if (isValid != null) + { + System.debug(LoggingLevel.DEBUG, 'USED CACHE(isValidSFDCFieldName)'); + return isValid; + } else { + isValid = false; + } + + //scrub the field of weird stuff like spaces and toLabel() etc. + sfdcFieldName = scrubFieldName(sfdcFieldName); + + //split field into parts. + List fieldHierarchy = sfdcFieldName.split('\\.'); + System.debug(LoggingLevel.FINE, 'Field Hierarchy - ' + fieldHierarchy); + + String objType = objName; + Schema.DescribeSObjectResult objDescribe = HelperSchema.getObjectSchema(objType); + objType = objDescribe.getName(); //it could have changed if we are working with a domain. i.e. it could go from List_View_Config_Condition__c --> simpli_lv__List_View_Config_Condition__c + + while (fieldHierarchy.size() > 1) { + + String currentField = fieldHierarchy.remove(0); + System.debug(LoggingLevel.DEBUG, 'Current objType/field - ' + objType + '/' + currentField); + String relType = getFieldRelationshipType(objType, currentField); + System.debug(LoggingLevel.DEBUG, 'Relationship type - ' + relType); + + if (relType == REL_TYPE_CHILD_REL) + { + List relationships = getObjectSchema(objType).getChildRelationships(); + + for (Schema.ChildRelationship relationship: relationships) + { + if (relationship.getRelationshipName() == currentField) + { + objType = relationship.getChildSObject().getDescribe().getName(); + break; + } + } + + } else if (relType == REL_TYPE_CORE_ID) + { + objType = getFieldLookupObject(objType, currentField + 'Id'); + + } else if (relType == REL_TYPE_LOOKUP) + { + objType = getFieldLookupObject(objType, currentField.removeEnd('__r') + '__c'); + + } else { + objType = currentField; + } + + } + + String currentField = fieldHierarchy.remove(0); + System.debug(LoggingLevel.DEBUG, 'Final objType/field - ' + objType + '/' + currentField); + + Schema.SObjectField objField = HelperSchema.getFieldByKey(objType, currentField, NAME); + if (objField != null) + isValid = true; + + + } catch (Exception e) { + ListViewErrorHelper.createFutureUsageError('Exception during HelperSchema.isValidSFDCFieldName(' + objName + ',' + sfdcFieldName + ') ' + ListViewException.getExtendedString(e)); + isValid = false; + } + + validFieldNames.put(key, isValid); + System.debug(LoggingLevel.FINE, 'Field validation result for ' + objName + '/' + sfdcFieldName + ' is ' + isValid); + return isValid; + + } + + /* + * Method to determine if a given object name is valid. + */ + public static Boolean isValidSFDCObjectName(String sfdcObjectName) + { + Boolean isValid = true; + try { + List objName = new List{sfdcObjectName}; + Schema.describeSObjects(objName); + } catch (Exception e) { + ListViewErrorHelper.createFutureUsageError('Exception during HelperSchema.isValidSFDCObjectName(' + sfdcObjectName + ') ' + ListViewException.getExtendedString(e)); + isValid = false; + } + + return isValid; + + } + + /* + * Method which given an sobject and a field name will retrieve + * the value for the field as well as field information. This data + * is returned in a structure which includes the following data - + * + * objValue - a pointer to the SObject holding the field + * name - the API name of the field + * label - the label of the field + * type - the type of the field holding the data value + * value - the value of the field. + + * + Account acc = [SELECT Name, Owner.Name, Owner.Alias, Owner.Profile.Name, Case__r.Status FROM Account WHERE Name LIKE 'Express%' LIMIT 1]; + HelperSchema.FieldData d = HelperSchema.getFieldData(acc, 'Case__r.Status'); + System.debug(LoggingLevel.FINE, 'Account Name - ' + d.name + ', ' + d.label + ', ' + d.getType() + ', ' + d.value); + * + */ + public static FieldData getFieldData(SObject obj, String fieldName) + { + System.debug(LoggingLevel.FINE, 'Starting getFieldData(' + fieldName + ', ' + obj + ')'); + FieldData data = null; + String objValueId = null; + String label = ''; + + try { + + fieldName = scrubFieldName(fieldName); + + if (obj == null || fieldName == null || fieldName == '') + throw new ListViewException('HelperSchema.getFieldData() called with empty values. obj = ' + obj + ', fieldName = ' + fieldName); + + List fieldHierarchy = fieldName.split('\\.'); + + System.debug(LoggingLevel.FINE, 'Field Hierarchy - ' + fieldHierarchy); + + SObject currentObj = obj; + + String objType = getObjectType(currentObj); + + for (String field: fieldHierarchy) + { + + //if we are at the last field then get the value and return + if (fieldHierarchy.indexOf(field) == fieldHierarchy.size()-1) + { + //get the object type + System.debug(LoggingLevel.FINE, 'Object Type - ' + objType); + System.debug(LoggingLevel.FINE, 'Field - ' + field); + + + //get the field describe + Schema.DescribeFieldResult fieldDesc = getFieldDescribeResult(objType, field); + + //if we do not have a field describe it could be because there is no data in the relationship + if (fieldDesc == null) { + data = new FieldData(field, label, Schema.DisplayType.STRING, '', currentObj); + System.debug(LoggingLevel.ERROR, 'Cannot find field with name ' + field + ' for object of type ' + objType); + + //if there is a field describe then get the label and set the data. + } else { + + label = fieldDesc.getLabel(); + + //if we have a lookup field then determine if we add the object name to the label + String addObjName = ListViewConfigHelper.getOrgWideConfigParam('AddObjNameOnLookupLabels'); + if (addObjName == ListViewHelper.TTRUE && fieldName.contains('.')) + label = objType + ' ' + fieldDesc.getLabel(); + + data = new FieldData(field, label, fieldDesc.getType(), currentObj.get(field), currentObj); + data.parentObjType = getObjectType(currentObj); + //data.objValueId = objValueId; + data.isEditable = fieldDesc.isUpdateable(); + + if (fieldDesc.isHtmlFormatted()) + data.isHTML = true; + } + + //if we are not at the last field then get the fields object + } else { + System.debug(LoggingLevel.FINE, 'Not at last field. Current field - ' + field); + Boolean isSet = false; + + System.debug(LoggingLevel.FINE, 'old currentObj - ' + currentObj); + + Map fieldsToValue = currentObj.getPopulatedFieldsAsMap(); + + //if there is a value for the field + if (fieldsToValue.get(field) != null) + { + Object tmpObj = fieldsToValue.get(field); + + if (tmpObj instanceof List) + currentObj = ((List) tmpObj)[0]; + else if (tmpObj instanceof SObject) + currentObj = (SObject) tmpObj; + + //if there is no value + } else { + System.debug(LoggingLevel.FINE, 'No field value for name ' + field); + //if we are trying to retrieve column data we need to get the label....so we get the label if we can. + if (label == '') + label = getSFDCFieldLabel(objType, fieldName); + data = new FieldData(field, label, Schema.DisplayType.STRING, '', currentObj); + return data; + } + + System.debug(LoggingLevel.FINE, 'new currentObj - ' + currentObj); + + if (currentObj != null) + { + isSet = true; + objType = getObjectType(currentObj); + + //only provide the Id value if there are only two fields in the hierarchy. i.e. CreatedBy.Name + if (objValueId == null && fieldHierarchy.size() == 2) + { + objValueId = String.valueOf(currentObj.get('Id')); + } + + } else { + //we might have a currentObj with no data so it bugs out. But, if this is the case + //and we are trying to retrieve column data that is bad....so we get the label if we can. + if (label == '') + label = getSFDCFieldLabel(objType, fieldName); + data = new FieldData(field, label, Schema.DisplayType.STRING, '', null); + data.isChildRel = true; + return data; + } + + } + } + } catch (Exception e) { + System.debug(LoggingLevel.ERROR, e.getMessage() + ' - ' + e.getStackTraceString()); + //ListViewErrorHelper.createFutureUsageError('Exception during HelperSchema.getFieldData(' + obj + ',' + fieldName + ') ' + ListViewException.getExtendedString(e)); + data = null; + } + + if (data != null) + System.debug(LoggingLevel.FINE, data.getDebugString()); + + return data; + } + + /** + * @description Method which, given a field name and an object returns the field data. + * This method is used for JSON structures returned via API's. For regular + * SObjects the getFieldData(SObject, String) method should be used. + * @author tom@ansleyllc.com | 08-01-2021 + * @param structure the structure holding the data necessary to find the field value + * @param fieldName the field name + * @return FieldData + **/ + public static FieldData getFieldData(Map structure, String fieldName, String objType) + { + System.debug(LoggingLevel.FINE, 'Starting getFieldData(' + fieldName + ', ' + objType + ', ' + structure + ')'); + + FieldData data = null; + String objValueId = null; + String label = ''; + + try { + + fieldName = scrubFieldName(fieldName); + + if (structure == null || fieldName == null || fieldName == '') + throw new ListViewException('HelperSchema.getFieldData() called with empty values. obj = ' + structure + ', fieldName = ' + fieldName); + + List fieldHierarchy = fieldName.split('\\.'); + + System.debug(LoggingLevel.FINE, 'Field Hierarchy - ' + fieldHierarchy); + + Map currentStructure = structure; + String currentParentObjType = objType; + + for (String field: fieldHierarchy) + { + + //if we are at the last field then get the value and return + if (fieldHierarchy.indexOf(field) == fieldHierarchy.size()-1) + { + System.debug(LoggingLevel.FINE, 'Field - ' + field); + System.debug(LoggingLevel.FINE, 'Value - ' + currentStructure.get(field)); + System.debug(LoggingLevel.FINE, 'Parent Obj - ' + currentParentObjType); + + data = new FieldData(field, currentStructure.get(field)); + data.parentObjType = currentParentObjType; + + //get the Id if this is a lookup to another object + if (currentStructure.get('attributes') != null) + { + System.debug(LoggingLevel.FINE, 'We have attribs!'); + Map attribs = (Map) currentStructure.get('attributes'); + String url = (String) attribs.get(ListViewHelper.TYPE_URL); + data.objValueId = url.substringAfterLast('/'); + System.debug(LoggingLevel.FINE, 'Id - ' + data.objValueId); + } + + //if we are not at the last field then get the fields object + } else { + System.debug(LoggingLevel.FINE, 'Old structure - ' + currentStructure); + currentStructure = (Map) currentStructure.get(field); + currentParentObjType = field; + System.debug(LoggingLevel.FINE, 'New structure - ' + currentStructure); + } + } + } catch (Exception e) { + ListViewErrorHelper.createFutureUsageError('Exception during HelperSchema.getFieldData(' + structure + ',' + fieldName + ') ' + ListViewException.getExtendedString(e)); + data = null; + } + + if (data != null) + System.debug(LoggingLevel.FINE, data.getDebugString()); + + return data; + } + + public static String scrubFieldNameForSOQL(String fieldName) + { + if (fieldName == null) return fieldName; + //remove whitespace + fieldName = fieldName.deleteWhitespace(); + + //remove toLabel keyword + if (fieldName.contains('toLabel(')) fieldName = fieldName.substringBetween('(', ')'); + + return fieldName; + } + + public static String scrubFieldName(String fieldName) + { + if (fieldName == null) return fieldName; + //remove whitespace + fieldName = fieldName.deleteWhitespace(); + + //remove toLabel keyword + if (fieldName.contains('toLabel(')) fieldName = fieldName.substringBetween('(', ')'); + else if (fieldName.contains('convertCurrency(')) fieldName = fieldName.substringBetween('(', ')'); + + return fieldName; + } + + public static Map getPicklistMap(String obj, String field) + { + Map lstPickvals = new Map(); + + Schema.DescribeSObjectResult sObjectDesc = HelperSchema.getObjectSchema(obj); + + Map fields = sObjectDesc.fields.getMap(); + List pickListValues = fields.get(field).getDescribe().getPickListValues(); + for (Schema.PicklistEntry a : pickListValues) + { + lstPickvals.put(a.getLabel(), a.getValue()); + } + + return lstPickvals; + + } + + /* + Found at https://glyntalkssalesforce.blogspot.com/2018/08/dependent-picklist-values-in-apex.html + HelperString.debug(HelperSchema.getDependentPicklistValues('Account', 'simpli_lv__Controlled_Picklist__c'), 'VALUES'); + */ + public static Map> getDependentPicklistValues(String sObjName, String fieldName) + { + return getDependentPicklistValues(Schema.getGlobalDescribe().get(sObjName).getDescribe().fields.getMap().get(fieldName)); + } + + //public static List getDependentPicklistValues(String sObjName, String fieldName, String recordTypeId) + //{ + // return getDependentPicklistValues(Schema.getGlobalDescribe().get(sObjName).getDescribe().fields.getMap().get(fieldName)); + //} + + /* + Found at https://glyntalkssalesforce.blogspot.com/2018/08/dependent-picklist-values-in-apex.html + */ + public static Map> getDependentPicklistValues(Schema.sObjectField dependToken) + { + System.debug(LoggingLevel.DEBUG, 'Depend Token - ' + dependToken); + Schema.DescribeFieldResult depend = dependToken.getDescribe(); + System.debug(LoggingLevel.DEBUG, 'Depend - ' + depend); + Schema.sObjectField controlToken = depend.getController(); + System.debug(LoggingLevel.DEBUG, 'Control Token - ' + controlToken); + if (controlToken == null) return null; + Schema.DescribeFieldResult control = controlToken.getDescribe(); + List controlEntries = + ( control.getType() == Schema.DisplayType.Boolean + ? null + : control.getPicklistValues() + ); + + String base64map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + Map> dependentPicklistValues = new Map>(); + for (Schema.PicklistEntry entry : depend.getPicklistValues()) + { + if (entry.isActive()) + { + System.debug(LoggingLevel.DEBUG, 'ENntry - ' + entry); + + List base64chars = String.valueOf(((Map) JSON.deserializeUntyped(JSON.serialize(entry))).get('validFor')).split(''); + for ( Integer index = 0; index < (controlEntries != null ? controlEntries.size() : 2); index++ ) + { + Object controlValue = + ( controlEntries == null + ? (Object) (index == 1) + : (Object) (controlEntries[ index ].isActive() ? controlEntries[ index ].getLabel() : null) + ); + Integer bitIndex = index / 6, bitShift = 5 - Math.mod( index, 6 ); + if ( controlValue == null + || (base64map.indexOf( base64chars[ bitIndex ] ) & (1 << bitShift)) == 0 + ) continue; + if ( !dependentPicklistValues.containsKey( controlValue ) ) + { + dependentPicklistValues.put( controlValue, new List() ); + } + dependentPicklistValues.get( controlValue ).add( entry.getLabel() ); + } + } + } + return dependentPicklistValues; + } + + + /* + * Method to determine if an SObject is createable. This is used + * for security to ensure user is allowed to create records for the given object. + */ + public static Boolean checkObjectCreateable(String objName, Boolean throwExc) + { + System.debug(LoggingLevel.FINE, 'checkObjectCreateable called with objName - ' + objName); + + if (getObjectSchema(objName).isCreateable()) { + return true; + } else { + if (throwExc) + throw new ListViewException('Records of type ' + objName + ' are not creatable by this user or does not exist in this org. Please check user permissions'); + return false; + } + } + + /* + * Method to determine if an SObject is createable. This is used + * for security to ensure user is allowed to create records for the given object. + */ + public static void checkObjectCreateable(String objName) + { + checkObjectCreateable(objName, true); + } + + /* + * Method to determine if an SObject is accessible. This is used + * for security to ensure user is allowed to create records for the given object. + */ + public static Boolean checkObjectAccessible(String objName, Boolean throwExc) + { + System.debug(LoggingLevel.FINE, 'checkObjectAccessible called with objName - ' + objName); + + //if we are using Tooling API the object might not be in the schema. + if (getObjectSchema(objName) != null) + { + if (getObjectSchema(objName).isAccessible()) { + return true; + } else { + if (throwExc) + throw new ListViewException('Records of type ' + objName + ' are not accessible. Please check user permissions'); + return false; + } + } + + return true; + } + + /* + * Method to determine if an SObject is accessible. This is used + * for security to ensure user is allowed to create records for the given object. + */ + public static void checkObjectAccessible(String objName) + { + checkObjectAccessible(objName, true); + } + + /* + * Method to determine if an SObject is createable. This is used + * for security to ensure user is allowed to create records for the given object. + */ + public static Boolean checkObjectUpdateable(String objName, Boolean throwExc) + { + System.debug(LoggingLevel.FINE, 'checkObjectUpdateable called with objName - ' + objName); + + if (getObjectSchema(objName).isUpdateable()) { + return true; + } else { + if (throwExc) + throw new ListViewException('Records of type ' + objName + ' are not updateable or do not exist. Please check user permissions'); + return false; + } + } + + /* + * Method to determine if an SObject is createable. This is used + * for security to ensure user is allowed to create records for the given object. + */ + public static void checkObjectUpdateable(String objName) + { + checkObjectUpdateable(objName, true); + } + + /* + * Method to determine if an SObject is deletable. This is used + * for security to ensure data being queried is deletable by the user. + */ + public static Boolean checkObjectDeletable(SObject obj, Boolean throwExc) + { + System.debug(LoggingLevel.FINE, 'checkObjectDeletable called with obj - ' + obj); + + if (obj == null || obj.Id == null) + throw new ListViewException('HelperSchema.checkObjectDeletable called with null Object or object with empty Id'); + + String objName = getSObjectTypeFromId(obj.Id); + + if (getObjectSchema(objName).isDeletable()) { + return true; + } else { + if (throwExc) + throw new ListViewException('Records of type ' + objName + ' are not deletable or do not exist. Please check user permissions'); + return false; + } + } + + /* + * Method to determine if an SObject is deletable. This is used + * for security to ensure data being queried is deletable by the user. + */ + public static void checkObjectDeletable(SObject obj) + { + checkObjectDeletable(obj, true); + } + + /* + * Method to determine if an SObject type is deletable. This is used + * for security to ensure data being queried is deletable by the user. + */ + public static Boolean checkObjectDeletable(String objName, Boolean throwExc) + { + System.debug(LoggingLevel.FINE, 'checkObjectDeletable called with objName - ' + objName); + + if (getObjectSchema(objName).isDeletable()) { + return true; + } else { + if (throwExc) + throw new ListViewException('Records of type ' + objName + ' are not deletable or do not exist. Please check user permissions'); + return false; + } + } + + /* + * Method to determine if an SObject type is deletable. This is used + * for security to ensure data being queried is deletable by the user. + */ + public static void checkObjectDeletable(String objName) + { + checkObjectDeletable(objName, true); + } + + public static String getDMLDecisionDebug(String objAPIName, SObjectAccessDecision dec) + { + String log = '\n\n--------------- Decision ---------------\n'; + log += 'Object - ' + objAPIName + '\n'; + for (Integer index: dec.getModifiedIndexes()) + log += index + ', '; + log.removeEnd(', '); + + for (String field: dec.getRemovedFields().keySet()) + log += field + dec.getRemovedFields().get(field) + '\n'; + + log += '----------------------------------------\n'; + + return log; + + } + + //------------------------------------------------ + // PRIVATE METHODS BELOW + //------------------------------------------------ + + private static void initObjectDescribeByPrefix() + { + objectDescribeByPrefix = new Map(); + Map describe = Schema.getGlobalDescribe(); + for(String s:describe.keyset()) + objectDescribeByPrefix.put(describe.get(s).getDescribe().getKeyPrefix(), describe.get(s)); + } + + private static void initSObjectFieldSchema(String obj, String keyType) + { + Schema.SObjectType objSchema = Schema.getGlobalDescribe().get(obj); + if (objSchema != null && objectDescribeFieldsByObjectNameAndKey.get(obj + ':' + keyType) == null) + { + System.debug(LoggingLevel.FINE, 'Found schema with name - ' + obj); + + + List fields = objSchema.getDescribe().fields.getMap().values(); + + Map fieldsByLabel = new Map(); + Map fieldsByName = new Map(); + + for (Schema.SObjectField objField: fields) + { + fieldsByName.put(objField.getDescribe().getName().toLowerCase(), objField); + fieldsByLabel.put(objField.getDescribe().getLabel().toLowerCase(), objField); + } + objectDescribeFieldsByObjectNameAndKey.put(obj + ':' + LABEL, fieldsByLabel); + objectDescribeFieldsByObjectNameAndKey.put(obj + ':' + NAME, fieldsByName); + + //if schema contain "simpli_lv" then remove and try again (this is the hack) + if (obj.contains(DOMAIN_NAME)) + { + objectDescribeByName.put(obj.removeStart(DOMAIN_NAME), objSchema); + objectDescribeFieldsByObjectNameAndKey.put(obj.removeStart(DOMAIN_NAME) + ':' + LABEL, fieldsByLabel); + objectDescribeFieldsByObjectNameAndKey.put(obj.removeStart(DOMAIN_NAME) + ':' + NAME, fieldsByName); + } + } + } + + private static void initSObjectSchema(String obj, Boolean includeFields) + { + initSObjectSchema(obj, true, includeFields); + } + + /* + * Method to initialize an sobject schema if it hasn't already been initialized. This has a small hack + * in it to detect if an object name being requested needs a domain prefix. If it does then it rerequests + * the schema. Once the schema is returned it places the schema into the map with both the domain prefix + * and without it. This hack ensures that tests will run in both the package dev org and other orgs. + * HelperSchema.initSObjectSchema('simpli_lv__List_View__c'); + * HelperSchema.initSObjectSchema(''); + */ + private static void initSObjectSchema(String obj, Boolean allowRecurring, Boolean includeFields) + { + //if this object has not been described yet get the data + if (objectDescribeByName.get(obj) == null) + { + System.debug(LoggingLevel.FINE, 'Trying to initialize schema with name - ' + obj); + Schema.SObjectType objSchema = Schema.getGlobalDescribe().get(obj); + if (objSchema != null) + { + System.debug(LoggingLevel.FINE, 'Found schema with name - ' + obj); + objectDescribeByName.put(obj, objSchema); + + if (obj.contains(DOMAIN_NAME)) + objectDescribeByName.put(obj.removeStart(DOMAIN_NAME), objSchema); + + if (includeFields) + { + initSObjectFieldSchema(obj, LABEL); + } + + } else if (allowRecurring) { + System.debug(LoggingLevel.FINE, 'NO schema with name - ' + obj); + initSObjectSchema(DOMAIN_NAME + obj, false, includeFields); + } + } + + } + + public static Type getClassType(String typeStr) + { + Type t = null; + Integer index = typeStr.indexOf('.'); + if (index != -1) { + // With namespace "ns.Type" + t = Type.forName(typeStr.substring(0, index), typeStr.substring(index + 1, typeStr.length())); + } + + if (t == null) { + // Just "Type" + t = Type.forName(null, typeStr); + } + + if (t == null) + throw new ListViewException('Provided type not found - ' + typeStr); + return t; + } + + //------------------------------------------------ + // INNER CLASSES + //------------------------------------------------ + + public class FieldData + { + + public SObject objValue {get; set;} + public String parentObjType {get; set;} + public String name {get; set;} + public String label {get; set;} + private Schema.DisplayType type {get; set;} + public Object value {get; set;} //holds the string value. i.e. CreatedBy.Name = "Tom Ansley" + public String objValueId {get; set;} //holds the id value if its a lookup. i.e. CreatedBy.Name = 0053h000000xrj3AAA + public Boolean isChildRel {get; set;} + public Boolean isEditable {get; set;} + public Boolean isHTML {get; set;} //identifies if its a rich text field + + public FieldData(String name, String label, Schema.DisplayType type, Object value, SObject objValue) + { + this.objValue = objValue; + this.name = name; + this.label = label; + this.type = type; + this.value = value; + this.isChildRel = false; + this.isEditable = false; + this.parentObjType = ''; + this.isHTML = false; + if (objValue != null) + objValueId = String.valueOf(objValue.get('Id')); + } + + public FieldData(String name, Object value) + { + this.name = name; + this.value = value; + this.isChildRel = false; + this.isEditable = false; + this.parentObjType = ''; + this.isHTML = false; + } + + public String getType() { + if (isHTML) + return 'rich textarea'; + else + return type.name().toLowerCase(); + } + + public String getDebugString() + { + String debug = '\n\n----------------\n'; + debug += 'objValue - ' + objValue + '\n'; + debug += 'parentObjType - ' + parentObjType + '\n'; + debug += 'name - ' + name + '\n'; + debug += 'label - ' + label + '\n'; + debug += 'type - ' + type + '\n'; + debug += 'value - ' + value + '\n'; + debug += 'objValueId - ' + objValueId + '\n'; + debug += 'isChildRel - ' + isChildRel + '\n'; + debug += 'isEditable - ' + isEditable + '\n'; + debug += 'isHTML - ' + isHTML + '\n'; + debug += '----------------\n'; + + return debug; + } + } + + } \ No newline at end of file diff --git a/force-app/main/default/classes/HelperString.cls b/force-app/main/default/classes/HelperString.cls index 5b749a4..693db72 100644 --- a/force-app/main/default/classes/HelperString.cls +++ b/force-app/main/default/classes/HelperString.cls @@ -1,7 +1,7 @@ /** * @description : * @author : tom@ansleyllc.com - * @last modified on : 11-08-2023 + * @last modified on : 04-29-2024 * @last modified by : tom@ansleyllc.com **/ public with sharing class HelperString { diff --git a/force-app/main/default/classes/HelperTest.cls b/force-app/main/default/classes/HelperTest.cls index 96f99a6..df1fb31 100644 --- a/force-app/main/default/classes/HelperTest.cls +++ b/force-app/main/default/classes/HelperTest.cls @@ -2,7 +2,7 @@ * @description : * @author : tom@ansleyllc.com * @group : - * @last modified on : 03-14-2024 + * @last modified on : 08-14-2024 * @last modified by : tom@ansleyllc.com * Modifications Log * Ver Date Author Modification @@ -214,7 +214,7 @@ public with sharing class HelperTest { params.add(param); param = new List_View_Config_Parameter__c(); - param.Parameter_Name__c = 'TotalColumns'; + param.Parameter_Name__c = ListViewHelper.PARAM_TOTAL_COLUMNS; param.Parameter_Type__c = ListViewHelper.TYPE_STRING; param.Parameter_Value__c = 'AnnualRevenue'; param.List_View_Config__c = config.Id; @@ -222,7 +222,7 @@ public with sharing class HelperTest { params.add(param); param = new List_View_Config_Parameter__c(); - param.Parameter_Name__c = 'TotalColumnsColor'; + param.Parameter_Name__c = ListViewHelper.PARAM_TOTAL_COLUMN_COLOR; param.Parameter_Type__c = 'Color'; param.Parameter_Value__c = '#E5F0FA'; param.List_View_Config__c = config.Id; @@ -357,6 +357,14 @@ public with sharing class HelperTest { param.Parameter_Label__c = 'Total Column Color'; params.add(param); + param = new List_View_Config_Parameter__c(); + param.Parameter_Name__c = ListViewHelper.PARAM_CHILD_RECORD_COLOR; + param.Parameter_Type__c = 'Color'; + param.Parameter_Value__c = '#E5F0FA'; + param.List_View_Config__c = config.Id; + param.Parameter_Label__c = 'Child Row Color'; + params.add(param); + param = new List_View_Config_Parameter__c(); param.Parameter_Name__c = ListViewHelper.PARAM_RETURN_SIZE; param.Parameter_Type__c = ListViewHelper.TYPE_NUMBER; diff --git a/force-app/main/default/classes/ListViewAbstract.cls b/force-app/main/default/classes/ListViewAbstract.cls index 2f5a744..6ce5084 100644 --- a/force-app/main/default/classes/ListViewAbstract.cls +++ b/force-app/main/default/classes/ListViewAbstract.cls @@ -1,7 +1,7 @@ /** * @description : * @author : tom@ansleyllc.com - * @last modified on : 05-20-2024 + * @last modified on : 08-14-2024 * @last modified by : tom@ansleyllc.com * Modifications Log * Ver Date Author Modification @@ -16,7 +16,7 @@ public with sharing abstract class ListViewAbstract { public static Set validTotalColumns = new Set{ListViewHelper.TYPE_INTEGER, ListViewHelper.TYPE_PERCENT, ListViewHelper.TYPE_DECIMAL, ListViewHelper.TYPE_CURRENCY, ListViewHelper.TYPE_DOUBLE, ListViewHelper.TYPE_NUMBER}; - public static Set validNameColumnNames = new Set{'Name', 'CaseNumber', 'Task_Name__c', 'ContractNumber'}; //holds the list of column names that if found will produce a link to the record. + public static Set validNameColumnNames = new Set{'Name', 'CaseNumber', 'Task_Name__c', 'ContractNumber', 'Id'}; //holds the list of column names that if found will produce a link to the record. public String listviewMode = null; //holds the mode that the list view is being displayed as. NULL = any mode public List_View__c listview = null; //holds the list view record associated with this request. @@ -496,7 +496,7 @@ public with sharing abstract class ListViewAbstract { ListViewParam useAPINames = null; if (lvConfig != null) - useAPINames = lvConfig.getParameter('ExportHeaderType'); + useAPINames = lvConfig.getParameter(ListViewHelper.PARAM_EXPORT_HEADER_TYPE); for (FieldWrapper column: fieldMetaData) { @@ -533,7 +533,7 @@ public with sharing abstract class ListViewAbstract { debug += ' Field Metadata' + '\n'; for (FieldWrapper metadata: fieldMetaData) { - debug += ' Name - ' + metadata.name + ', Column Index - ' + metadata.columnIndex + ', Label - ' + metadata.label + ', Type - ' + metadata.type + '\n'; + debug += ' Name - ' + metadata.name + ', Column Index - ' + metadata.columnIndex + ', Label - ' + metadata.label + ', Type - ' + metadata.type + ', Function - ' + metadata.getFunction() + '\n'; } debug += '' + '\n'; debug += ' Row Data' + '\n'; @@ -544,7 +544,7 @@ public with sharing abstract class ListViewAbstract { for (String fieldKey: row.fields.keySet()) { FieldWrapper field = row.fields.get(fieldKey); - debug += ' Key - ' + fieldKey + ', Name - ' + field.name + ', Long Name - ' + field.longName + ', Label - ' + field.label + ', Value - ' + field.getValue() + ', Type - ' + field.getType() + ', objValueId - ' + field.objValueId + ', URL Str - ' + field.urlStr + ', parentObjType - ' + field.parentObjType + ', isTotalsRow - ' + field.isTotalsRow + ', isEditable - ' + field.isEditable + '\n'; + debug += ' Key - ' + fieldKey + ', Name - ' + field.name + ', Long Name - ' + field.longName + ', Label - ' + field.label + ', Value - ' + field.getValue() + ', Type - ' + field.getType() + ', Function - ' + field.getFunction() + ', objValueId - ' + field.objValueId + ', URL Str - ' + field.urlStr + ', parentObjType - ' + field.parentObjType + ', isTotalsRow - ' + field.isTotalsRow + ', isEditable - ' + field.isEditable + '\n'; } debug += '' + '\n'; } @@ -1126,6 +1126,7 @@ public with sharing abstract class ListViewAbstract { public Boolean isTotalsRow; //identifies whether the row this field is a part of is a totals row. public String columnWidth; //indicates the preset column width. public String cssStyle; //the css style that should be applied to this field based on the column styles configured + public String functionApexClass;//if the field is to be calculated based on a provided function this is the apex class to use public FieldWrapper(String label, Object objValue, String type, String name, Integer columnIndex, String parentObjType) { @@ -1136,7 +1137,6 @@ public with sharing abstract class ListViewAbstract { this.columnIndex = columnIndex; this.isSortable = true; this.urlTarget = '_self'; - this.objValue = objValue; this.parentObjType = parentObjType; this.longName = ''; this.isTotalsRow = false; @@ -1144,10 +1144,10 @@ public with sharing abstract class ListViewAbstract { this.setType(type); //must be below setValue() as it uses value to determine type in some cases. String debug = '\n\n---------------------------------------------------------------\n'; - debug += 'Label - ' + this.label + '\n'; - debug += 'type - ' + this.type + '\n'; - debug += 'object - ' + this.objValue + '\n'; - debug += 'name - ' + this.name + '\n'; + debug += 'Label - ' + this.label + '\n'; + debug += 'type - ' + this.type + '\n'; + debug += 'object - ' + this.objValue + '\n'; + debug += 'name - ' + this.name + '\n'; debug += 'columnIndex - ' + this.columnIndex + '\n'; debug += '---------------------------------------------------------------\n'; System.debug(LoggingLevel.FINE, debug); @@ -1174,6 +1174,23 @@ public with sharing abstract class ListViewAbstract { System.debug(LoggingLevel.FINE, debug); } + public Boolean hasFunction() + { + System.debug(LoggingLevel.DEBUG, 'FUNCTION APEX CLASS - ' + this.functionApexClass); + if (String.isBlank(this.functionApexClass)) return false; + return true; + } + + public void setFunction(String functionApexClass) + { + this.functionApexClass = functionApexClass; + } + + public String getFunction() + { + return this.functionApexClass; + } + public void setCSSStyle(String cssStyle) { this.cssStyle = cssStyle; @@ -1261,8 +1278,10 @@ public with sharing abstract class ListViewAbstract { @AuraEnabled(cacheable=true) public Boolean getIsEditable() { - //only editable if its a field that is on the main object or a lookup. - if (this.name.countMatches('.') > 1 || (this.name.countMatches('.') == 1 && !this.name.contains('.Name'))) return false; + //only editable if its a field that is on the main object or a lookup and is not a function + if (this.name.countMatches('.') > 1 + || hasFunction() + || (this.name.countMatches('.') == 1 && !this.name.contains('.Name'))) return false; System.debug(LoggingLevel.DEBUG, 'IS EDITABLE - ' + isEditable + ' - ' + this.label); @@ -1307,7 +1326,7 @@ public with sharing abstract class ListViewAbstract { } /* - * Method to return a pretty human readable version of the value. + * Method to return a pretty human readable version of the value. This is used for printing out the PDF */ @AuraEnabled(cacheable=true) public String getPrettyValue() @@ -1328,6 +1347,17 @@ public with sharing abstract class ListViewAbstract { { String strValue = ''; + //if we have a function we turn the object value into another value + if (hasFunction()) { + System.debug(LoggingLevel.DEBUG, 'Before function - ' + this.objValue); + Map context = new Map(); + context.put('SObject', this.fieldObj); + this.objValue = ListViewHelper.processFunction(this.getFunction(), this.objValue, context); + System.debug(LoggingLevel.DEBUG, 'After function - ' + this.objValue); + } else { + System.debug(LoggingLevel.DEBUG, 'No function'); + } + //No Value! if (this.objValue == null) { strValue = ''; @@ -1681,6 +1711,13 @@ public with sharing abstract class ListViewAbstract { cloneField.urlStr = urlStr; cloneField.setFieldObj(fieldObj); cloneField.objValueId = objValueId; + cloneField.functionApexClass = functionApexClass; + cloneField.columnWidth = columnWidth; + cloneField.sortIndex = sortIndex; + cloneField.isEditable = isEditable; + cloneField.isTotalsRow = isTotalsRow; + cloneField.cssStyle = cssStyle; + //public String key; //if we are not deleting the field then it means its the header record so allow edits. if (!deleteFieldData) @@ -2000,6 +2037,7 @@ public with sharing abstract class ListViewAbstract { } else if (listViewMode == ListViewHelper.MODE_SPLIT) { if (param.getName() != ListViewHelper.PARAM_TOTAL_COLUMNS && param.getName() != ListViewHelper.PARAM_TOTAL_COLUMN_COLOR + && param.getName() != ListViewHelper.PARAM_CHILD_RECORD_COLOR && param.getName() != ListViewHelper.PARAM_REFRESH_RATE && param.getName() != ListViewHelper.PARAM_SINGLE_CLICK_REFRESH && param.getName() != ListViewHelper.PARAM_EXPORT_HEADER_TYPE diff --git a/force-app/main/default/classes/ListViewActionBL.cls b/force-app/main/default/classes/ListViewActionBL.cls index eb4fdfd..ce18c2b 100644 --- a/force-app/main/default/classes/ListViewActionBL.cls +++ b/force-app/main/default/classes/ListViewActionBL.cls @@ -1,7 +1,7 @@ /** * @description : * @author : tom@ansleyllc.com - * @last modified on : 05-23-2024 + * @last modified on : 08-01-2024 * @last modified by : tom@ansleyllc.com * Modifications Log * Ver Date Author Modification @@ -57,6 +57,7 @@ public with sharing class ListViewActionBL { action.addError('An apex class is required and must extend simpli_lv.ListViewAction'); } else { + //CLASS AND CONSTRUCTOR MUST BE GLOBAL FOR THIS TO WORK if (!bogusClassNames.contains(action.Apex_Class_Name__c)) { try { diff --git a/force-app/main/default/classes/ListViewBL.cls b/force-app/main/default/classes/ListViewBL.cls index a59c3ae..5e1b3d6 100644 --- a/force-app/main/default/classes/ListViewBL.cls +++ b/force-app/main/default/classes/ListViewBL.cls @@ -1,7 +1,7 @@ /** * @description : Class which holds all business logic related to the list view. * @author : tom@ansleyllc.com - * @last modified on : 01-25-2022 + * @last modified on : 08-09-2024 * @last modified by : tom@ansleyllc.com * Modifications Log * Ver Date Author Modification @@ -203,4 +203,13 @@ public with sharing class ListViewBL { } + public static void deleteListViewConfigs(Map oldRecords) + { + Set primaryKeys = HelperCollection.getStringFieldSet(oldRecords.values(), 'simpli_lv__Primary_Key__c'); + + Map configs = ListViewConfigHelper.getListViewConfigs(primaryKeys); + + HelperDatabase.deleteRecords(configs.values()); + + } } \ No newline at end of file diff --git a/force-app/main/default/classes/ListViewConfigHelper.cls b/force-app/main/default/classes/ListViewConfigHelper.cls index 326e40d..893b684 100644 --- a/force-app/main/default/classes/ListViewConfigHelper.cls +++ b/force-app/main/default/classes/ListViewConfigHelper.cls @@ -1,878 +1,878 @@ -/** - * @File Name : ListViewConfigHelper.cls - * @Description : - * @Author : tom@ansleyllc.com - * @Group : - * @Last Modified By : tom@ansleyllc.com - * @Last Modified On : 08-07-2024 - * @Modification Log : - * Ver Date Author Modification - * 1.0 6/11/2020 tom@ansleyllc.com Initial Version - * 2.0 06-18-2021 tom@ansleyllc.com Added offset to allow for larger datasets - * 3.0 07-27-2021 tom@ansleyllc.com Added logic for new admin page. - * 4.0 08-18-2021 tom@ansleyllc.com Updated strings to static final variables - * 4.0 08-25-2021 tom@ansleyllc.com Major enhancement to condition validation for more types - * 4.0 12-15-2021 tom@ansleyllc.com Removed old methods for checking object accessibility - * 5.0 07-02-2024 tom@ansleyllc.com Created global methods and set class to global -**/ -global with sharing class ListViewConfigHelper { - - private static Map orgWideConfigParams = null; //convenience variable to easily access param values - public static final String ALL = 'All'; - - public static final String DEBUG = 'Debug'; - - public static final String OPER_EQUAL = 'Equals'; - public static final String OPER_NOT_EQUAL = 'Not Equal'; - public static final String OPER_GREATER = 'Greater Than'; - public static final String OPER_LESS = 'Less Than'; - public static final String OPER_CONTAINS = 'Contains'; - - public static final String TODAY = 'Today'; - public static final String TOMORROW = 'Tomorrow'; - public static final String YESTERDAY = 'Yesterday'; - - public static final Set READ_ONLY_PARAMS = new Set{'ListViewObjects', 'IsInitialized', 'RefreshJob'}; - - //======================================================================= - // ORG WIDE PARAM METHODS - //======================================================================= - - @TestVisible - private static void resetCache(String objectName, String listViewName) - { - if (objectName == ALL && listViewName == ALL) - orgWideConfigParams = null; - CacheListViewConfig.remove(objectName, listViewName); - } - - /* - * Method to populate the cache. - */ - private static void populateOrgWideConfigParams() - { - if (orgWideConfigParams == null) - { - - orgWideConfigParams = new Map(); - - List_View_Config__c config = getListViewConfig(ALL, ALL); - - for (List_View_Config_Parameter__c param: config.List_View_Config_Parameters__r) - { - if (String.isEmpty(param.Parameter_Value__c)) - orgWideConfigParams.put(param.Parameter_Name__c, ''); - else - orgWideConfigParams.put(param.Parameter_Name__c, param.Parameter_Value__c); - } - } - - } - - public static Boolean isDebuggingOn() - { - return Boolean.valueOf(getOrgWideConfigParam(DEBUG)); - } - - /** - * @description Method to get the parameter details for the org wide configuration. - * @author tom@ansleyllc.com | 07-07-2021 - **/ - public static Map getOrgWideParamDetails() - { - return getListViewParamDetails(ALL, ALL); - } - - public static void setOrgWideConfigParam(String settingName, String value) - { - - List_View_Config__c config = getListViewConfig(ALL, ALL); - - for (List_View_Config_Parameter__c param: config.List_View_Config_Parameters__r) - { - if (param.Parameter_Name__c == settingName) - { - //update database - param.Parameter_Value__c = value; - - //bug fix to stop failures during auto updates of params without labels. - if (param.Parameter_Label__c == null) - param.Parameter_Label__c = param.Parameter_Name__c; - - HelperDatabase.updateRecord(param); - - //update session cache - if (orgWideConfigParams == null) - orgWideConfigParams = new Map(); - orgWideConfigParams.put(settingName, value); - break; - } - } - - //force org cache update - resetCache(ALL, ALL); - - - } - - /** - * @description Method to get all the org wide config params - * @author tom@ansleyllc.com | 07-07-2021 - * @return Map - **/ - public static Map getOrgWideConfigParams() - { - populateOrgWideConfigParams(); - - return orgWideConfigParams; - } - - /* - * Method to get a specific config setting for the org. Note that for this method if - * the setting is not found the provided value is returned. - */ - public static String getOrgWideConfigParam(String settingName, String defaultValue) - { - populateOrgWideConfigParams(); - - String value = defaultValue; - if (orgWideConfigParams.get(settingName) != null) - value = (String) orgWideConfigParams.get(settingName); - - return value; - } - - - /* - * Method to get a specific config setting for the org. Note that for this method if - * the setting is not found an exception is thrown. - */ - public static String getOrgWideConfigParam(String settingName) - { - populateOrgWideConfigParams(); - String value = (String) orgWideConfigParams.get(settingName); - if (value == null) - value = ''; - return value; - } - - public static String setOrgWideParams(Map newParams) - { - - Map paramDetails = getOrgWideParamDetails(); - - for (String paramName: newParams.keySet()) - { - - List_View_Config_Parameter__c paramDetail = paramDetails.get(paramName); - - Object newValue = newParams.get(paramName); - - System.debug(LoggingLevel.FINE, 'NEW VALUE - ' + paramName + ' - ' + newValue); - - if (paramDetail.Parameter_Type__c.toLowerCase() == ListViewHelper.TYPE_BOOLEAN) - { - paramDetail.Parameter_Value__c = String.valueOf(newValue); - } else if (paramDetail.Parameter_Type__c.toLowerCase() == ListViewHelper.TYPE_NUMBER) - { - paramDetail.Parameter_Value__c = String.valueOf(newValue); - } else if (paramDetail.Parameter_Name__c == 'ExcludedObjectTypes' //total hack as we cannot add new picklist values - || paramDetail.Parameter_Name__c == 'IncludedObjectTypes' - || paramDetail.Parameter_Name__c == 'ExcludedRecordPopoverTypes') - { - paramDetail.Parameter_Value__c = ''; - List values = (List) newValue; - for (Object value: values) - paramDetail.Parameter_Value__c += (String) value + ','; - paramDetail.Parameter_Value__c = paramDetail.Parameter_Value__c.removeEnd(','); - } else { - paramDetail.Parameter_Value__c = String.valueOf(newValue); - } - } - - update paramDetails.values(); - - //force org cache update - resetCache(ALL, ALL); - - //if the excluded list views was changed we need to delete those list views that are to be excluded - if (newParams.get('ExcludedListViews') != null && ((String) newParams.get('ExcludedListViews') != '')) - deleteExcludedListViews(); - - return ''; - } - - @TestVisible - private static void deleteExcludedListViews() - { - String soql = 'SELECT Id FROM List_View__c WHERE '; - - List excStrings = ListViewConfigHelper.getOrgWideConfigParam('ExcludedListViews').split('\\|'); - - if (excStrings.size() > 0 && excStrings[0] != '') - { - for (String excString: excStrings) - soql += 'API_Name__c LIKE \'%' + excString + '%\' OR '; - } - - soql = soql.removeEnd(' OR '); - - System.debug(LoggingLevel.DEBUG, 'Final SOQL - ' + soql); - - Map vars = new Map(); - vars.put(HelperBatch.VAR_SOQL_STATEMENT, soql); - vars.put(HelperBatch.VAR_BATCH_SIZE, 40); - vars.put(HelperBatch.VAR_OPERATION, HelperBatch.OPER_DELETE); - - HelperBatch job = new HelperBatch(vars); - - if (!Test.isRunningTest()) - Database.executeBatch(job); - - } - - - //======================================================================= - // LIST VIEW PARAM METHODS - //======================================================================= - - /** - * @description Method to get the parameter details for a provided list view. - * @author tom@ansleyllc.com | 07-07-2021 - **/ - public static Map getListViewParamDetails(String objectName, String listViewName) - { - List params = [SELECT Parameter_Name__c, - Parameter_Type__c, - Parameter_Label__c, - Parameter_Value__c - FROM List_View_Config_Parameter__c - WHERE List_View_Config__r.Name = :listViewName - AND List_View_Config__r.List_View_Object__c = :objectName]; - - Map paramMap = new Map(); - for (List_View_Config_Parameter__c param: params) - paramMap.put(param.Parameter_Name__c, param); - - return paramMap; - } - - public static String updateListViewParam(String objectName, String listViewName, String paramName, String paramValue, String paramLabel, String paramType) - { - - System.debug(LoggingLevel.FINE, 'Starting updateListViewParam(' + objectName + ', ' + listViewName + ', ' + paramName + ', ' + paramValue + ')'); - - String errorStr = ListViewConfigHelper.validateParameter(objectName, listViewName, paramName, paramValue); - - if (errorStr == '') - { - - SObjectAccessDecision dec = - Security.stripInaccessible(AccessType.READABLE, - [SELECT Parameter_Name__c, - Parameter_Type__c, - Parameter_Value__c - FROM List_View_Config_Parameter__c - WHERE List_View_Config__r.Name = :listViewName - AND List_View_Config__r.List_View_Object__c = :objectName - AND Parameter_Name__c = :paramName]); - - List params = (List) dec.getRecords(); - - //if we have a parameter - if (params.size() > 0) - { - System.debug(LoggingLevel.FINE, 'Old param found - ' + params[0]); - - params[0].Parameter_Value__c = paramValue; - - //we have no parameter so we need to create one - } else { - - List_View_Config__c config = getListViewConfig(objectName, listViewName); - List_View_Config_Parameter__c param = new List_View_Config_Parameter__c(); - param.List_View_Config__c = config.Id; - param.Parameter_Name__c = paramName; - param.Parameter_Type__c = paramType; - param.Parameter_Value__c = paramValue; - param.Parameter_Label__c = paramLabel; - params.add(param); - } - - System.debug(LoggingLevel.FINE, 'Updated params - ' + params); - - HelperDatabase.upsertRecords(params); - - //force org cache update - resetCache(objectName, listViewName); - - System.debug(LoggingLevel.FINE, 'Finished upserting param!'); - } - - return errorStr; - - } - - public static void addListViewCondition(String objectName, String listViewName, String fieldName, String fieldOperator, String fieldValue, String fieldOrder, String fieldColor) - { - - //get the list view config for the parameter - List_View_Config__c lvConfig = getListViewConfig(objectName, listViewName); - - List_View_Config_Condition__c condition = new List_View_Config_Condition__c(); - condition.Field_Name__c = fieldName; - condition.Highlight_Color__c = fieldColor; - condition.Operator__c = fieldOperator; - condition.Order__c = fieldOrder; - condition.Value__c = fieldValue; - condition.List_View_Config__c = lvConfig.Id; - - System.debug(LoggingLevel.DEBUG, 'ADDING CONDITION - ' + condition); - HelperDatabase.insertRecord(condition); - - //force org cache update - resetCache(objectName, listViewName); - - } - - public static void addListViewColumnStyle(String objectName, String listViewName, String fieldName, String decoration, String font, String style, String transform, String variant, String weight, String alignment) - { - - //get the list view config for the column style - List_View_Config__c lvConfig = getListViewConfig(objectName, listViewName); - - List_View_Config_Column_Style__c columnStyle = new List_View_Config_Column_Style__c(); - columnStyle.Field_Name__c = fieldName; - columnStyle.List_View_Config__c = lvConfig.Id; - columnStyle.Style_Decoration__c = decoration; - columnStyle.Style_Font__c = font; - columnStyle.Style_Style__c = style; - columnStyle.Style_Transform__c = transform; - columnStyle.Style_Variant__c = variant; - columnStyle.Style_Weight__c = weight; - columnStyle.Style_Alignment__c = alignment; - - System.debug(LoggingLevel.DEBUG, 'ADDING COLUMN STYLE - ' + columnStyle); - HelperDatabase.insertRecord(columnStyle); - - //force org cache update - resetCache(objectName, listViewName); - - } - - public static void deleteListViewCondition(String id) - { - System.debug(LoggingLevel.FINE, 'Deleting list view condition - ' + id); - - SObjectAccessDecision dec = - Security.stripInaccessible(AccessType.READABLE, - [SELECT Id - FROM List_View_Config_Condition__c - WHERE Id = :id]); - - List conditions = (List) dec.getRecords(); - - HelperDatabase.deleteRecords(conditions); - - } - - public static void deleteListViewColumnStyle(String id) - { - System.debug(LoggingLevel.FINE, 'Deleting list view column style - ' + id); - - SObjectAccessDecision dec = - Security.stripInaccessible(AccessType.READABLE, - [SELECT Id - FROM List_View_Config_Column_Style__c - WHERE Id = :id]); - - List columnStyles = (List) dec.getRecords(); - - HelperDatabase.deleteRecords(columnStyles); - - } - - public static String getListViewConfigParameter(String objectName, String listViewName, String paramName) - { - System.debug(LoggingLevel.FINE, 'Starting getListViewConfigParameter(' + objectName + ', ' + listViewName + ', ' + paramName + ')'); - - String paramValue = null; - List_View_Config__c config = getListViewConfig(objectName, listViewName); - if (config != null) - { - for (List_View_Config_Parameter__c param: config.List_View_Config_Parameters__r) - { - if (param.Parameter_Name__c == paramName) - paramValue = param.Parameter_Value__c; - } - } - return paramValue; - } - - public static List_View_Config__c getListViewConfig(String objectName, String listViewName) - { - List_View_Config__c config = CacheListViewConfig.get(objectName, listViewName); - return config; - } - - /* - * - delete [SELECT Id FROM List_View_Config__c WHERE CreatedDate > 2021-10-12T12:00:00Z]; - */ - public static List_View_Config__c getListViewConfigCache(String objectName, String listViewName) - { - System.debug(LoggingLEvel.FINE, 'Starting getListViewConfig(' + objectName + ', ' + listViewName + ')'); - - SObjectAccessDecision dec = - Security.stripInaccessible(AccessType.READABLE, - [SELECT Name, - List_View_Object__c, - List_View_Label__c, - LastModifiedDate, - LastModifiedBy.Name, - Primary_Key__c, - (SELECT Parameter_Name__c, - Parameter_Type__c, - Parameter_Label__c, - Parameter_Value__c - FROM List_View_Config_Parameters__r - ORDER BY Parameter_Label__c), - (SELECT Field_Name__c, - Highlight_Color__c, - Operator__c, - Order__c, - Value__c - FROM List_View_Config_Conditions__r - ORDER BY Order__c ASC), - (SELECT Field_Name__c, - Style_Decoration__c, - Style_Font__c, - Style_Style__c, - Style_Transform__c, - Style_Variant__c, - Style_Weight__c, - Style_Alignment__c - FROM List_View_Config_Column_Styles__r - ORDER BY Field_Name__c) - FROM List_View_Config__c - WHERE Name = :listViewName - AND List_View_Object__c = :objectName]); - - List configs = (List) dec.getRecords(); - - if (configs.size() > 1) - throw new ListViewException('Found more than one list view config with name - ' + listViewName + ' and object - ' + objectName); - else if (configs.size() == 0) { - if (objectName == 'All' && listViewName == 'All') - throw new ListViewException('Global config not found. Go to the admin page and either import a config file or create a new config'); - else - return null; - //throw new ListViewException('No list view config was found with name - ' + listViewName + ' and object - ' + objectName); - } - System.debug(LoggingLevel.FINE, 'Returning configs - ' + configs); - return configs[0]; - } - - public static String getPrimaryKey(String objectName, String listViewName) - { - return objectName.replace(' ', '_') + ':' + listViewName; - } - - public static Map getListViewConfigs(Set primaryKeys) - { - List configs = [SELECT Name, - List_View_Object__c, - List_View_Label__c, - LastModifiedDate, - LastModifiedBy.Name, - Primary_Key__c, - (SELECT Parameter_Name__c, - Parameter_Type__c, - Parameter_Label__c, - Parameter_Value__c - FROM List_View_Config_Parameters__r - ORDER BY Parameter_Label__c), - (SELECT Field_Name__c, - Highlight_Color__c, - Operator__c, - Order__c, - Value__c - FROM List_View_Config_Conditions__r - ORDER BY Order__c ASC), - (SELECT Field_Name__c, - Style_Decoration__c, - Style_Font__c, - Style_Style__c, - Style_Transform__c, - Style_Variant__c, - Style_Weight__c, - Style_Alignment__c - FROM List_View_Config_Column_Styles__r - ORDER BY Field_Name__c) - FROM List_View_Config__c - WHERE Primary_Key__c IN :primaryKeys]; - - Map mappedConfigs = new Map(); - for (List_View_Config__c config: configs) - { - mappedConfigs.put(config.Primary_Key__c, config); - } - - return mappedConfigs; - - } - - public static Map getListViewConfigsById(Set ids) - { - return new Map( - [SELECT Name, - List_View_Object__c, - List_View_Label__c, - LastModifiedDate, - LastModifiedBy.Name, - Primary_Key__c, - (SELECT Parameter_Name__c, - Parameter_Type__c, - Parameter_Label__c, - Parameter_Value__c - FROM List_View_Config_Parameters__r - ORDER BY Parameter_Label__c), - (SELECT Field_Name__c, - Highlight_Color__c, - Operator__c, - Order__c, - Value__c - FROM List_View_Config_Conditions__r - ORDER BY Order__c ASC), - (SELECT Field_Name__c, - Style_Decoration__c, - Style_Font__c, - Style_Style__c, - Style_Transform__c, - Style_Variant__c, - Style_Weight__c, - Style_Alignment__c - FROM List_View_Config_Column_Styles__r - ORDER BY Field_Name__c) - FROM List_View_Config__c - WHERE Id IN :ids]); - } - - /** - * @description Method to validate parameter updates - * @author tom@ansleyllc.com | 08-17-2020 - * @param paramName the name of the parameter being updated - * @param paramValue the value the parameter is being updated to - * @return String the error message if the parameter is invalid. - **/ - public static String validateParameter(String objectName, String listViewName, String paramName, String paramValue) - { - String error = ''; - - if (paramName == ListViewHelper.PARAM_REFRESH_RATE) - { - - try { - Integer rate = Integer.valueOf(paramValue); - - if (rate < 10 || rate > 500) - { - error = 'Refresh rate must be between 10 and 500'; - } - } catch (Exception e) { - error = 'Refresh rate must be an integer value'; - } - - } else if (paramName == ListViewHelper.PARAM_RETURN_SIZE) - { - try { - Integer size = Integer.valueOf(paramValue); - - Integer maxRowsDisplayed = Integer.valueOf(ListViewConfigHelper.getOrgWideConfigParam('MaxRowsDisplayed')); - - if (size > maxRowsDisplayed) - { - error = 'Return size must be no greater than ' + maxRowsDisplayed; - } - } catch (Exception e) { - error = 'Return size must be an integer value'; - } - - - } else if (paramName == ListViewHelper.PARAM_TOTAL_COLUMNS) - { - } else if (paramName == ListViewHelper.PARAM_ADD_FIELDS) - { - if (paramValue != '') - { - - try { - - //get the list view - List_View__c listview = ListViewHelper.getListViews(objectName, listViewName).values()[0]; - - //get the list views query - String query = listview.Core_ListView_Query__c; - - //get the fields on the SELECT - String selectStr = query.substringBetween('SELECT ', ' FROM '); - - selectStr += ', ' + paramValue; - - String soql = 'SELECT ' + selectStr + ' FROM ' + objectName + ' LIMIT 1'; - - System.debug(LoggingLevel.FINE, 'Param Validation SOQL - ' + soql); - - List result = Database.query(soql); - - } catch (Exception e) { - error = 'The additional fields are invalid. Please ensure syntax is correct, there are no duplicate fields, and the names are valid API field names - ' + e.getMessage(); - } - } - - } else if (paramName == ListViewHelper.PARAM_SPLIT_COLUMNS) - { - paramValue = paramValue.remove(' '); - List fieldNames = paramValue.split(','); - - if (fieldNames.size() > 4) { - error = 'Only 4 columns can be added.'; - } else { - - try { - - String soql = 'SELECT ' + paramValue + ' FROM ' + objectName + ' LIMIT 1'; - - System.debug(LoggingLevel.FINE, 'Param Validation SOQL - ' + soql); - - List result = Database.query(soql); - - } catch (Exception e) { - error = 'The split column fields are invalid. Please ensure syntax is correct and the names are valid API field names - ' + e.getMessage(); - } - } - } - - return error; - } - - /** - * @description Method to validate a condition given an object being validated against and - * a condition. The return of an HTML color indicates the condition yielded a result. If no - * color was returned the value did meet any criteria. - * - * NOTE - DATE and DATETIME come through as millisecond strings and therefore need to be converted. - * - * @author tom@ansleyllc.com | 08-05-2020 - * @param value the value being validated against - * @param type the type of the value being validated against - * @param conditions the conditions the value is being validated against - * @return String either an HTML color (condition match) or empty string (condition not met) - **/ - public static String validateFieldCondition(Object value, String type, List_View_Config_Condition__c condition) - { - try { - System.debug(LoggingLevel.FINE, 'Starting validateFieldCondition(' + value + ',' + type + ')'); - System.debug(LoggingLevel.FINE, 'Conditions - ' + condition); - - if (value == null || condition == null) return ''; - - System.debug(LoggingLevel.FINE, 'Value = ' + value); - System.debug(LoggingLevel.FINE, 'Type = ' + type); - System.debug(LoggingLevel.FINE, 'Operator = ' + condition.Operator__c); - System.debug(LoggingLevel.FINE, 'condValue = ' + condition.Value__c); - - //if we have a meaningless comparison then skip - if ((value == null || value == '') && type != ListViewHelper.TYPE_STRING) return ''; - - //EQUAL - if (condition.Operator__c == OPER_EQUAL) { - if (type == ListViewHelper.TYPE_DOUBLE) { - if (value == Double.valueOf(condition.Value__c)) return condition.Highlight_Color__c; - }else if (type == ListViewHelper.TYPE_DATE) { - - Date dte = getDateValue(condition.Value__c); - if (HelperDate.convertMillisToDate((String) value) == dte) return condition.Highlight_Color__c; - - } else if (type == ListViewHelper.TYPE_DATETIME) { - - DateTime dte = getDateTimeValue(condition.Value__c); - if (HelperDate.convertMillisToDateTime((String) value) == dte) return condition.Highlight_Color__c; - - } else if (type == ListViewHelper.TYPE_TIME) { - if (HelperDate.getTimeFromString((String) value) == HelperDate.getTimeFromString(condition.Value__c)) return condition.Highlight_Color__c; - } else if (String.valueOf(value) == condition.Value__c) return condition.Highlight_Color__c; - - //NOT EQUAL - } else if (condition.Operator__c == OPER_NOT_EQUAL) { - if (type == ListViewHelper.TYPE_DOUBLE) { - if (value != Double.valueOf(condition.Value__c)) return condition.Highlight_Color__c; - } else if (type == ListViewHelper.TYPE_DATE) { - - Date dte = getDateValue(condition.Value__c); - if (HelperDate.convertMillisToDate((String) value) != dte) return condition.Highlight_Color__c; - - } else if (type == ListViewHelper.TYPE_DATETIME) { - - DateTime dte = getDateTimeValue(condition.Value__c); - if (HelperDate.convertMillisToDateTime((String) value) != dte) return condition.Highlight_Color__c; - - } else if (type == ListViewHelper.TYPE_TIME) { - if (HelperDate.getTimeFromString((String) value) != HelperDate.getTimeFromString(condition.Value__c)) return condition.Highlight_Color__c; - } else if (String.valueOf(value) != condition.Value__c) return condition.Highlight_Color__c; - - //GREATER THAN - } else if (condition.Operator__c == OPER_GREATER) { - if ((type == ListViewHelper.TYPE_STRING - || type == ListViewHelper.TYPE_EMAIL - || type == ListViewHelper.TYPE_MULTI_PICK - || type == ListViewHelper.TYPE_PHONE - || type == ListViewHelper.TYPE_PICKLIST - || type == ListViewHelper.TYPE_RICH_TEXTAREA - || type == ListViewHelper.TYPE_TEXTAREA - || type == ListViewHelper.TYPE_URL) - && String.valueOf(value) > condition.Value__c) return condition.Highlight_Color__c; - else if ((type == ListViewHelper.TYPE_DECIMAL - || type == ListViewHelper.TYPE_CURRENCY - || type == ListViewHelper.TYPE_NUMBER - || type == ListViewHelper.TYPE_PERCENT) - && Decimal.valueOf(String.valueOf(value)) > Decimal.valueOf(condition.Value__c)) return condition.Highlight_Color__c; - else if (type == ListViewHelper.TYPE_DATE) { - - Date dte = getDateValue(condition.Value__c); - if (HelperDate.convertMillisToDate((String) value) > dte) return condition.Highlight_Color__c; - - } else if (type == ListViewHelper.TYPE_DATETIME) { - - DateTime dte = getDateTimeValue(condition.Value__c); - if (HelperDate.convertMillisToDateTime((String) value) > dte) return condition.Highlight_Color__c; - - } else if (type == ListViewHelper.TYPE_DOUBLE && Double.valueOf(value) > Double.valueOf(condition.Value__c)) return condition.Highlight_Color__c; - else if (type == ListViewHelper.TYPE_INTEGER && Integer.valueOf(value) > Integer.valueOf(condition.Value__c)) return condition.Highlight_Color__c; - else if (type == ListViewHelper.TYPE_TIME && HelperDate.getTimeFromString((String) value) > HelperDate.getTimeFromString(condition.Value__c)) return condition.Highlight_Color__c; - - //LESS THAN - } else if (condition.Operator__c == OPER_LESS) { - if ((type == ListViewHelper.TYPE_STRING - || type == ListViewHelper.TYPE_EMAIL - || type == ListViewHelper.TYPE_MULTI_PICK - || type == ListViewHelper.TYPE_PHONE - || type == ListViewHelper.TYPE_PICKLIST - || type == ListViewHelper.TYPE_RICH_TEXTAREA - || type == ListViewHelper.TYPE_TEXTAREA - || type == ListViewHelper.TYPE_URL) - && String.valueOf(value) < condition.Value__c) return condition.Highlight_Color__c; - else if ((type == ListViewHelper.TYPE_DECIMAL - || type == ListViewHelper.TYPE_CURRENCY - || type == ListViewHelper.TYPE_NUMBER - || type == ListViewHelper.TYPE_PERCENT) - && Decimal.valueOf(String.valueOf(value)) < Decimal.valueOf(condition.Value__c)) return condition.Highlight_Color__c; - else if (type == ListViewHelper.TYPE_DATE) { - - Date dte = getDateValue(condition.Value__c); - if (HelperDate.convertMillisToDate((String) value) < dte) return condition.Highlight_Color__c; - - } else if (type == ListViewHelper.TYPE_DATETIME) { - - DateTime dte = getDateTimeValue(condition.Value__c); - if (HelperDate.convertMillisToDateTime((String) value) < dte) return condition.Highlight_Color__c; - - } else if (type == ListViewHelper.TYPE_DOUBLE && Double.valueOf(value) < Double.valueOf(condition.Value__c)) return condition.Highlight_Color__c; - else if (type == ListViewHelper.TYPE_INTEGER && Integer.valueOf(value) < Integer.valueOf(condition.Value__c)) return condition.Highlight_Color__c; - else if (type == ListViewHelper.TYPE_TIME && HelperDate.getTimeFromString((String) value) < HelperDate.getTimeFromString(condition.Value__c)) return condition.Highlight_Color__c; - - //CONTAINS - } else if (condition.Operator__c == OPER_CONTAINS) { - if (type == ListViewHelper.TYPE_DATETIME - || type == ListViewHelper.TYPE_DATE - || type == ListViewHelper.TYPE_TIME - || type == ListViewHelper.TYPE_PERCENT - || type == ListViewHelper.TYPE_CURRENCY) throw new ListViewException('Cannot use "Contains" operator on DateTime, Date, Time, Percent or Currency types'); - if (String.valueOf(value).contains(condition.Value__c)) return condition.Highlight_Color__c; - } - } catch (Exception e) { - String message = 'Exception during ListViewController.validateFieldCondition(' + value + ',' + type + ',' + condition + ') ' + ListViewException.getExtendedString(e); - if (!HelperLimits.hasReachedLimit('DMLStatements')) - ListViewErrorHelper.createFutureUsageError(message); - } - return ''; - } - - - - - - - - - //================================================================================================================ - // GLOBAL METHODS - //================================================================================================================ - - global static void updateOrgWideParam(String settingName, String value) - { - setOrgWideConfigParam(settingName, value); - } - - global static void updateOrgWideParams(Map newParams) - { - setOrgWideParams(newParams); - } - - global static String getOrgWideParam(String settingName) - { - return ListViewConfigHelper.getOrgWideConfigParam(settingName); - } - - global static Map getOrgWideParams() - { - Map params = new Map(); - - Map orgWideParams = ListViewConfigHelper.getOrgWideConfigParams(); - - for (String key: orgWideParams.keySet()) - params.put(key, String.valueOf(orgWideParams.get(key))); - - return params; - } - - - - - - - //================================================================================================================ - // PRIVATE METHODS - //================================================================================================================ - - private static Date getDateValue(String condValue) - { - Date dte = null; - if (condValue == TODAY) - dte = System.today(); - else if (condValue == TOMORROW) - dte = System.today().addDays(1); - else if (condValue == YESTERDAY) - dte = System.today().addDays(-1); - else - dte = Date.valueOf(condValue); - - return dte; - } - - private static DateTime getDateTimeValue(String condValue) - { - DateTime dte = null; - if (condValue == TODAY) - dte = HelperDate.convertDateToDateTime(System.today()); - else if (condValue == TOMORROW) - dte = HelperDate.convertDateToDateTime(System.today().addDays(1)); - else if (condValue == YESTERDAY) - dte = HelperDate.convertDateToDateTime(System.today().addDays(-1)); - else - dte = DateTime.valueOf(condValue); - - return dte; - } - +/** + * @File Name : ListViewConfigHelper.cls + * @Description : + * @Author : tom@ansleyllc.com + * @Group : + * @Last Modified By : tom@ansleyllc.com + * @Last Modified On : 08-07-2024 + * @Modification Log : + * Ver Date Author Modification + * 1.0 6/11/2020 tom@ansleyllc.com Initial Version + * 2.0 06-18-2021 tom@ansleyllc.com Added offset to allow for larger datasets + * 3.0 07-27-2021 tom@ansleyllc.com Added logic for new admin page. + * 4.0 08-18-2021 tom@ansleyllc.com Updated strings to static final variables + * 4.0 08-25-2021 tom@ansleyllc.com Major enhancement to condition validation for more types + * 4.0 12-15-2021 tom@ansleyllc.com Removed old methods for checking object accessibility + * 5.0 07-02-2024 tom@ansleyllc.com Created global methods and set class to global +**/ +global with sharing class ListViewConfigHelper { + + private static Map orgWideConfigParams = null; //convenience variable to easily access param values + public static final String ALL = 'All'; + + public static final String DEBUG = 'Debug'; + + public static final String OPER_EQUAL = 'Equals'; + public static final String OPER_NOT_EQUAL = 'Not Equal'; + public static final String OPER_GREATER = 'Greater Than'; + public static final String OPER_LESS = 'Less Than'; + public static final String OPER_CONTAINS = 'Contains'; + + public static final String TODAY = 'Today'; + public static final String TOMORROW = 'Tomorrow'; + public static final String YESTERDAY = 'Yesterday'; + + public static final Set READ_ONLY_PARAMS = new Set{'ListViewObjects', 'IsInitialized', 'RefreshJob'}; + + //======================================================================= + // ORG WIDE PARAM METHODS + //======================================================================= + + @TestVisible + private static void resetCache(String objectName, String listViewName) + { + if (objectName == ALL && listViewName == ALL) + orgWideConfigParams = null; + CacheListViewConfig.remove(objectName, listViewName); + } + + /* + * Method to populate the cache. + */ + private static void populateOrgWideConfigParams() + { + if (orgWideConfigParams == null) + { + + orgWideConfigParams = new Map(); + + List_View_Config__c config = getListViewConfig(ALL, ALL); + + for (List_View_Config_Parameter__c param: config.List_View_Config_Parameters__r) + { + if (String.isEmpty(param.Parameter_Value__c)) + orgWideConfigParams.put(param.Parameter_Name__c, ''); + else + orgWideConfigParams.put(param.Parameter_Name__c, param.Parameter_Value__c); + } + } + + } + + public static Boolean isDebuggingOn() + { + return Boolean.valueOf(getOrgWideConfigParam(DEBUG)); + } + + /** + * @description Method to get the parameter details for the org wide configuration. + * @author tom@ansleyllc.com | 07-07-2021 + **/ + public static Map getOrgWideParamDetails() + { + return getListViewParamDetails(ALL, ALL); + } + + public static void setOrgWideConfigParam(String settingName, String value) + { + + List_View_Config__c config = getListViewConfig(ALL, ALL); + + for (List_View_Config_Parameter__c param: config.List_View_Config_Parameters__r) + { + if (param.Parameter_Name__c == settingName) + { + //update database + param.Parameter_Value__c = value; + + //bug fix to stop failures during auto updates of params without labels. + if (param.Parameter_Label__c == null) + param.Parameter_Label__c = param.Parameter_Name__c; + + HelperDatabase.updateRecord(param); + + //update session cache + if (orgWideConfigParams == null) + orgWideConfigParams = new Map(); + orgWideConfigParams.put(settingName, value); + break; + } + } + + //force org cache update + resetCache(ALL, ALL); + + + } + + /** + * @description Method to get all the org wide config params + * @author tom@ansleyllc.com | 07-07-2021 + * @return Map + **/ + public static Map getOrgWideConfigParams() + { + populateOrgWideConfigParams(); + + return orgWideConfigParams; + } + + /* + * Method to get a specific config setting for the org. Note that for this method if + * the setting is not found the provided value is returned. + */ + public static String getOrgWideConfigParam(String settingName, String defaultValue) + { + populateOrgWideConfigParams(); + + String value = defaultValue; + if (orgWideConfigParams.get(settingName) != null) + value = (String) orgWideConfigParams.get(settingName); + + return value; + } + + + /* + * Method to get a specific config setting for the org. Note that for this method if + * the setting is not found an exception is thrown. + */ + public static String getOrgWideConfigParam(String settingName) + { + populateOrgWideConfigParams(); + String value = (String) orgWideConfigParams.get(settingName); + if (value == null) + value = ''; + return value; + } + + public static String setOrgWideParams(Map newParams) + { + + Map paramDetails = getOrgWideParamDetails(); + + for (String paramName: newParams.keySet()) + { + + List_View_Config_Parameter__c paramDetail = paramDetails.get(paramName); + + Object newValue = newParams.get(paramName); + + System.debug(LoggingLevel.FINE, 'NEW VALUE - ' + paramName + ' - ' + newValue); + + if (paramDetail.Parameter_Type__c.toLowerCase() == ListViewHelper.TYPE_BOOLEAN) + { + paramDetail.Parameter_Value__c = String.valueOf(newValue); + } else if (paramDetail.Parameter_Type__c.toLowerCase() == ListViewHelper.TYPE_NUMBER) + { + paramDetail.Parameter_Value__c = String.valueOf(newValue); + } else if (paramDetail.Parameter_Name__c == 'ExcludedObjectTypes' //total hack as we cannot add new picklist values + || paramDetail.Parameter_Name__c == 'IncludedObjectTypes' + || paramDetail.Parameter_Name__c == 'ExcludedRecordPopoverTypes') + { + paramDetail.Parameter_Value__c = ''; + List values = (List) newValue; + for (Object value: values) + paramDetail.Parameter_Value__c += (String) value + ','; + paramDetail.Parameter_Value__c = paramDetail.Parameter_Value__c.removeEnd(','); + } else { + paramDetail.Parameter_Value__c = String.valueOf(newValue); + } + } + + update paramDetails.values(); + + //force org cache update + resetCache(ALL, ALL); + + //if the excluded list views was changed we need to delete those list views that are to be excluded + if (newParams.get('ExcludedListViews') != null && ((String) newParams.get('ExcludedListViews') != '')) + deleteExcludedListViews(); + + return ''; + } + + @TestVisible + private static void deleteExcludedListViews() + { + String soql = 'SELECT Id FROM List_View__c WHERE '; + + List excStrings = ListViewConfigHelper.getOrgWideConfigParam('ExcludedListViews').split('\\|'); + + if (excStrings.size() > 0 && excStrings[0] != '') + { + for (String excString: excStrings) + soql += 'API_Name__c LIKE \'%' + excString + '%\' OR '; + } + + soql = soql.removeEnd(' OR '); + + System.debug(LoggingLevel.DEBUG, 'Final SOQL - ' + soql); + + Map vars = new Map(); + vars.put(HelperBatch.VAR_SOQL_STATEMENT, soql); + vars.put(HelperBatch.VAR_BATCH_SIZE, 40); + vars.put(HelperBatch.VAR_OPERATION, HelperBatch.OPER_DELETE); + + HelperBatch job = new HelperBatch(vars); + + if (!Test.isRunningTest()) + Database.executeBatch(job); + + } + + + //======================================================================= + // LIST VIEW PARAM METHODS + //======================================================================= + + /** + * @description Method to get the parameter details for a provided list view. + * @author tom@ansleyllc.com | 07-07-2021 + **/ + public static Map getListViewParamDetails(String objectName, String listViewName) + { + List params = [SELECT Parameter_Name__c, + Parameter_Type__c, + Parameter_Label__c, + Parameter_Value__c + FROM List_View_Config_Parameter__c + WHERE List_View_Config__r.Name = :listViewName + AND List_View_Config__r.List_View_Object__c = :objectName]; + + Map paramMap = new Map(); + for (List_View_Config_Parameter__c param: params) + paramMap.put(param.Parameter_Name__c, param); + + return paramMap; + } + + public static String updateListViewParam(String objectName, String listViewName, String paramName, String paramValue, String paramLabel, String paramType) + { + + System.debug(LoggingLevel.FINE, 'Starting updateListViewParam(' + objectName + ', ' + listViewName + ', ' + paramName + ', ' + paramValue + ')'); + + String errorStr = ListViewConfigHelper.validateParameter(objectName, listViewName, paramName, paramValue); + + if (errorStr == '') + { + + SObjectAccessDecision dec = + Security.stripInaccessible(AccessType.READABLE, + [SELECT Parameter_Name__c, + Parameter_Type__c, + Parameter_Value__c + FROM List_View_Config_Parameter__c + WHERE List_View_Config__r.Name = :listViewName + AND List_View_Config__r.List_View_Object__c = :objectName + AND Parameter_Name__c = :paramName]); + + List params = (List) dec.getRecords(); + + //if we have a parameter + if (params.size() > 0) + { + System.debug(LoggingLevel.FINE, 'Old param found - ' + params[0]); + + params[0].Parameter_Value__c = paramValue; + + //we have no parameter so we need to create one + } else { + + List_View_Config__c config = getListViewConfig(objectName, listViewName); + List_View_Config_Parameter__c param = new List_View_Config_Parameter__c(); + param.List_View_Config__c = config.Id; + param.Parameter_Name__c = paramName; + param.Parameter_Type__c = paramType; + param.Parameter_Value__c = paramValue; + param.Parameter_Label__c = paramLabel; + params.add(param); + } + + System.debug(LoggingLevel.FINE, 'Updated params - ' + params); + + HelperDatabase.upsertRecords(params); + + //force org cache update + resetCache(objectName, listViewName); + + System.debug(LoggingLevel.FINE, 'Finished upserting param!'); + } + + return errorStr; + + } + + public static void addListViewCondition(String objectName, String listViewName, String fieldName, String fieldOperator, String fieldValue, String fieldOrder, String fieldColor) + { + + //get the list view config for the parameter + List_View_Config__c lvConfig = getListViewConfig(objectName, listViewName); + + List_View_Config_Condition__c condition = new List_View_Config_Condition__c(); + condition.Field_Name__c = fieldName; + condition.Highlight_Color__c = fieldColor; + condition.Operator__c = fieldOperator; + condition.Order__c = fieldOrder; + condition.Value__c = fieldValue; + condition.List_View_Config__c = lvConfig.Id; + + System.debug(LoggingLevel.DEBUG, 'ADDING CONDITION - ' + condition); + HelperDatabase.insertRecord(condition); + + //force org cache update + resetCache(objectName, listViewName); + + } + + public static void addListViewColumnStyle(String objectName, String listViewName, String fieldName, String decoration, String font, String style, String transform, String variant, String weight, String alignment) + { + + //get the list view config for the column style + List_View_Config__c lvConfig = getListViewConfig(objectName, listViewName); + + List_View_Config_Column_Style__c columnStyle = new List_View_Config_Column_Style__c(); + columnStyle.Field_Name__c = fieldName; + columnStyle.List_View_Config__c = lvConfig.Id; + columnStyle.Style_Decoration__c = decoration; + columnStyle.Style_Font__c = font; + columnStyle.Style_Style__c = style; + columnStyle.Style_Transform__c = transform; + columnStyle.Style_Variant__c = variant; + columnStyle.Style_Weight__c = weight; + columnStyle.Style_Alignment__c = alignment; + + System.debug(LoggingLevel.DEBUG, 'ADDING COLUMN STYLE - ' + columnStyle); + HelperDatabase.insertRecord(columnStyle); + + //force org cache update + resetCache(objectName, listViewName); + + } + + public static void deleteListViewCondition(String id) + { + System.debug(LoggingLevel.FINE, 'Deleting list view condition - ' + id); + + SObjectAccessDecision dec = + Security.stripInaccessible(AccessType.READABLE, + [SELECT Id + FROM List_View_Config_Condition__c + WHERE Id = :id]); + + List conditions = (List) dec.getRecords(); + + HelperDatabase.deleteRecords(conditions); + + } + + public static void deleteListViewColumnStyle(String id) + { + System.debug(LoggingLevel.FINE, 'Deleting list view column style - ' + id); + + SObjectAccessDecision dec = + Security.stripInaccessible(AccessType.READABLE, + [SELECT Id + FROM List_View_Config_Column_Style__c + WHERE Id = :id]); + + List columnStyles = (List) dec.getRecords(); + + HelperDatabase.deleteRecords(columnStyles); + + } + + public static String getListViewConfigParameter(String objectName, String listViewName, String paramName) + { + System.debug(LoggingLevel.FINE, 'Starting getListViewConfigParameter(' + objectName + ', ' + listViewName + ', ' + paramName + ')'); + + String paramValue = null; + List_View_Config__c config = getListViewConfig(objectName, listViewName); + if (config != null) + { + for (List_View_Config_Parameter__c param: config.List_View_Config_Parameters__r) + { + if (param.Parameter_Name__c == paramName) + paramValue = param.Parameter_Value__c; + } + } + return paramValue; + } + + public static List_View_Config__c getListViewConfig(String objectName, String listViewName) + { + List_View_Config__c config = CacheListViewConfig.get(objectName, listViewName); + return config; + } + + /* + * + delete [SELECT Id FROM List_View_Config__c WHERE CreatedDate > 2021-10-12T12:00:00Z]; + */ + public static List_View_Config__c getListViewConfigCache(String objectName, String listViewName) + { + System.debug(LoggingLEvel.FINE, 'Starting getListViewConfig(' + objectName + ', ' + listViewName + ')'); + + SObjectAccessDecision dec = + Security.stripInaccessible(AccessType.READABLE, + [SELECT Name, + List_View_Object__c, + List_View_Label__c, + LastModifiedDate, + LastModifiedBy.Name, + Primary_Key__c, + (SELECT Parameter_Name__c, + Parameter_Type__c, + Parameter_Label__c, + Parameter_Value__c + FROM List_View_Config_Parameters__r + ORDER BY Parameter_Label__c), + (SELECT Field_Name__c, + Highlight_Color__c, + Operator__c, + Order__c, + Value__c + FROM List_View_Config_Conditions__r + ORDER BY Order__c ASC), + (SELECT Field_Name__c, + Style_Decoration__c, + Style_Font__c, + Style_Style__c, + Style_Transform__c, + Style_Variant__c, + Style_Weight__c, + Style_Alignment__c + FROM List_View_Config_Column_Styles__r + ORDER BY Field_Name__c) + FROM List_View_Config__c + WHERE Name = :listViewName + AND List_View_Object__c = :objectName]); + + List configs = (List) dec.getRecords(); + + if (configs.size() > 1) + throw new ListViewException('Found more than one list view config with name - ' + listViewName + ' and object - ' + objectName); + else if (configs.size() == 0) { + if (objectName == 'All' && listViewName == 'All') + throw new ListViewException('Global config not found. Go to the admin page and either import a config file or create a new config'); + else + return null; + //throw new ListViewException('No list view config was found with name - ' + listViewName + ' and object - ' + objectName); + } + System.debug(LoggingLevel.FINE, 'Returning configs - ' + configs); + return configs[0]; + } + + public static String getPrimaryKey(String objectName, String listViewName) + { + return objectName.replace(' ', '_') + ':' + listViewName; + } + + public static Map getListViewConfigs(Set primaryKeys) + { + List configs = [SELECT Name, + List_View_Object__c, + List_View_Label__c, + LastModifiedDate, + LastModifiedBy.Name, + Primary_Key__c, + (SELECT Parameter_Name__c, + Parameter_Type__c, + Parameter_Label__c, + Parameter_Value__c + FROM List_View_Config_Parameters__r + ORDER BY Parameter_Label__c), + (SELECT Field_Name__c, + Highlight_Color__c, + Operator__c, + Order__c, + Value__c + FROM List_View_Config_Conditions__r + ORDER BY Order__c ASC), + (SELECT Field_Name__c, + Style_Decoration__c, + Style_Font__c, + Style_Style__c, + Style_Transform__c, + Style_Variant__c, + Style_Weight__c, + Style_Alignment__c + FROM List_View_Config_Column_Styles__r + ORDER BY Field_Name__c) + FROM List_View_Config__c + WHERE Primary_Key__c IN :primaryKeys]; + + Map mappedConfigs = new Map(); + for (List_View_Config__c config: configs) + { + mappedConfigs.put(config.Primary_Key__c, config); + } + + return mappedConfigs; + + } + + public static Map getListViewConfigsById(Set ids) + { + return new Map( + [SELECT Name, + List_View_Object__c, + List_View_Label__c, + LastModifiedDate, + LastModifiedBy.Name, + Primary_Key__c, + (SELECT Parameter_Name__c, + Parameter_Type__c, + Parameter_Label__c, + Parameter_Value__c + FROM List_View_Config_Parameters__r + ORDER BY Parameter_Label__c), + (SELECT Field_Name__c, + Highlight_Color__c, + Operator__c, + Order__c, + Value__c + FROM List_View_Config_Conditions__r + ORDER BY Order__c ASC), + (SELECT Field_Name__c, + Style_Decoration__c, + Style_Font__c, + Style_Style__c, + Style_Transform__c, + Style_Variant__c, + Style_Weight__c, + Style_Alignment__c + FROM List_View_Config_Column_Styles__r + ORDER BY Field_Name__c) + FROM List_View_Config__c + WHERE Id IN :ids]); + } + + /** + * @description Method to validate parameter updates + * @author tom@ansleyllc.com | 08-17-2020 + * @param paramName the name of the parameter being updated + * @param paramValue the value the parameter is being updated to + * @return String the error message if the parameter is invalid. + **/ + public static String validateParameter(String objectName, String listViewName, String paramName, String paramValue) + { + String error = ''; + + if (paramName == ListViewHelper.PARAM_REFRESH_RATE) + { + + try { + Integer rate = Integer.valueOf(paramValue); + + if (rate < 10 || rate > 500) + { + error = 'Refresh rate must be between 10 and 500'; + } + } catch (Exception e) { + error = 'Refresh rate must be an integer value'; + } + + } else if (paramName == ListViewHelper.PARAM_RETURN_SIZE) + { + try { + Integer size = Integer.valueOf(paramValue); + + Integer maxRowsDisplayed = Integer.valueOf(ListViewConfigHelper.getOrgWideConfigParam('MaxRowsDisplayed')); + + if (size > maxRowsDisplayed) + { + error = 'Return size must be no greater than ' + maxRowsDisplayed; + } + } catch (Exception e) { + error = 'Return size must be an integer value'; + } + + + } else if (paramName == ListViewHelper.PARAM_TOTAL_COLUMNS) + { + } else if (paramName == ListViewHelper.PARAM_ADD_FIELDS) + { + if (paramValue != '') + { + + try { + + //get the list view + List_View__c listview = ListViewHelper.getListViews(objectName, listViewName).values()[0]; + + //get the list views query + String query = listview.Core_ListView_Query__c; + + //get the fields on the SELECT + String selectStr = query.substringBetween('SELECT ', ' FROM '); + + selectStr += ', ' + paramValue; + + String soql = 'SELECT ' + selectStr + ' FROM ' + objectName + ' LIMIT 1'; + + System.debug(LoggingLevel.FINE, 'Param Validation SOQL - ' + soql); + + List result = Database.query(soql); + + } catch (Exception e) { + error = 'The additional fields are invalid. Please ensure syntax is correct, there are no duplicate fields, and the names are valid API field names - ' + e.getMessage(); + } + } + + } else if (paramName == ListViewHelper.PARAM_SPLIT_COLUMNS) + { + paramValue = paramValue.remove(' '); + List fieldNames = paramValue.split(','); + + if (fieldNames.size() > 4) { + error = 'Only 4 columns can be added.'; + } else { + + try { + + String soql = 'SELECT ' + paramValue + ' FROM ' + objectName + ' LIMIT 1'; + + System.debug(LoggingLevel.FINE, 'Param Validation SOQL - ' + soql); + + List result = Database.query(soql); + + } catch (Exception e) { + error = 'The split column fields are invalid. Please ensure syntax is correct and the names are valid API field names - ' + e.getMessage(); + } + } + } + + return error; + } + + /** + * @description Method to validate a condition given an object being validated against and + * a condition. The return of an HTML color indicates the condition yielded a result. If no + * color was returned the value did meet any criteria. + * + * NOTE - DATE and DATETIME come through as millisecond strings and therefore need to be converted. + * + * @author tom@ansleyllc.com | 08-05-2020 + * @param value the value being validated against + * @param type the type of the value being validated against + * @param conditions the conditions the value is being validated against + * @return String either an HTML color (condition match) or empty string (condition not met) + **/ + public static String validateFieldCondition(Object value, String type, List_View_Config_Condition__c condition) + { + try { + System.debug(LoggingLevel.FINE, 'Starting validateFieldCondition(' + value + ',' + type + ')'); + System.debug(LoggingLevel.FINE, 'Conditions - ' + condition); + + if (value == null || condition == null) return ''; + + System.debug(LoggingLevel.FINE, 'Value = ' + value); + System.debug(LoggingLevel.FINE, 'Type = ' + type); + System.debug(LoggingLevel.FINE, 'Operator = ' + condition.Operator__c); + System.debug(LoggingLevel.FINE, 'condValue = ' + condition.Value__c); + + //if we have a meaningless comparison then skip + if ((value == null || value == '') && type != ListViewHelper.TYPE_STRING) return ''; + + //EQUAL + if (condition.Operator__c == OPER_EQUAL) { + if (type == ListViewHelper.TYPE_DOUBLE) { + if (value == Double.valueOf(condition.Value__c)) return condition.Highlight_Color__c; + }else if (type == ListViewHelper.TYPE_DATE) { + + Date dte = getDateValue(condition.Value__c); + if (HelperDate.convertMillisToDate((String) value) == dte) return condition.Highlight_Color__c; + + } else if (type == ListViewHelper.TYPE_DATETIME) { + + DateTime dte = getDateTimeValue(condition.Value__c); + if (HelperDate.convertMillisToDateTime((String) value) == dte) return condition.Highlight_Color__c; + + } else if (type == ListViewHelper.TYPE_TIME) { + if (HelperDate.getTimeFromString((String) value) == HelperDate.getTimeFromString(condition.Value__c)) return condition.Highlight_Color__c; + } else if (String.valueOf(value) == condition.Value__c) return condition.Highlight_Color__c; + + //NOT EQUAL + } else if (condition.Operator__c == OPER_NOT_EQUAL) { + if (type == ListViewHelper.TYPE_DOUBLE) { + if (value != Double.valueOf(condition.Value__c)) return condition.Highlight_Color__c; + } else if (type == ListViewHelper.TYPE_DATE) { + + Date dte = getDateValue(condition.Value__c); + if (HelperDate.convertMillisToDate((String) value) != dte) return condition.Highlight_Color__c; + + } else if (type == ListViewHelper.TYPE_DATETIME) { + + DateTime dte = getDateTimeValue(condition.Value__c); + if (HelperDate.convertMillisToDateTime((String) value) != dte) return condition.Highlight_Color__c; + + } else if (type == ListViewHelper.TYPE_TIME) { + if (HelperDate.getTimeFromString((String) value) != HelperDate.getTimeFromString(condition.Value__c)) return condition.Highlight_Color__c; + } else if (String.valueOf(value) != condition.Value__c) return condition.Highlight_Color__c; + + //GREATER THAN + } else if (condition.Operator__c == OPER_GREATER) { + if ((type == ListViewHelper.TYPE_STRING + || type == ListViewHelper.TYPE_EMAIL + || type == ListViewHelper.TYPE_MULTI_PICK + || type == ListViewHelper.TYPE_PHONE + || type == ListViewHelper.TYPE_PICKLIST + || type == ListViewHelper.TYPE_RICH_TEXTAREA + || type == ListViewHelper.TYPE_TEXTAREA + || type == ListViewHelper.TYPE_URL) + && String.valueOf(value) > condition.Value__c) return condition.Highlight_Color__c; + else if ((type == ListViewHelper.TYPE_DECIMAL + || type == ListViewHelper.TYPE_CURRENCY + || type == ListViewHelper.TYPE_NUMBER + || type == ListViewHelper.TYPE_PERCENT) + && Decimal.valueOf(String.valueOf(value)) > Decimal.valueOf(condition.Value__c)) return condition.Highlight_Color__c; + else if (type == ListViewHelper.TYPE_DATE) { + + Date dte = getDateValue(condition.Value__c); + if (HelperDate.convertMillisToDate((String) value) > dte) return condition.Highlight_Color__c; + + } else if (type == ListViewHelper.TYPE_DATETIME) { + + DateTime dte = getDateTimeValue(condition.Value__c); + if (HelperDate.convertMillisToDateTime((String) value) > dte) return condition.Highlight_Color__c; + + } else if (type == ListViewHelper.TYPE_DOUBLE && Double.valueOf(value) > Double.valueOf(condition.Value__c)) return condition.Highlight_Color__c; + else if (type == ListViewHelper.TYPE_INTEGER && Integer.valueOf(value) > Integer.valueOf(condition.Value__c)) return condition.Highlight_Color__c; + else if (type == ListViewHelper.TYPE_TIME && HelperDate.getTimeFromString((String) value) > HelperDate.getTimeFromString(condition.Value__c)) return condition.Highlight_Color__c; + + //LESS THAN + } else if (condition.Operator__c == OPER_LESS) { + if ((type == ListViewHelper.TYPE_STRING + || type == ListViewHelper.TYPE_EMAIL + || type == ListViewHelper.TYPE_MULTI_PICK + || type == ListViewHelper.TYPE_PHONE + || type == ListViewHelper.TYPE_PICKLIST + || type == ListViewHelper.TYPE_RICH_TEXTAREA + || type == ListViewHelper.TYPE_TEXTAREA + || type == ListViewHelper.TYPE_URL) + && String.valueOf(value) < condition.Value__c) return condition.Highlight_Color__c; + else if ((type == ListViewHelper.TYPE_DECIMAL + || type == ListViewHelper.TYPE_CURRENCY + || type == ListViewHelper.TYPE_NUMBER + || type == ListViewHelper.TYPE_PERCENT) + && Decimal.valueOf(String.valueOf(value)) < Decimal.valueOf(condition.Value__c)) return condition.Highlight_Color__c; + else if (type == ListViewHelper.TYPE_DATE) { + + Date dte = getDateValue(condition.Value__c); + if (HelperDate.convertMillisToDate((String) value) < dte) return condition.Highlight_Color__c; + + } else if (type == ListViewHelper.TYPE_DATETIME) { + + DateTime dte = getDateTimeValue(condition.Value__c); + if (HelperDate.convertMillisToDateTime((String) value) < dte) return condition.Highlight_Color__c; + + } else if (type == ListViewHelper.TYPE_DOUBLE && Double.valueOf(value) < Double.valueOf(condition.Value__c)) return condition.Highlight_Color__c; + else if (type == ListViewHelper.TYPE_INTEGER && Integer.valueOf(value) < Integer.valueOf(condition.Value__c)) return condition.Highlight_Color__c; + else if (type == ListViewHelper.TYPE_TIME && HelperDate.getTimeFromString((String) value) < HelperDate.getTimeFromString(condition.Value__c)) return condition.Highlight_Color__c; + + //CONTAINS + } else if (condition.Operator__c == OPER_CONTAINS) { + if (type == ListViewHelper.TYPE_DATETIME + || type == ListViewHelper.TYPE_DATE + || type == ListViewHelper.TYPE_TIME + || type == ListViewHelper.TYPE_PERCENT + || type == ListViewHelper.TYPE_CURRENCY) throw new ListViewException('Cannot use "Contains" operator on DateTime, Date, Time, Percent or Currency types'); + if (String.valueOf(value).contains(condition.Value__c)) return condition.Highlight_Color__c; + } + } catch (Exception e) { + String message = 'Exception during ListViewController.validateFieldCondition(' + value + ',' + type + ',' + condition + ') ' + ListViewException.getExtendedString(e); + if (!HelperLimits.hasReachedLimit('DMLStatements')) + ListViewErrorHelper.createFutureUsageError(message); + } + return ''; + } + + + + + + + + + //================================================================================================================ + // GLOBAL METHODS + //================================================================================================================ + + global static void updateOrgWideParam(String settingName, String value) + { + setOrgWideConfigParam(settingName, value); + } + + global static void updateOrgWideParams(Map newParams) + { + setOrgWideParams(newParams); + } + + global static String getOrgWideParam(String settingName) + { + return ListViewConfigHelper.getOrgWideConfigParam(settingName); + } + + global static Map getOrgWideParams() + { + Map params = new Map(); + + Map orgWideParams = ListViewConfigHelper.getOrgWideConfigParams(); + + for (String key: orgWideParams.keySet()) + params.put(key, String.valueOf(orgWideParams.get(key))); + + return params; + } + + + + + + + //================================================================================================================ + // PRIVATE METHODS + //================================================================================================================ + + private static Date getDateValue(String condValue) + { + Date dte = null; + if (condValue == TODAY) + dte = System.today(); + else if (condValue == TOMORROW) + dte = System.today().addDays(1); + else if (condValue == YESTERDAY) + dte = System.today().addDays(-1); + else + dte = Date.valueOf(condValue); + + return dte; + } + + private static DateTime getDateTimeValue(String condValue) + { + DateTime dte = null; + if (condValue == TODAY) + dte = HelperDate.convertDateToDateTime(System.today()); + else if (condValue == TOMORROW) + dte = HelperDate.convertDateToDateTime(System.today().addDays(1)); + else if (condValue == YESTERDAY) + dte = HelperDate.convertDateToDateTime(System.today().addDays(-1)); + else + dte = DateTime.valueOf(condValue); + + return dte; + } + } \ No newline at end of file diff --git a/force-app/main/default/classes/ListViewConfigHelperTest.cls b/force-app/main/default/classes/ListViewConfigHelperTest.cls index 91d130f..d984f63 100644 --- a/force-app/main/default/classes/ListViewConfigHelperTest.cls +++ b/force-app/main/default/classes/ListViewConfigHelperTest.cls @@ -2,7 +2,7 @@ * @description : * @author : tom@ansleyllc.com * @group : - * @last modified on : 07-25-2024 + * @last modified on : 08-14-2024 * @last modified by : tom@ansleyllc.com * Modifications Log * Ver Date Author Modification @@ -831,15 +831,15 @@ private class ListViewConfigHelperTest { System.assert(result != ''); - result = ListViewConfigHelper.updateListViewParam('Account', 'simpli_lv__AllAccounts', 'RefreshRate', '45', 'Refresh Rate', ListViewHelper.TYPE_NUMBER); + result = ListViewConfigHelper.updateListViewParam('Account', 'simpli_lv__AllAccounts', ListViewHelper.PARAM_REFRESH_RATE, '45', 'Refresh Rate', ListViewHelper.TYPE_NUMBER); System.assert(result == ''); - result = ListViewConfigHelper.updateListViewParam('Account', 'simpli_lv__AllAccounts', 'ReturnSize', '123', 'Return Size', ListViewHelper.TYPE_NUMBER); + result = ListViewConfigHelper.updateListViewParam('Account', 'simpli_lv__AllAccounts', ListViewHelper.PARAM_RETURN_SIZE, '123', 'Return Size', ListViewHelper.TYPE_NUMBER); System.assert(result == ''); - result = ListViewConfigHelper.updateListViewParam('Account', 'simpli_lv__AllAccounts', 'AllRows', 'true', 'All Rows', ListViewHelper.TYPE_BOOLEAN); + result = ListViewConfigHelper.updateListViewParam('Account', 'simpli_lv__AllAccounts', ListViewHelper.PARAM_ALL_ROWS, 'true', 'All Rows', ListViewHelper.TYPE_BOOLEAN); System.assert(result == ''); diff --git a/force-app/main/default/classes/ListViewController.cls b/force-app/main/default/classes/ListViewController.cls index 312ce7a..18571a0 100644 --- a/force-app/main/default/classes/ListViewController.cls +++ b/force-app/main/default/classes/ListViewController.cls @@ -1,995 +1,995 @@ -/** - * @description : - * @author : tom@ansleyllc.com - * @group : - * @last modified on : 07-24-2024 - * @last modified by : tom@ansleyllc.com - * Modifications Log - * Ver Date Author Modification - * 1.0 08-04-2020 tom@ansleyllc.com Initial Version - * 2.0 06-18-2021 tom@ansleyllc.com Added offset to allow for larger datasets - * 3.0 06-18-2021 tom@ansleyllc.com Added logic when list views are not initialized - * 4.0 07-30-2021 tom@ansleyllc.com Added updateRecords() method - * 5.0 08-16-2021 tom@ansleyllc.com Updated getListViewsActions() and added permission check for each action before display - * 6.0 08-20-2021 tom@ansleyllc.com renamed getListViewsActions() to getListViewActions() - * 7.0 08-25-2021 tom@ansleyllc.com renamed getListViewColumnLabels() to getListViewColumns() - * 8.0 12-15-2021 tom@ansleyllc.com Pulled out creating types due to different methods for handling types with package names etc. - **/ -public without sharing class ListViewController -{ - - public static final String SUCCESS = 'success'; - public static final String FAILED = 'failed'; - - @AuraEnabled - public static Boolean hasEnterprise() - { - return ListViewHelper.hasEnterprise(); - } - - @AuraEnabled - public static Boolean hasModifyAll() - { - ListViewErrorHelper.processLogs(true); - - return HelperProfile.hasModifyAll(); - } - - /* - * Method to retrieve the progress of the initialization batch process - */ - @AuraEnabled - public static String getListViewInitProgress(String batchId) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewInitProgress(' + batchId + ')'); - String result = '0'; - String status = ''; - - if (batchId != '') - { - //Query the Batch apex jobs - List jobs = HelperScheduler.getCurrentlyRunningJobs(); - - if (jobs.size() == 0) { - - result = '0'; - status = 'Waiting'; - - if(getIsInitialized()) - { - status = 'Completed'; - result = '100'; - } - - } else if (jobs[0].TotalJobItems == 0) { - - result = '0'; - status = jobs[0].Status + ' ' + jobs[0].ApexClass.Name; - - } else { - - Decimal percentComp = (Decimal.valueOf(jobs[0].JobItemsProcessed)/Decimal.valueOf(jobs[0].TotalJobItems)) * 100; - if (percentComp == 100) - percentComp = 99; //make it 99 if done as 100 is considered completely finished! - result = String.valueOf(percentComp); - status = jobs[0].Status + ' ' + jobs[0].ApexClass.Name; - - } - } else { - result = '-1'; - } - - System.debug(LoggingLevel.DEBUG, 'Finishing ListViewController.getListViewInitProgress with result - ' + result + ':' + status); - - ListViewErrorHelper.processLogs(true); - - return String.valueOf(result) + ':' + status; - } - - /* - * Method which returns list of objects names and API names in the system - */ - @AuraEnabled(cacheable=true) - public static List getListViewObjects(String includedObjects, String excludedObjects) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewObjects(' + includedObjects + ', ' + excludedObjects + ')'); - List configListOptions = new List(); - - Set excObjs = HelperString.getSetFromString(excludedObjects, ','); - Set incObjs = HelperString.getSetFromString(includedObjects, ','); - - Map listviewObjects = ListViewHelper.getListViewObjects(incObjs, excObjs); - - String debug = '\n\n================ FINAL OBJECT LIST ================\n'; - for (String apiName: listviewObjects.keySet()) - { - debug += apiName + ' - ' + listviewObjects.get(apiName) + '\n'; - configListOptions.add(new SelectOption(apiName, listviewObjects.get(apiName))); - } - debug += '===================================================\n'; - System.debug(LoggingLevel.DEBUG, debug); - - configListOptions.sort(); - - ListViewErrorHelper.processLogs(true); - - return configListOptions; - } - - @AuraEnabled - public static Boolean getIsInitialized() - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getIsInitialized()'); - - Boolean isInitialized = false; - try { - isInitialized = Boolean.valueOf(ListViewConfigHelper.getOrgWideConfigParam('IsInitialized')); - } catch(Exception e) { - //no global config! - } - ListViewErrorHelper.processLogs(true); - - return isInitialized; - } - - @AuraEnabled - public static List getObjectListViews(String objectName) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getObjectListViews(' + objectName + ')'); - - Map listviews = ListViewHelper.getListViewsByObject(objectName); - - List configListOptions = new List(); - - for (List_View__c listview : listviews.values()) - { - configListOptions.add(new SelectOption(listview.API_Name__c, listview.Label__c)); - } - - configListOptions.sort(); - - ListViewErrorHelper.processLogs(true); - - return configListOptions; - } - - @AuraEnabled - public static ListViewAbstract.ListViewConfigWrapper getListViewConfig(String objectName, String listViewName, String listViewMode) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewConfig(' + objectName + ', ' + listViewName + ', ' + listViewMode + ')'); - - ListViewAbstract.ListViewConfigWrapper lvConfigWrapper = new ListViewAbstract.ListViewConfigWrapper(objectName, listViewName, listViewMode); - - if (objectName == null || listViewName == null) - return lvConfigWrapper; - - - Map listViews = ListViewHelper.getListViews(objectName, listViewName); - if (listViews.isEmpty()) - throw new ListViewException('The list view configuration can not be found for object ' + objectName + ' and list view name - ' + listViewName + ')'); - - List_View__c listview = listViews.values()[0]; - - String objType = null; - if (listView.RecordTypeId == null || listView.RecordTypeId == ListViewHelper.coreRTId) - objType = ListViewHelper.CORE_APEX_CLASS; - else - objType = listView.Custom_Apex_Class__c; - - //get an apex Type of the object type - Type t = HelperSchema.getClassType(objType); - - //create a new instance - ListViewAbstract lvLogic = (ListViewAbstract) t.newInstance(); - lvLogic.listviewMode = listViewMode; - - //set all request information into the apex processing class. - lvlogic.setListView(listview); - - lvConfigWrapper = lvlogic.lvConfig; - - HelperString.debug(lvConfigWrapper, 'ListViewController(getListViewConfig)'); - ListViewErrorHelper.addLog('ListViewController(getListViewConfig)', lvConfigWrapper.getDebugString()); - - ListViewErrorHelper.processLogs(true); - - return lvConfigWrapper; - } - - /** - * @description Method which gets the users SORT configuration for the provided COMPONENT. The method manually creates - the JSON request as it makes it easier on the front end to handle. Here is an example JSON response - - {"listviews": [{"name": "Account:Simpli_LV_Acct_1","fields": [{"sortIndex": "0", "fieldName": "Name", "sortDirection": "true"},{"sortIndex": "1", "fieldName": "BillingState", "sortDirection": "false"}]}, {"name": "Account:PlatinumandGoldSLACustomers","fields": [{"sortIndex": "0", "fieldName": "Name", "sortDirection": "true"},{"sortIndex": "1", "fieldName": "BillingState", "sortDirection": "false"},{"sortIndex": "2", "fieldName": "Id", "sortDirection": "false"}]}]} - - * @author tom@ansleyllc.com | 10-10-2020 - * @param compName the component name which is the name of the Lightning component that calls this method. - * @return Map - **/ - @AuraEnabled - public static String getUserSortConfigs(String compName) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getUserSortConfigs(' + compName + ')'); - - String sortConfigJSON = '{"listviews": ['; - //String sortJSON = '{'; - //get the users component config - List configs = ListViewUserConfigHelper.getCurrentUserCompSortConfigs(compName); - for (List_View_User_Config__c config: configs) - { - sortConfigJSON += '{"name": "' + config.Name__c.removeStart('sortOrder:') + '","fields": ['; - - List sortFields = config.Value__c.split(';'); - - for (String sortField: sortFields) - { - List sortParts = sortField.split(':'); - sortConfigJSON += '{"sortIndex": "' + sortParts[0] + '", "fieldName": "' + sortParts[2] + '", "sortDirection": "' + sortParts[1] + '"},'; - } - - sortConfigJSON = sortConfigJSON.removeEnd(',') + ']},'; - - } - sortConfigJSON = sortConfigJSON.removeEnd(',') + ']}'; - - ListViewErrorHelper.processLogs(true); - - return sortConfigJSON; - } - - @AuraEnabled - public static String getListViewConfigParameter(String objectName, String listViewName, String paramName) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewConfigParameter(' + objectName + ', ' + listViewName + ',' + paramName + ')'); - - String paramValue = ListViewConfigHelper.getListViewConfigParameter(objectName, listViewName, paramName); - - ListViewErrorHelper.processLogs(true); - - return paramValue; - } - - /** - * @description Method which gets the users configuration for the provided COMPONENT. - * Note that this method also gets the org wide defaults as it needs to layer those defaults on top of the user config - * @author tom@ansleyllc.com | 10-10-2020 - * @param compName the component name which is the name of the Lightning component that calls this method. - * @return Map - **/ - @AuraEnabled - public static Map getComponentConfig(String compName) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getComponentConfig(' + compName + ')'); - Map userConfigs = ListViewUserConfigHelper.getComponentConfig(compName); - - ListViewErrorHelper.processLogs(true); - - return userConfigs; - } - - @AuraEnabled - public static String updateUserConfig(String compName, String configName, String value) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.updateUserConfig(' + compName + ', ' + configName + ',' + value + ')'); - try { - ListViewUserConfigHelper.updateCurrentUserConfigValue(compName, configName, value); - } catch (Exception e) { - String message = 'Exception during ListViewController.updateUserConfig(' + compName + ',' + configName + ',' + value + ') ' + ListViewException.getExtendedString(e); - ListViewErrorHelper.createFutureUsageError(message); - return 'There was an error during user configuration update'; - } - - ListViewErrorHelper.processLogs(true); - - return SUCCESS; - } - - /* - * Method to update a specific list view for a user with given column width data. - */ - @AuraEnabled - public static String updateUserConfigListViewWidth(String compName, String configName, String columnIndex, String width) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.updateUserConfigListViewWidth(' + compName + ', ' + configName + ',' + columnIndex + ',' + width + ')'); - try { - - //get existing config if it exists. - List_View_User_Config__c widthConfig = ListViewUserConfigHelper.getCurrentUserConfigByName(compName, configName); - - String widthStr = ''; - - //if config exists then UPDATE - if (widthConfig != null) - { - //list width config example - 1:25;2:25;3:120;4:80 - widthStr = widthConfig.Value__c; - - //break up string into columns - List columnWidthsStr = widthStr.split(';'); - Map widthsByIndex = new Map(); - Boolean hasValue = false; - for (String columnWidthStr: columnWidthsStr) - { - //update individual column width data if it exists - List values = columnWidthStr.split(':'); - if (values[0] == columnIndex) //value[0] = column index - { - hasValue = true; - widthsByIndex.put(Integer.valueOf(values[0]), width); - } else { - widthsByIndex.put(Integer.valueOf(values[0]), values[1]); - } - } - - //add column and width if the column index doesn't already exist - if (!hasValue) - widthsByIndex.put(Integer.valueOf(columnIndex), width); - - //go through map and sort - List columnIndexes = new List(widthsByIndex.keySet()); - columnIndexes.sort(); - - //go through map and recreate string - widthStr = ''; - for (Integer tmpColumnIndex: columnIndexes) - { - String tmpWidth = widthsByIndex.get(tmpColumnIndex); - - widthStr += tmpColumnIndex + ':' + tmpWidth + ';'; - } - - //else INSERT new data - } else { - widthStr = columnIndex + ':' + width; - } - - ListViewUserConfigHelper.updateCurrentUserConfigValue(compName, configName, widthStr); - - } catch (Exception e) { - String message = 'Exception during ListViewController.updateUserConfigListViewWidth(' + compName + ',' + configName + ',' + columnIndex + ',' + width + ') ' + ListViewException.getExtendedString(e); - ListViewErrorHelper.createFutureUsageError(message); - return 'There was an error during user configuration update'; - } - - ListViewErrorHelper.processLogs(true); - - return SUCCESS; - } - - @AuraEnabled - public static String updateObjectListViews(String objectType) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.updateObjectListViews(' + objectType + ')'); - - //update the list view - String response = ListViewHelper.updateObjectListViews(objectType); - - ListViewErrorHelper.processLogs(true); - - return response; - } - - @AuraEnabled - public static String updateSingleListView(String objectType, String listViewName) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.updateSingleListView(' + objectType + ', ' + listViewName + ')'); - - String response = ListViewHelper.updateSingleListView(objectType, listViewName); - - ListViewErrorHelper.processLogs(true); - - return response; - } - - @AuraEnabled - public static String updateChangedListViews() - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.updateChangedListViews()'); - - String response = ListViewHelper.updateChangedListViews(); - - ListViewErrorHelper.processLogs(true); - - return response; - } - - @AuraEnabled - public static String updateAllListViews() - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.updateAllListViews()'); - - String response = ListViewHelper.updateAllListViews(); - - ListViewErrorHelper.processLogs(true); - - return response; - } - - @AuraEnabled(cacheable=true) - public static List getListViewActions(String objectType, String listViewName, String componentName) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewActions(' + objectType + ', ' + listViewName + ', ' + componentName + ')'); - - List actions = ListViewActionHelper.getListViewActions(objectType, listViewName, componentName); - - ListViewErrorHelper.processLogs(true); - - return actions; - } - - @AuraEnabled - public static ListViewActionWrapper getListViewActionAndData(String actionName, String dataIds) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewAction(' + actionName + ', ' + dataIds + ')'); - - ListViewActionWrapper wrapper = getListViewAction(actionName); - - try { - - //if we are provided record Ids then get the record and try to set the initial values. - if (dataIds != null){ - List listIds = (List) JSON.deserializeUntyped(dataIds); - if (listIds.size() == 1) - { - //1. get the object API Name - String objAPIName = wrapper.getObjectType(); - - //2. get the single record Id - String recordId = (String) listIds[0]; - recordId = String.escapeSingleQuotes(recordId); - - - //3. Get the list of fields - List fieldAPINames = new List(); - for (ListViewActionWrapper.ActionParameterWrapper param: wrapper.getDisplayParameters()) - { - fieldAPINames.add(param.getAPIName()); - } - - //4. Get record - SObject record = HelperDatabase.query(objAPIName, fieldAPINames, recordId); - - //5. Set the record values into the action wrapper - if (record != null) - { - for (ListViewActionWrapper.ActionParameterWrapper param: wrapper.getDisplayParameters()) - { - param.setDefaultValue(record.get(param.getAPIName())); - } - } - } - } - } catch (Exception e) { - String message = 'Exception during ListViewController.getListViewAction(' + actionName + ') ' + ListViewException.getExtendedString(e); - ListViewErrorHelper.createFutureUsageError(message); - } - - ListViewErrorHelper.processLogs(true); - - return wrapper; - } - - @AuraEnabled - public static ListViewActionWrapper getListViewAction(String actionName) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewAction(' + actionName + ')'); - - ListViewActionWrapper wrapper = null; - if (!String.isEmpty(actionName)) - { - - List_View_Action__c action = null; - - try { - action = ListViewActionHelper.getListViewActionByKey(actionName); - wrapper = new ListViewActionWrapper(action); - - } catch (Exception e) { - String message = 'Exception during ListViewController.getListViewAction(' + actionName + ') ' + ListViewException.getExtendedString(e); - ListViewErrorHelper.createFutureUsageError(message); - } - } - - ListViewErrorHelper.processLogs(true); - - return wrapper; - } - - /* - * Method which determines whether a list view data request is valid or not. - */ - @AuraEnabled - public static String isValidListViewDataRequest(String objectName, String joinFieldName, String joinData) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.isValidListViewDataRequest(' + objectName + ', ' + joinFieldName + ',' + joinData + ')'); - String result = SUCCESS; - - //if we have join information then get the record Ids - Set joinRecordIds = null; - if (joinFieldName != '' && joinData != '') - { - //check that we can actually act on the join data as it could be for another component. - if (HelperSchema.isValidSFDCFieldName(objectName, joinFieldName)) - { - Map joinDataMap = (Map) JSON.deserializeUntyped(joinData); - joinRecordIds = HelperString.getSetFromString(((String) joinDataMap.get('recordIds')), ','); - joinRecordIds.remove(null); //make sure we remove any nulls - joinRecordIds.remove(''); - - //if we have any Ids we need to check that the Ids match the field object type - //unless its a polymorphic field, then we just have to go with it!! - if (joinRecordIds.size() > 0 - && !ListViewHelper.POLYMORPHIC_FIELDS.contains(joinFieldName) - && !joinFieldName.contains('.')) - { - //get the type of the join field lookup object. - String objType = HelperSchema.getObjectTypeForField(objectName, joinFieldName); - - String objType2 = ''; - //this is crazy. In order to get any one value from a set - //I have to iterate over it and break at the first value!! - for (String recordId: joinRecordIds) - { - objType2 = HelperSchema.getObjectTypeFromId(recordId); - break; - } - - if (objType != objType2) - result = 'failure'; - } - } - } - - System.debug(LoggingLevel.DEBUG, 'Leaving isValidListViewDataRequest - ' + result); - ListViewErrorHelper.processLogs(true); - - return result; - } - - /* - * Method which determines whether a list view data request is valid or not. - */ - @AuraEnabled - public static String getListViewId(String objectName, String listViewName) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewId(' + objectName + ', ' + listViewName + ')'); - String id = ''; - - Map listView = ListViewHelper.getListViews(objectName, listViewName); - - if (!listView.isEmpty()) - id = listView.values()[0].Id; - - System.debug(LoggingLevel.DEBUG, 'Leaving getListViewId - ' + id); - ListViewErrorHelper.processLogs(true); - - return id; - } - - @AuraEnabled - public static ListViewAbstract.RowsWrapper getListViewDataShell(String compType, String objectName, String listViewName, String joinFieldName, String joinData) - { - ListViewAbstract.RowsWrapper rows = getListViewData(null, compType, objectName, listViewName, null, joinFieldName, joinData, -1, true, ''); - - //clone the only returned row and change its rowId - ListViewAbstract.RowWrapper rowWrapper = rows.getRows()[0]; - rowWrapper.rowId = '1'; - rowWrapper.sfdcId = '1'; - - String debug = '\n\n------------------------------------\n'; - for(ListViewAbstract.FieldWrapper field: rowWrapper.getFields()) - { - field.setFieldObj(null); - field.setObjValueId(null); - field.setValue(null); - debug += field.getName() + ' - ' + field.getIsEditable() + ' - ' + field.getType() + '\n'; - } - debug += '------------------------------------\n'; - System.debug(LoggingLevel.DEBUG, debug); - - Integer rowCount = Integer.valueOf(ListViewConfigHelper.getOrgWideConfigParam('MassCreateRowCount', '10')); - - - for (Integer count = 2; count < rowCount + 1; count++) - { - ListViewAbstract.RowWrapper row = rowWrapper.cloneWrapper(false); - row.rowId = String.valueOf(count); - rows.addRow(row); - } - return rows; - } - - public static ListViewAbstract.RowsWrapper getListViewData(String compType, String objectName, String listViewName) - { - return getListViewData('', compType, objectName, listViewName, '', '', '', -1, false, ''); - } - - @AuraEnabled - public static ListViewAbstract.RowsWrapper getListViewData(String pageName, String compType, String objectName, String listViewName, String sortData, String joinFieldName, String joinData, Integer offset, String textSearchStr) - { - return getListViewData(pageName, compType, objectName, listViewName, sortData, joinFieldName, joinData, offset, false, textSearchStr); - } - - /** - * @description Method to retrieve list view data based on user provided criteria. - * @author tom@ansleyllc.com | 06-28-2021 - * @param pageName the name of the lightning page that the list is being displayed on. This is used for list view config retrieval - * @param compType the mode of the component the data is being retrieved for. - * @param objectName the name of the list view object - * @param listViewName the name of the list view - * @param sortData the sorting information for the list view. - * @param joinFieldName used by a list view component listening on the same page. Indicates the field name used to join the data between the two list views. - * @param joinData the joined field data to use when performing the query. - * @param offset indicates the offset if the data is being paged. - * @param isShell indicates whether the request should only retrieve the rowwrapper without data or all the data - * @return ListViewAbstract.RowsWrapper - **/ - public static ListViewAbstract.RowsWrapper getListViewData(String pageName, String compType, String objectName, String listViewName, String sortData, String joinFieldName, String joinData, Integer offset, Boolean isShell, String textSearchStr) - { - System.debug(LoggingLevel.ERROR, 'Starting ListViewController.getListViewData(' + pageName + ', ' + objectName + ', ' + listViewName + ',' + sortData + ',' + joinFieldName + ',' + joinData + ', ' + isShell + ', ' + textSearchStr + ')'); - return ListViewHelper.getListViewData(pageName, compType, objectName, listViewName, sortData, joinFieldName, joinData, offset, isShell, textSearchStr); - } - - @AuraEnabled - public static String processParamChange(String objectName, String listViewName, String paramName, String paramValue, String paramLabel, String paramType) - { - objectName = String.escapeSingleQuotes(objectName); - listViewName = String.escapeSingleQuotes(listViewName); - paramName = String.escapeSingleQuotes(paramName); - paramValue = String.escapeSingleQuotes(paramValue); - paramLabel = String.escapeSingleQuotes(paramLabel); - paramType = String.escapeSingleQuotes(paramType); - - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.processParamChange(' + objectName + ', ' + listViewName + ', ' + paramName + ', ' + paramValue + ', ' + paramLabel + ', ' + paramType + ')'); - - String rtnStr = 'Ok:'; - - try { - - String errorStr = ListViewConfigHelper.updateListViewParam(objectName, listViewName, paramName, paramValue, paramLabel, paramType); - - if (errorStr != '') - { - rtnStr = 'Failed:' + errorStr; - } - - } catch (Exception e) { - rtnStr = 'Failed:' + e.getMessage(); - String message = 'Exception during ListViewController.processParamChange(' + objectName + ', ' + listViewName + ', ' + paramName + ', ' + paramValue + ') ' + ListViewException.getExtendedString(e); - ListViewErrorHelper.createFutureUsageError(message); - } - - ListViewErrorHelper.processLogs(true); - - return rtnStr; - } - - /* - * action = remove or add - * conditionData = condition id (if remove) or condition data map (if add) - */ - @AuraEnabled - public static String processConditionChange(String objectName, String listViewName, String action, String conditionData) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.processConditionChange(' + objectName + ', ' + listViewName + ', ' + action + ', ' + conditionData + ')'); - - String rtnStr = 'Ok:'; - - if (action == 'add') - { - //get the field/value pairs of the update data - List listValues = (List) JSON.deserializeUntyped(conditionData); - Map mapValues = new Map(); - for (Object key: listValues) - { - List keyValues = (List) key; - mapValues.put( (String) keyValues[0], keyValues[1]); - } - - try { - ListViewConfigHelper.addListViewCondition(objectName, - listViewName, - (String) mapValues.get('field'), - (String) mapValues.get('operator'), - (String) mapValues.get('value'), - (String) mapValues.get('order'), - (String) mapValues.get('color')); - CacheListViewConfig.remove(objectName, listViewName); - } catch (Exception e) { - rtnStr = 'Failed:' + e.getMessage(); - String message = 'Exception during ListViewController.processConditionChange(' + objectName + ', ' + listViewName + ', ' + action + ', ' + conditionData + ') ' + ListViewException.getExtendedString(e); - ListViewErrorHelper.createFutureUsageError(message); - } - - } else if (action == 'remove') - { - try { - ListViewConfigHelper.deleteListViewCondition(conditionData); - CacheListViewConfig.remove(objectName, listViewName); - } catch (Exception e) { - rtnStr = 'Failed:' + e.getMessage(); - String message = 'Exception during ListViewController.processConditionChange(' + objectName + ', ' + listViewName + ', ' + action + ', ' + conditionData + ') ' + ListViewException.getExtendedString(e); - ListViewErrorHelper.createFutureUsageError(message); - } - } - - ListViewErrorHelper.processLogs(true); - - return rtnStr; - } - - /* - * action = remove or add - * conditionData = condition id (if remove) or condition data map (if add) - */ - @AuraEnabled - public static String processColumnStyleChange(String objectName, String listViewName, String action, String columnStyleData) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.processColumnStyleChange(' + objectName + ', ' + listViewName + ', ' + action + ', ' + columnStyleData + ')'); - - String rtnStr = 'Ok:'; - - if (action == 'add') - { - //get the field/value pairs of the update data - List listValues = (List) JSON.deserializeUntyped(columnStyleData); - Map mapValues = new Map(); - for (Object key: listValues) - { - List keyValues = (List) key; - mapValues.put( (String) keyValues[0], keyValues[1]); - } - - try { - ListViewConfigHelper.addListViewColumnStyle(objectName, - listViewName, - (String) mapValues.get('field'), - (String) mapValues.get('decoration'), - (String) mapValues.get('font'), - (String) mapValues.get('style'), - (String) mapValues.get('transform'), - (String) mapValues.get('variant'), - (String) mapValues.get('weight'), - (String) mapValues.get('alignment')); - CacheListViewConfig.remove(objectName, listViewName); - } catch (Exception e) { - rtnStr = 'Failed:' + e.getMessage(); - String message = 'Exception during ListViewController.processColumnStyleChange(' + objectName + ', ' + listViewName + ', ' + action + ', ' + columnStyleData + ') ' + ListViewException.getExtendedString(e); - ListViewErrorHelper.createFutureUsageError(message); - } - - } else if (action == 'remove') - { - try { - ListViewConfigHelper.deleteListViewColumnStyle(columnStyleData); - CacheListViewConfig.remove(objectName, listViewName); - } catch (Exception e) { - rtnStr = 'Failed:' + e.getMessage(); - String message = 'Exception during ListViewController.processColumnStyleChange(' + objectName + ', ' + listViewName + ', ' + action + ', ' + columnStyleData + ') ' + ListViewException.getExtendedString(e); - ListViewErrorHelper.createFutureUsageError(message); - } - } - - ListViewErrorHelper.processLogs(true); - - return rtnStr; - } - - @AuraEnabled - public static String processAction(String actionKey, String dataIds, String valuesMap) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.processAction(' + actionKey + ', ' + dataIds + ',' + valuesMap + ')'); - - String responseStr = ListViewActionHelper.processAction(actionKey, dataIds, valuesMap); - - return responseStr; - } - - @AuraEnabled(cacheable=true) - public static List getListViewColumns(String objectName, String listViewName) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewColumns(' + objectName + ', ' + listViewName + ')'); - - //get the core SFDC list view data - Map listViews = ListViewHelper.getListViews(objectName, listViewName); - if (listViews.size() == 0) return new List(); - List_View__c listView = listViews.values()[0]; - - //------------------------------------------------------------------- - String objType = null; - if (listView.RecordTypeId == null || listView.RecordTypeId == ListViewHelper.coreRTId) - objType = ListViewHelper.CORE_APEX_CLASS; - else - objType = listView.Custom_Apex_Class__c; - - //get an apex Type of the object type - Type t = HelperSchema.getClassType(objType); - - //create a new instance - ListViewAbstract lvLogic = (ListViewAbstract) t.newInstance(); - - //set all request information into the apex processing class. - lvlogic.setListView(listview); - - List columnData = lvLogic.getColumnData(); - - columnData.sort(); - - ListViewErrorHelper.processLogs(true); - - return columnData; - } - - /** - * @description Method which accepts a record Id and a JSON string containing field data to be updated on the record. - * @author tom@ansleyllc.com | 07-22-2021 - **/ - @AuraEnabled - public static String updateRecord(String rowId, String rowData) - { - //get just the Id as we return Id:position - String id = rowId.split(':')[0]; - - //add all field data - remember that Map objects cannot be stringified in Javascript so we need to turn - //everything into an array before sending so here we turn it back again. - Map fieldMap = new Map(); - List fieldData = (List) JSON.deserializeUntyped(rowData); - for (Object values: fieldData) - { - List fieldDataVals = (List) values; - String fieldName = (String) fieldDataVals[0]; - String value = (String) fieldDataVals[1]; - - fieldMap.put(fieldName, value); - } - - String result = ''; - try { - ListViewHelper.updateRecord(id, fieldMap); - - } catch (Exception e) { - String message = 'Exception during ListViewController.updateRecord(' + rowId + ', ' + rowData + ') ' + ListViewException.getExtendedString(e); - ListViewErrorHelper.createNonFutureUsageError(message); - result = 'There was an error saving the records - ' + e.getMessage(); - } - - ListViewErrorHelper.processLogs(true); - - return result; - } - - /** - * @description Method which accepts a record Id and a JSON string containing field data to be updated on the record. - * @author tom@ansleyllc.com | 07-30-2021 - **/ - @AuraEnabled - public static String updateRecords(String rowData) - { - System.debug(LoggingLevel.FINE, 'Starting updateRecords - ' + rowData); - - Map> dataMap = getRowDataFromJSON(rowData); - - System.debug(LoggingLevel.FINE, 'dataMap - ' + dataMap); - String result = ''; - try { - ListViewHelper.updateRecords(ListViewHelper.PROC_TYPE_UPDATE, null, dataMap); - - } catch (Exception e) { - String message = 'Exception during ListViewController.updateRecords(' + rowData + ') ' + ListViewException.getExtendedString(e); - ListViewErrorHelper.createNonFutureUsageError(message); - result = 'There was an error saving the records - ' + e.getMessage(); - } - - ListViewErrorHelper.processLogs(true); - - return result; - - } - - /** - * @description Method which accepts a record Id and a JSON string containing field data to use for creating new records. - * @author tom@ansleyllc.com | 11-13-2021 - **/ - @AuraEnabled - public static String createRecords(String objType, String rowData) - { - System.debug(LoggingLevel.FINE, 'Starting createRecords(' + objType + ', ' + rowData + ')'); - - Map> dataMap = getRowDataFromJSON(rowData); - - String result = ''; - try { - Map results = ListViewHelper.updateRecords(ListViewHelper.PROC_TYPE_CREATE, objType, dataMap); - - result = results.size() + ':' + SUCCESS; - } catch (Exception e) { - String message = 'Exception during ListViewController.createRecords(' + rowData + ') ' + ListViewException.getExtendedString(e); - ListViewErrorHelper.createNonFutureUsageError(message); - result = 'There was an error saving the records - ' + e.getMessage(); - } - - ListViewErrorHelper.processLogs(true); - - return result; - - } - - @AuraEnabled(cacheable=true) - public static List getPicklistValues(String objectName, String fieldName) - { - System.debug(LoggingLevel.DEBUG, 'Starting ListViewPicklistController.getPicklistValues(' + objectName + ',' + fieldName + ')'); - - Map picklistVals = HelperSchema.getPicklistMap(objectName, fieldName); - - List options = new List(); - for (String key: picklistVals.keySet()) - options.add(new SelectOption(key, picklistVals.get(key))); - return options; - } - - //============================= - // INTERNAL CLASSES - //============================= - - /** - * @description Method which takes a JSON string and converts it back into row data for processing - * @author tom.h.ansley@medtronic.com | 11-13-2021 - **/ - private static Map> getRowDataFromJSON(String jsonString) - { - //remember that Map objects cannot be stringified in Javascript so we need to turn - //everything into an array before sending so here we turn it back again. AND, this - //is a map of maps so its trickier - Map rowMap = (Map) JSON.deserializeUntyped(jsonString); - Map> dataMap = new Map>(); - System.debug(LoggingLevel.FINE, 'rowMap - ' + rowMap); - for (String rowId: rowMap.keySet()) - { - System.debug(LoggingLevel.FINE, 'Row Id - ' + rowId); - Map fieldMap = new Map(); - - //get just the Id as we return Id:position - String id = rowId.split(':')[0]; - - List fieldDataVals = (List) rowMap.get(rowId); - System.debug(LoggingLevel.FINE, 'fieldDataVals - ' + fieldDataVals); - for (Object valuePairs: fieldDataVals) - { - List values = (List) valuePairs; - String fieldName = (String) values[0]; - String value = (String) values[1]; - - fieldMap.put(fieldName, value); - } - dataMap.put(id, fieldMap); - - } - System.debug(LoggingLevel.FINE, 'dataMap - ' + dataMap); - - return dataMap; - } - - //============================= - // INTERNAL CLASSES - //============================= - - /* - * Have to build our own SelectOption object as Lightning cannot use System.SelectOption - */ - public class SelectOption implements Comparable { - public SelectOption(String value, String label) { - this.value = value; - this.label = label; - } - - @AuraEnabled - public String label { get;set; } - @AuraEnabled - public String value { get;set; } - - public Integer compareTo(Object compareTo) { - SelectOption option2 = (SelectOption) compareTo; - return label.compareTo(option2.label); - } - } - +/** + * @description : + * @author : tom@ansleyllc.com + * @group : + * @last modified on : 07-24-2024 + * @last modified by : tom@ansleyllc.com + * Modifications Log + * Ver Date Author Modification + * 1.0 08-04-2020 tom@ansleyllc.com Initial Version + * 2.0 06-18-2021 tom@ansleyllc.com Added offset to allow for larger datasets + * 3.0 06-18-2021 tom@ansleyllc.com Added logic when list views are not initialized + * 4.0 07-30-2021 tom@ansleyllc.com Added updateRecords() method + * 5.0 08-16-2021 tom@ansleyllc.com Updated getListViewsActions() and added permission check for each action before display + * 6.0 08-20-2021 tom@ansleyllc.com renamed getListViewsActions() to getListViewActions() + * 7.0 08-25-2021 tom@ansleyllc.com renamed getListViewColumnLabels() to getListViewColumns() + * 8.0 12-15-2021 tom@ansleyllc.com Pulled out creating types due to different methods for handling types with package names etc. + **/ +public without sharing class ListViewController +{ + + public static final String SUCCESS = 'success'; + public static final String FAILED = 'failed'; + + @AuraEnabled + public static Boolean hasEnterprise() + { + return ListViewHelper.hasEnterprise(); + } + + @AuraEnabled + public static Boolean hasModifyAll() + { + ListViewErrorHelper.processLogs(true); + + return HelperProfile.hasModifyAll(); + } + + /* + * Method to retrieve the progress of the initialization batch process + */ + @AuraEnabled + public static String getListViewInitProgress(String batchId) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewInitProgress(' + batchId + ')'); + String result = '0'; + String status = ''; + + if (batchId != '') + { + //Query the Batch apex jobs + List jobs = HelperScheduler.getCurrentlyRunningJobs(); + + if (jobs.size() == 0) { + + result = '0'; + status = 'Waiting'; + + if(getIsInitialized()) + { + status = 'Completed'; + result = '100'; + } + + } else if (jobs[0].TotalJobItems == 0) { + + result = '0'; + status = jobs[0].Status + ' ' + jobs[0].ApexClass.Name; + + } else { + + Decimal percentComp = (Decimal.valueOf(jobs[0].JobItemsProcessed)/Decimal.valueOf(jobs[0].TotalJobItems)) * 100; + if (percentComp == 100) + percentComp = 99; //make it 99 if done as 100 is considered completely finished! + result = String.valueOf(percentComp); + status = jobs[0].Status + ' ' + jobs[0].ApexClass.Name; + + } + } else { + result = '-1'; + } + + System.debug(LoggingLevel.DEBUG, 'Finishing ListViewController.getListViewInitProgress with result - ' + result + ':' + status); + + ListViewErrorHelper.processLogs(true); + + return String.valueOf(result) + ':' + status; + } + + /* + * Method which returns list of objects names and API names in the system + */ + @AuraEnabled(cacheable=true) + public static List getListViewObjects(String includedObjects, String excludedObjects) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewObjects(' + includedObjects + ', ' + excludedObjects + ')'); + List configListOptions = new List(); + + Set excObjs = HelperString.getSetFromString(excludedObjects, ','); + Set incObjs = HelperString.getSetFromString(includedObjects, ','); + + Map listviewObjects = ListViewHelper.getListViewObjects(incObjs, excObjs); + + String debug = '\n\n================ FINAL OBJECT LIST ================\n'; + for (String apiName: listviewObjects.keySet()) + { + debug += apiName + ' - ' + listviewObjects.get(apiName) + '\n'; + configListOptions.add(new SelectOption(apiName, listviewObjects.get(apiName))); + } + debug += '===================================================\n'; + System.debug(LoggingLevel.DEBUG, debug); + + configListOptions.sort(); + + ListViewErrorHelper.processLogs(true); + + return configListOptions; + } + + @AuraEnabled + public static Boolean getIsInitialized() + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getIsInitialized()'); + + Boolean isInitialized = false; + try { + isInitialized = Boolean.valueOf(ListViewConfigHelper.getOrgWideConfigParam('IsInitialized')); + } catch(Exception e) { + //no global config! + } + ListViewErrorHelper.processLogs(true); + + return isInitialized; + } + + @AuraEnabled + public static List getObjectListViews(String objectName) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getObjectListViews(' + objectName + ')'); + + Map listviews = ListViewHelper.getListViewsByObject(objectName); + + List configListOptions = new List(); + + for (List_View__c listview : listviews.values()) + { + configListOptions.add(new SelectOption(listview.API_Name__c, listview.Label__c)); + } + + configListOptions.sort(); + + ListViewErrorHelper.processLogs(true); + + return configListOptions; + } + + @AuraEnabled + public static ListViewAbstract.ListViewConfigWrapper getListViewConfig(String objectName, String listViewName, String listViewMode) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewConfig(' + objectName + ', ' + listViewName + ', ' + listViewMode + ')'); + + ListViewAbstract.ListViewConfigWrapper lvConfigWrapper = new ListViewAbstract.ListViewConfigWrapper(objectName, listViewName, listViewMode); + + if (objectName == null || listViewName == null) + return lvConfigWrapper; + + + Map listViews = ListViewHelper.getListViews(objectName, listViewName); + if (listViews.isEmpty()) + throw new ListViewException('The list view configuration can not be found for object ' + objectName + ' and list view name - ' + listViewName + ')'); + + List_View__c listview = listViews.values()[0]; + + String objType = null; + if (listView.RecordTypeId == null || listView.RecordTypeId == ListViewHelper.coreRTId) + objType = ListViewHelper.CORE_APEX_CLASS; + else + objType = listView.Custom_Apex_Class__c; + + //get an apex Type of the object type + Type t = HelperSchema.getClassType(objType); + + //create a new instance + ListViewAbstract lvLogic = (ListViewAbstract) t.newInstance(); + lvLogic.listviewMode = listViewMode; + + //set all request information into the apex processing class. + lvlogic.setListView(listview); + + lvConfigWrapper = lvlogic.lvConfig; + + HelperString.debug(lvConfigWrapper, 'ListViewController(getListViewConfig)'); + ListViewErrorHelper.addLog('ListViewController(getListViewConfig)', lvConfigWrapper.getDebugString()); + + ListViewErrorHelper.processLogs(true); + + return lvConfigWrapper; + } + + /** + * @description Method which gets the users SORT configuration for the provided COMPONENT. The method manually creates + the JSON request as it makes it easier on the front end to handle. Here is an example JSON response - + {"listviews": [{"name": "Account:Simpli_LV_Acct_1","fields": [{"sortIndex": "0", "fieldName": "Name", "sortDirection": "true"},{"sortIndex": "1", "fieldName": "BillingState", "sortDirection": "false"}]}, {"name": "Account:PlatinumandGoldSLACustomers","fields": [{"sortIndex": "0", "fieldName": "Name", "sortDirection": "true"},{"sortIndex": "1", "fieldName": "BillingState", "sortDirection": "false"},{"sortIndex": "2", "fieldName": "Id", "sortDirection": "false"}]}]} + + * @author tom@ansleyllc.com | 10-10-2020 + * @param compName the component name which is the name of the Lightning component that calls this method. + * @return Map + **/ + @AuraEnabled + public static String getUserSortConfigs(String compName) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getUserSortConfigs(' + compName + ')'); + + String sortConfigJSON = '{"listviews": ['; + //String sortJSON = '{'; + //get the users component config + List configs = ListViewUserConfigHelper.getCurrentUserCompSortConfigs(compName); + for (List_View_User_Config__c config: configs) + { + sortConfigJSON += '{"name": "' + config.Name__c.removeStart('sortOrder:') + '","fields": ['; + + List sortFields = config.Value__c.split(';'); + + for (String sortField: sortFields) + { + List sortParts = sortField.split(':'); + sortConfigJSON += '{"sortIndex": "' + sortParts[0] + '", "fieldName": "' + sortParts[2] + '", "sortDirection": "' + sortParts[1] + '"},'; + } + + sortConfigJSON = sortConfigJSON.removeEnd(',') + ']},'; + + } + sortConfigJSON = sortConfigJSON.removeEnd(',') + ']}'; + + ListViewErrorHelper.processLogs(true); + + return sortConfigJSON; + } + + @AuraEnabled + public static String getListViewConfigParameter(String objectName, String listViewName, String paramName) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewConfigParameter(' + objectName + ', ' + listViewName + ',' + paramName + ')'); + + String paramValue = ListViewConfigHelper.getListViewConfigParameter(objectName, listViewName, paramName); + + ListViewErrorHelper.processLogs(true); + + return paramValue; + } + + /** + * @description Method which gets the users configuration for the provided COMPONENT. + * Note that this method also gets the org wide defaults as it needs to layer those defaults on top of the user config + * @author tom@ansleyllc.com | 10-10-2020 + * @param compName the component name which is the name of the Lightning component that calls this method. + * @return Map + **/ + @AuraEnabled + public static Map getComponentConfig(String compName) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getComponentConfig(' + compName + ')'); + Map userConfigs = ListViewUserConfigHelper.getComponentConfig(compName); + + ListViewErrorHelper.processLogs(true); + + return userConfigs; + } + + @AuraEnabled + public static String updateUserConfig(String compName, String configName, String value) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.updateUserConfig(' + compName + ', ' + configName + ',' + value + ')'); + try { + ListViewUserConfigHelper.updateCurrentUserConfigValue(compName, configName, value); + } catch (Exception e) { + String message = 'Exception during ListViewController.updateUserConfig(' + compName + ',' + configName + ',' + value + ') ' + ListViewException.getExtendedString(e); + ListViewErrorHelper.createFutureUsageError(message); + return 'There was an error during user configuration update'; + } + + ListViewErrorHelper.processLogs(true); + + return SUCCESS; + } + + /* + * Method to update a specific list view for a user with given column width data. + */ + @AuraEnabled + public static String updateUserConfigListViewWidth(String compName, String configName, String columnIndex, String width) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.updateUserConfigListViewWidth(' + compName + ', ' + configName + ',' + columnIndex + ',' + width + ')'); + try { + + //get existing config if it exists. + List_View_User_Config__c widthConfig = ListViewUserConfigHelper.getCurrentUserConfigByName(compName, configName); + + String widthStr = ''; + + //if config exists then UPDATE + if (widthConfig != null) + { + //list width config example - 1:25;2:25;3:120;4:80 + widthStr = widthConfig.Value__c; + + //break up string into columns + List columnWidthsStr = widthStr.split(';'); + Map widthsByIndex = new Map(); + Boolean hasValue = false; + for (String columnWidthStr: columnWidthsStr) + { + //update individual column width data if it exists + List values = columnWidthStr.split(':'); + if (values[0] == columnIndex) //value[0] = column index + { + hasValue = true; + widthsByIndex.put(Integer.valueOf(values[0]), width); + } else { + widthsByIndex.put(Integer.valueOf(values[0]), values[1]); + } + } + + //add column and width if the column index doesn't already exist + if (!hasValue) + widthsByIndex.put(Integer.valueOf(columnIndex), width); + + //go through map and sort + List columnIndexes = new List(widthsByIndex.keySet()); + columnIndexes.sort(); + + //go through map and recreate string + widthStr = ''; + for (Integer tmpColumnIndex: columnIndexes) + { + String tmpWidth = widthsByIndex.get(tmpColumnIndex); + + widthStr += tmpColumnIndex + ':' + tmpWidth + ';'; + } + + //else INSERT new data + } else { + widthStr = columnIndex + ':' + width; + } + + ListViewUserConfigHelper.updateCurrentUserConfigValue(compName, configName, widthStr); + + } catch (Exception e) { + String message = 'Exception during ListViewController.updateUserConfigListViewWidth(' + compName + ',' + configName + ',' + columnIndex + ',' + width + ') ' + ListViewException.getExtendedString(e); + ListViewErrorHelper.createFutureUsageError(message); + return 'There was an error during user configuration update'; + } + + ListViewErrorHelper.processLogs(true); + + return SUCCESS; + } + + @AuraEnabled + public static String updateObjectListViews(String objectType) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.updateObjectListViews(' + objectType + ')'); + + //update the list view + String response = ListViewHelper.updateObjectListViews(objectType); + + ListViewErrorHelper.processLogs(true); + + return response; + } + + @AuraEnabled + public static String updateSingleListView(String objectType, String listViewName) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.updateSingleListView(' + objectType + ', ' + listViewName + ')'); + + String response = ListViewHelper.updateSingleListView(objectType, listViewName); + + ListViewErrorHelper.processLogs(true); + + return response; + } + + @AuraEnabled + public static String updateChangedListViews() + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.updateChangedListViews()'); + + String response = ListViewHelper.updateChangedListViews(); + + ListViewErrorHelper.processLogs(true); + + return response; + } + + @AuraEnabled + public static String updateAllListViews() + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.updateAllListViews()'); + + String response = ListViewHelper.updateAllListViews(); + + ListViewErrorHelper.processLogs(true); + + return response; + } + + @AuraEnabled(cacheable=true) + public static List getListViewActions(String objectType, String listViewName, String componentName) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewActions(' + objectType + ', ' + listViewName + ', ' + componentName + ')'); + + List actions = ListViewActionHelper.getListViewActions(objectType, listViewName, componentName); + + ListViewErrorHelper.processLogs(true); + + return actions; + } + + @AuraEnabled + public static ListViewActionWrapper getListViewActionAndData(String actionName, String dataIds) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewAction(' + actionName + ', ' + dataIds + ')'); + + ListViewActionWrapper wrapper = getListViewAction(actionName); + + try { + + //if we are provided record Ids then get the record and try to set the initial values. + if (dataIds != null){ + List listIds = (List) JSON.deserializeUntyped(dataIds); + if (listIds.size() == 1) + { + //1. get the object API Name + String objAPIName = wrapper.getObjectType(); + + //2. get the single record Id + String recordId = (String) listIds[0]; + recordId = String.escapeSingleQuotes(recordId); + + + //3. Get the list of fields + List fieldAPINames = new List(); + for (ListViewActionWrapper.ActionParameterWrapper param: wrapper.getDisplayParameters()) + { + fieldAPINames.add(param.getAPIName()); + } + + //4. Get record + SObject record = HelperDatabase.query(objAPIName, fieldAPINames, recordId); + + //5. Set the record values into the action wrapper + if (record != null) + { + for (ListViewActionWrapper.ActionParameterWrapper param: wrapper.getDisplayParameters()) + { + param.setDefaultValue(record.get(param.getAPIName())); + } + } + } + } + } catch (Exception e) { + String message = 'Exception during ListViewController.getListViewAction(' + actionName + ') ' + ListViewException.getExtendedString(e); + ListViewErrorHelper.createFutureUsageError(message); + } + + ListViewErrorHelper.processLogs(true); + + return wrapper; + } + + @AuraEnabled + public static ListViewActionWrapper getListViewAction(String actionName) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewAction(' + actionName + ')'); + + ListViewActionWrapper wrapper = null; + if (!String.isEmpty(actionName)) + { + + List_View_Action__c action = null; + + try { + action = ListViewActionHelper.getListViewActionByKey(actionName); + wrapper = new ListViewActionWrapper(action); + + } catch (Exception e) { + String message = 'Exception during ListViewController.getListViewAction(' + actionName + ') ' + ListViewException.getExtendedString(e); + ListViewErrorHelper.createFutureUsageError(message); + } + } + + ListViewErrorHelper.processLogs(true); + + return wrapper; + } + + /* + * Method which determines whether a list view data request is valid or not. + */ + @AuraEnabled + public static String isValidListViewDataRequest(String objectName, String joinFieldName, String joinData) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.isValidListViewDataRequest(' + objectName + ', ' + joinFieldName + ',' + joinData + ')'); + String result = SUCCESS; + + //if we have join information then get the record Ids + Set joinRecordIds = null; + if (joinFieldName != '' && joinData != '') + { + //check that we can actually act on the join data as it could be for another component. + if (HelperSchema.isValidSFDCFieldName(objectName, joinFieldName)) + { + Map joinDataMap = (Map) JSON.deserializeUntyped(joinData); + joinRecordIds = HelperString.getSetFromString(((String) joinDataMap.get('recordIds')), ','); + joinRecordIds.remove(null); //make sure we remove any nulls + joinRecordIds.remove(''); + + //if we have any Ids we need to check that the Ids match the field object type + //unless its a polymorphic field, then we just have to go with it!! + if (joinRecordIds.size() > 0 + && !ListViewHelper.POLYMORPHIC_FIELDS.contains(joinFieldName) + && !joinFieldName.contains('.')) + { + //get the type of the join field lookup object. + String objType = HelperSchema.getObjectTypeForField(objectName, joinFieldName); + + String objType2 = ''; + //this is crazy. In order to get any one value from a set + //I have to iterate over it and break at the first value!! + for (String recordId: joinRecordIds) + { + objType2 = HelperSchema.getObjectTypeFromId(recordId); + break; + } + + if (objType != objType2) + result = 'failure'; + } + } + } + + System.debug(LoggingLevel.DEBUG, 'Leaving isValidListViewDataRequest - ' + result); + ListViewErrorHelper.processLogs(true); + + return result; + } + + /* + * Method which determines whether a list view data request is valid or not. + */ + @AuraEnabled + public static String getListViewId(String objectName, String listViewName) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewId(' + objectName + ', ' + listViewName + ')'); + String id = ''; + + Map listView = ListViewHelper.getListViews(objectName, listViewName); + + if (!listView.isEmpty()) + id = listView.values()[0].Id; + + System.debug(LoggingLevel.DEBUG, 'Leaving getListViewId - ' + id); + ListViewErrorHelper.processLogs(true); + + return id; + } + + @AuraEnabled + public static ListViewAbstract.RowsWrapper getListViewDataShell(String compType, String objectName, String listViewName, String joinFieldName, String joinData) + { + ListViewAbstract.RowsWrapper rows = getListViewData(null, compType, objectName, listViewName, null, joinFieldName, joinData, -1, true, ''); + + //clone the only returned row and change its rowId + ListViewAbstract.RowWrapper rowWrapper = rows.getRows()[0]; + rowWrapper.rowId = '1'; + rowWrapper.sfdcId = '1'; + + String debug = '\n\n------------------------------------\n'; + for(ListViewAbstract.FieldWrapper field: rowWrapper.getFields()) + { + field.setFieldObj(null); + field.setObjValueId(null); + field.setValue(null); + debug += field.getName() + ' - ' + field.getIsEditable() + ' - ' + field.getType() + '\n'; + } + debug += '------------------------------------\n'; + System.debug(LoggingLevel.DEBUG, debug); + + Integer rowCount = Integer.valueOf(ListViewConfigHelper.getOrgWideConfigParam('MassCreateRowCount', '10')); + + + for (Integer count = 2; count < rowCount + 1; count++) + { + ListViewAbstract.RowWrapper row = rowWrapper.cloneWrapper(false); + row.rowId = String.valueOf(count); + rows.addRow(row); + } + return rows; + } + + public static ListViewAbstract.RowsWrapper getListViewData(String compType, String objectName, String listViewName) + { + return getListViewData('', compType, objectName, listViewName, '', '', '', -1, false, ''); + } + + @AuraEnabled + public static ListViewAbstract.RowsWrapper getListViewData(String pageName, String compType, String objectName, String listViewName, String sortData, String joinFieldName, String joinData, Integer offset, String textSearchStr) + { + return getListViewData(pageName, compType, objectName, listViewName, sortData, joinFieldName, joinData, offset, false, textSearchStr); + } + + /** + * @description Method to retrieve list view data based on user provided criteria. + * @author tom@ansleyllc.com | 06-28-2021 + * @param pageName the name of the lightning page that the list is being displayed on. This is used for list view config retrieval + * @param compType the mode of the component the data is being retrieved for. + * @param objectName the name of the list view object + * @param listViewName the name of the list view + * @param sortData the sorting information for the list view. + * @param joinFieldName used by a list view component listening on the same page. Indicates the field name used to join the data between the two list views. + * @param joinData the joined field data to use when performing the query. + * @param offset indicates the offset if the data is being paged. + * @param isShell indicates whether the request should only retrieve the rowwrapper without data or all the data + * @return ListViewAbstract.RowsWrapper + **/ + public static ListViewAbstract.RowsWrapper getListViewData(String pageName, String compType, String objectName, String listViewName, String sortData, String joinFieldName, String joinData, Integer offset, Boolean isShell, String textSearchStr) + { + System.debug(LoggingLevel.ERROR, 'Starting ListViewController.getListViewData(' + pageName + ', ' + objectName + ', ' + listViewName + ',' + sortData + ',' + joinFieldName + ',' + joinData + ', ' + isShell + ', ' + textSearchStr + ')'); + return ListViewHelper.getListViewData(pageName, compType, objectName, listViewName, sortData, joinFieldName, joinData, offset, isShell, textSearchStr); + } + + @AuraEnabled + public static String processParamChange(String objectName, String listViewName, String paramName, String paramValue, String paramLabel, String paramType) + { + objectName = String.escapeSingleQuotes(objectName); + listViewName = String.escapeSingleQuotes(listViewName); + paramName = String.escapeSingleQuotes(paramName); + paramValue = String.escapeSingleQuotes(paramValue); + paramLabel = String.escapeSingleQuotes(paramLabel); + paramType = String.escapeSingleQuotes(paramType); + + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.processParamChange(' + objectName + ', ' + listViewName + ', ' + paramName + ', ' + paramValue + ', ' + paramLabel + ', ' + paramType + ')'); + + String rtnStr = 'Ok:'; + + try { + + String errorStr = ListViewConfigHelper.updateListViewParam(objectName, listViewName, paramName, paramValue, paramLabel, paramType); + + if (errorStr != '') + { + rtnStr = 'Failed:' + errorStr; + } + + } catch (Exception e) { + rtnStr = 'Failed:' + e.getMessage(); + String message = 'Exception during ListViewController.processParamChange(' + objectName + ', ' + listViewName + ', ' + paramName + ', ' + paramValue + ') ' + ListViewException.getExtendedString(e); + ListViewErrorHelper.createFutureUsageError(message); + } + + ListViewErrorHelper.processLogs(true); + + return rtnStr; + } + + /* + * action = remove or add + * conditionData = condition id (if remove) or condition data map (if add) + */ + @AuraEnabled + public static String processConditionChange(String objectName, String listViewName, String action, String conditionData) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.processConditionChange(' + objectName + ', ' + listViewName + ', ' + action + ', ' + conditionData + ')'); + + String rtnStr = 'Ok:'; + + if (action == 'add') + { + //get the field/value pairs of the update data + List listValues = (List) JSON.deserializeUntyped(conditionData); + Map mapValues = new Map(); + for (Object key: listValues) + { + List keyValues = (List) key; + mapValues.put( (String) keyValues[0], keyValues[1]); + } + + try { + ListViewConfigHelper.addListViewCondition(objectName, + listViewName, + (String) mapValues.get('field'), + (String) mapValues.get('operator'), + (String) mapValues.get('value'), + (String) mapValues.get('order'), + (String) mapValues.get('color')); + CacheListViewConfig.remove(objectName, listViewName); + } catch (Exception e) { + rtnStr = 'Failed:' + e.getMessage(); + String message = 'Exception during ListViewController.processConditionChange(' + objectName + ', ' + listViewName + ', ' + action + ', ' + conditionData + ') ' + ListViewException.getExtendedString(e); + ListViewErrorHelper.createFutureUsageError(message); + } + + } else if (action == 'remove') + { + try { + ListViewConfigHelper.deleteListViewCondition(conditionData); + CacheListViewConfig.remove(objectName, listViewName); + } catch (Exception e) { + rtnStr = 'Failed:' + e.getMessage(); + String message = 'Exception during ListViewController.processConditionChange(' + objectName + ', ' + listViewName + ', ' + action + ', ' + conditionData + ') ' + ListViewException.getExtendedString(e); + ListViewErrorHelper.createFutureUsageError(message); + } + } + + ListViewErrorHelper.processLogs(true); + + return rtnStr; + } + + /* + * action = remove or add + * conditionData = condition id (if remove) or condition data map (if add) + */ + @AuraEnabled + public static String processColumnStyleChange(String objectName, String listViewName, String action, String columnStyleData) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.processColumnStyleChange(' + objectName + ', ' + listViewName + ', ' + action + ', ' + columnStyleData + ')'); + + String rtnStr = 'Ok:'; + + if (action == 'add') + { + //get the field/value pairs of the update data + List listValues = (List) JSON.deserializeUntyped(columnStyleData); + Map mapValues = new Map(); + for (Object key: listValues) + { + List keyValues = (List) key; + mapValues.put( (String) keyValues[0], keyValues[1]); + } + + try { + ListViewConfigHelper.addListViewColumnStyle(objectName, + listViewName, + (String) mapValues.get('field'), + (String) mapValues.get('decoration'), + (String) mapValues.get('font'), + (String) mapValues.get('style'), + (String) mapValues.get('transform'), + (String) mapValues.get('variant'), + (String) mapValues.get('weight'), + (String) mapValues.get('alignment')); + CacheListViewConfig.remove(objectName, listViewName); + } catch (Exception e) { + rtnStr = 'Failed:' + e.getMessage(); + String message = 'Exception during ListViewController.processColumnStyleChange(' + objectName + ', ' + listViewName + ', ' + action + ', ' + columnStyleData + ') ' + ListViewException.getExtendedString(e); + ListViewErrorHelper.createFutureUsageError(message); + } + + } else if (action == 'remove') + { + try { + ListViewConfigHelper.deleteListViewColumnStyle(columnStyleData); + CacheListViewConfig.remove(objectName, listViewName); + } catch (Exception e) { + rtnStr = 'Failed:' + e.getMessage(); + String message = 'Exception during ListViewController.processColumnStyleChange(' + objectName + ', ' + listViewName + ', ' + action + ', ' + columnStyleData + ') ' + ListViewException.getExtendedString(e); + ListViewErrorHelper.createFutureUsageError(message); + } + } + + ListViewErrorHelper.processLogs(true); + + return rtnStr; + } + + @AuraEnabled + public static String processAction(String actionKey, String dataIds, String valuesMap) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.processAction(' + actionKey + ', ' + dataIds + ',' + valuesMap + ')'); + + String responseStr = ListViewActionHelper.processAction(actionKey, dataIds, valuesMap); + + return responseStr; + } + + @AuraEnabled(cacheable=true) + public static List getListViewColumns(String objectName, String listViewName) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewController.getListViewColumns(' + objectName + ', ' + listViewName + ')'); + + //get the core SFDC list view data + Map listViews = ListViewHelper.getListViews(objectName, listViewName); + if (listViews.size() == 0) return new List(); + List_View__c listView = listViews.values()[0]; + + //------------------------------------------------------------------- + String objType = null; + if (listView.RecordTypeId == null || listView.RecordTypeId == ListViewHelper.coreRTId) + objType = ListViewHelper.CORE_APEX_CLASS; + else + objType = listView.Custom_Apex_Class__c; + + //get an apex Type of the object type + Type t = HelperSchema.getClassType(objType); + + //create a new instance + ListViewAbstract lvLogic = (ListViewAbstract) t.newInstance(); + + //set all request information into the apex processing class. + lvlogic.setListView(listview); + + List columnData = lvLogic.getColumnData(); + + columnData.sort(); + + ListViewErrorHelper.processLogs(true); + + return columnData; + } + + /** + * @description Method which accepts a record Id and a JSON string containing field data to be updated on the record. + * @author tom@ansleyllc.com | 07-22-2021 + **/ + @AuraEnabled + public static String updateRecord(String rowId, String rowData) + { + //get just the Id as we return Id:position + String id = rowId.split(':')[0]; + + //add all field data - remember that Map objects cannot be stringified in Javascript so we need to turn + //everything into an array before sending so here we turn it back again. + Map fieldMap = new Map(); + List fieldData = (List) JSON.deserializeUntyped(rowData); + for (Object values: fieldData) + { + List fieldDataVals = (List) values; + String fieldName = (String) fieldDataVals[0]; + String value = (String) fieldDataVals[1]; + + fieldMap.put(fieldName, value); + } + + String result = ''; + try { + ListViewHelper.updateRecord(id, fieldMap); + + } catch (Exception e) { + String message = 'Exception during ListViewController.updateRecord(' + rowId + ', ' + rowData + ') ' + ListViewException.getExtendedString(e); + ListViewErrorHelper.createNonFutureUsageError(message); + result = 'There was an error saving the records - ' + e.getMessage(); + } + + ListViewErrorHelper.processLogs(true); + + return result; + } + + /** + * @description Method which accepts a record Id and a JSON string containing field data to be updated on the record. + * @author tom@ansleyllc.com | 07-30-2021 + **/ + @AuraEnabled + public static String updateRecords(String rowData) + { + System.debug(LoggingLevel.FINE, 'Starting updateRecords - ' + rowData); + + Map> dataMap = getRowDataFromJSON(rowData); + + System.debug(LoggingLevel.FINE, 'dataMap - ' + dataMap); + String result = ''; + try { + ListViewHelper.updateRecords(ListViewHelper.PROC_TYPE_UPDATE, null, dataMap); + + } catch (Exception e) { + String message = 'Exception during ListViewController.updateRecords(' + rowData + ') ' + ListViewException.getExtendedString(e); + ListViewErrorHelper.createNonFutureUsageError(message); + result = 'There was an error saving the records - ' + e.getMessage(); + } + + ListViewErrorHelper.processLogs(true); + + return result; + + } + + /** + * @description Method which accepts a record Id and a JSON string containing field data to use for creating new records. + * @author tom@ansleyllc.com | 11-13-2021 + **/ + @AuraEnabled + public static String createRecords(String objType, String rowData) + { + System.debug(LoggingLevel.FINE, 'Starting createRecords(' + objType + ', ' + rowData + ')'); + + Map> dataMap = getRowDataFromJSON(rowData); + + String result = ''; + try { + Map results = ListViewHelper.updateRecords(ListViewHelper.PROC_TYPE_CREATE, objType, dataMap); + + result = results.size() + ':' + SUCCESS; + } catch (Exception e) { + String message = 'Exception during ListViewController.createRecords(' + rowData + ') ' + ListViewException.getExtendedString(e); + ListViewErrorHelper.createNonFutureUsageError(message); + result = 'There was an error saving the records - ' + e.getMessage(); + } + + ListViewErrorHelper.processLogs(true); + + return result; + + } + + @AuraEnabled(cacheable=true) + public static List getPicklistValues(String objectName, String fieldName) + { + System.debug(LoggingLevel.DEBUG, 'Starting ListViewPicklistController.getPicklistValues(' + objectName + ',' + fieldName + ')'); + + Map picklistVals = HelperSchema.getPicklistMap(objectName, fieldName); + + List options = new List(); + for (String key: picklistVals.keySet()) + options.add(new SelectOption(key, picklistVals.get(key))); + return options; + } + + //============================= + // INTERNAL CLASSES + //============================= + + /** + * @description Method which takes a JSON string and converts it back into row data for processing + * @author tom.h.ansley@medtronic.com | 11-13-2021 + **/ + private static Map> getRowDataFromJSON(String jsonString) + { + //remember that Map objects cannot be stringified in Javascript so we need to turn + //everything into an array before sending so here we turn it back again. AND, this + //is a map of maps so its trickier + Map rowMap = (Map) JSON.deserializeUntyped(jsonString); + Map> dataMap = new Map>(); + System.debug(LoggingLevel.FINE, 'rowMap - ' + rowMap); + for (String rowId: rowMap.keySet()) + { + System.debug(LoggingLevel.FINE, 'Row Id - ' + rowId); + Map fieldMap = new Map(); + + //get just the Id as we return Id:position + String id = rowId.split(':')[0]; + + List fieldDataVals = (List) rowMap.get(rowId); + System.debug(LoggingLevel.FINE, 'fieldDataVals - ' + fieldDataVals); + for (Object valuePairs: fieldDataVals) + { + List values = (List) valuePairs; + String fieldName = (String) values[0]; + String value = (String) values[1]; + + fieldMap.put(fieldName, value); + } + dataMap.put(id, fieldMap); + + } + System.debug(LoggingLevel.FINE, 'dataMap - ' + dataMap); + + return dataMap; + } + + //============================= + // INTERNAL CLASSES + //============================= + + /* + * Have to build our own SelectOption object as Lightning cannot use System.SelectOption + */ + public class SelectOption implements Comparable { + public SelectOption(String value, String label) { + this.value = value; + this.label = label; + } + + @AuraEnabled + public String label { get;set; } + @AuraEnabled + public String value { get;set; } + + public Integer compareTo(Object compareTo) { + SelectOption option2 = (SelectOption) compareTo; + return label.compareTo(option2.label); + } + } + } \ No newline at end of file diff --git a/force-app/main/default/classes/ListViewControllerTest.cls b/force-app/main/default/classes/ListViewControllerTest.cls index 1a44797..4bcefdd 100644 --- a/force-app/main/default/classes/ListViewControllerTest.cls +++ b/force-app/main/default/classes/ListViewControllerTest.cls @@ -2,7 +2,7 @@ * @description : * @author : tom@ansleyllc.com * @group : - * @last modified on : 08-07-2024 + * @last modified on : 08-14-2024 * @last modified by : tom@ansleyllc.com * Modifications Log * Ver Date Author Modification @@ -118,7 +118,7 @@ global class ListViewControllerTest { { HelperTest.createCoreListViews(); - String result = ListViewController.processParamChange('Account', 'simpli_lv__AllAccounts', 'RefreshRate', '45', 'Refresh Rate', ListViewHelper.TYPE_NUMBER); + String result = ListViewController.processParamChange('Account', 'simpli_lv__AllAccounts', ListViewHelper.PARAM_REFRESH_RATE, '45', 'Refresh Rate', ListViewHelper.TYPE_NUMBER); System.assertEquals(result, 'Ok:'); diff --git a/force-app/main/default/classes/ListViewCore.cls b/force-app/main/default/classes/ListViewCore.cls index 3775a52..3c34d0e 100644 --- a/force-app/main/default/classes/ListViewCore.cls +++ b/force-app/main/default/classes/ListViewCore.cls @@ -1,7 +1,7 @@ /** * @description : * @author : tom@ansleyllc.com - * @last modified on : 04-28-2024 + * @last modified on : 08-13-2024 * @last modified by : tom@ansleyllc.com * Modifications Log * Ver Date Author Modification @@ -219,22 +219,17 @@ public with sharing class ListViewCore extends ListViewAbstract { List listViewColumns = new List(); //get all columns from the list view - List allColumns = (List) JSON.deserializeUntyped(listView.Core_ListView_Columns__c); + Map cols = ListViewHelper.getColumnsFromJSON(listView.Core_ListView_Columns__c); //get the CORE SFDC COLUMN meta data - for (Object column: allColumns) + for (ListViewHelper.StandardColumn column: cols.values()) { - Map columnData = (Map) column; - - String fieldName = (String) columnData.get('fieldNameOrPath'); - Boolean hidden = (Boolean) columnData.get('hidden'); - - if (hidden == null || !hidden) + if (column.hidden == null || !column.hidden) { - HelperSchema.FieldData d = HelperSchema.getFieldData(exampleRow, fieldName); + HelperSchema.FieldData d = HelperSchema.getFieldData(exampleRow, column.fieldNameOrPath); if (d != null && !d.isChildRel) - listViewColumns.add(new ListViewColumn(d.label, fieldName, d.getType())); + listViewColumns.add(new ListViewColumn(d.label, column.fieldNameOrPath, d.getType())); } } @@ -285,33 +280,30 @@ public with sharing class ListViewCore extends ListViewAbstract { rows.listView.offset = offset; //get all columns from the list view - List allColumns = (List) JSON.deserializeUntyped(listView.Core_ListView_Columns__c); - List columns = new List(); + Map cols = ListViewHelper.getColumnsFromJSON(listView.Core_ListView_Columns__c); + + List columns = new List(); Boolean addColumn = true; //set the CORE SFDC COLUMN meta data into the rows wrapper Integer columnIndex = 1; - for (Object column: allColumns) + for (ListViewHelper.StandardColumn column: cols.values()) { - Map columnData = (Map) column; - String fieldName = (String) columnData.get('fieldNameOrPath'); - Boolean hidden = (Boolean) columnData.get('hidden'); - - if (dataRows.size() > 0 && !hidden) + if (dataRows.size() > 0 && !column.hidden) { - HelperSchema.FieldData fieldData = HelperSchema.getFieldData(dataRows[0], fieldName); + HelperSchema.FieldData fieldData = HelperSchema.getFieldData(dataRows[0], column.fieldNameOrPath); FieldWrapper field = new FieldWrapper(fieldData.label, fieldData.getType(), - fieldName, + column.fieldNameOrPath, columnIndex); - if (sortDataByFieldName.containsKey(fieldName)) + if (sortDataByFieldName.containsKey(column.fieldNameOrPath)) { - field.sortIndex = String.valueOf(sortDataByFieldName.get(fieldName).sortIndex); - field.sortDir = sortDataByFieldName.get(fieldName).sortDirection; + field.sortIndex = String.valueOf(sortDataByFieldName.get(column.fieldNameOrPath).sortIndex); + field.sortDir = sortDataByFieldName.get(column.fieldNameOrPath).sortDirection; } rows.addFieldMetaData(field); @@ -334,12 +326,8 @@ public with sharing class ListViewCore extends ListViewAbstract { System.debug(LoggingLevel.FINE, 'FIELDS - ' + fields); - //get the object type we are working with - //String objType = HelperSchema.getObjectType(dataRows[0]); - for (String fieldName: fields) { - HelperSchema.FieldData fieldData = HelperSchema.getFieldData(dataRows[0], fieldName); String label = fieldData.label; String parentObjType = fieldData.parentObjType; @@ -358,16 +346,15 @@ public with sharing class ListViewCore extends ListViewAbstract { rows.addFieldMetaData(field); - Map newColumn = new Map(); - newColumn.put('type', fieldData.getType()); - newColumn.put('fieldNameOrPath', fieldName); - newColumn.put('hidden', false); - newColumn.put('label', label); + ListViewHelper.StandardColumn newColumn = new ListViewHelper.StandardColumn(); + newColumn.type = fieldData.getType(); + newColumn.fieldNameOrPath = fieldName; + newColumn.hidden = false; + newColumn.label = label; columns.add(newColumn); columnIndex++; - } } @@ -407,20 +394,12 @@ public with sharing class ListViewCore extends ListViewAbstract { //for each listview column columnIndex = 1; - for (Object column: columns) + for (ListViewHelper.StandardColumn column: columns) { - Map columnData = (Map) column; - - String fieldType = (String) columnData.get('type'); - String fieldName = (String) columnData.get('fieldNameOrPath'); - Boolean hidden = (Boolean) columnData.get('hidden'); - - System.debug(LoggingLevel.FINE, 'Field - ' + fieldName + ', Type - ' + fieldType); - //do not display hidden - if (hidden) continue; + if (column.hidden) continue; - HelperSchema.FieldData fieldData = HelperSchema.getFieldData(row, fieldName); + HelperSchema.FieldData fieldData = HelperSchema.getFieldData(row, column.fieldNameOrPath); System.debug(LoggingLevel.FINE, 'Field Data - ' + fieldData); @@ -432,7 +411,7 @@ public with sharing class ListViewCore extends ListViewAbstract { FieldWrapper field = new FieldWrapper(fieldData.label, fieldData.value, fieldDataType, - fieldName, + column.fieldNameOrPath, columnIndex, parentObjType); if (fieldData.objValueId != null) @@ -455,10 +434,10 @@ public with sharing class ListViewCore extends ListViewAbstract { field.createURL(); } - if (sortDataByFieldName.containsKey(fieldName)) + if (sortDataByFieldName.containsKey(column.fieldNameOrPath)) { - field.sortIndex = String.valueOf(sortDataByFieldName.get(fieldName).sortIndex); - field.sortDir = sortDataByFieldName.get(fieldName).sortDirection; + field.sortIndex = String.valueOf(sortDataByFieldName.get(column.fieldNameOrPath).sortIndex); + field.sortDir = sortDataByFieldName.get(column.fieldNameOrPath).sortDirection; } rowWrapper.addField(field); diff --git a/force-app/main/default/classes/ListViewCustomManual.cls b/force-app/main/default/classes/ListViewCustomManual.cls index e63514e..cc3e5a6 100644 --- a/force-app/main/default/classes/ListViewCustomManual.cls +++ b/force-app/main/default/classes/ListViewCustomManual.cls @@ -1,7 +1,7 @@ /** * @description : * @author : tom@ansleyllc.com - * @last modified on : 07-08-2024 + * @last modified on : 08-14-2024 * @last modified by : tom@ansleyllc.com * Modifications Log * Ver Date Author Modification @@ -55,6 +55,19 @@ public with sharing class ListViewCustomManual extends ListViewCustomManualAbstr params.add(paramsByName.get(ListViewHelper.PARAM_TOTAL_COLUMN_COLOR)); } + if (paramsByName.get(ListViewHelper.PARAM_CHILD_RECORD_COLOR) == null) + { + List_View_Config_Parameter__c param = new List_View_Config_Parameter__c(); + param.List_View_Config__c = config.Id; + param.Parameter_Name__c = ListViewHelper.PARAM_CHILD_RECORD_COLOR; + param.Parameter_Type__c = 'Color'; + param.Parameter_Value__c = ''; + param.Parameter_Label__c = 'Child Row Color'; + params.add(param); + } else { + params.add(paramsByName.get(ListViewHelper.PARAM_CHILD_RECORD_COLOR)); + } + if (paramsByName.get(ListViewHelper.PARAM_RETURN_SIZE) == null) { List_View_Config_Parameter__c param = new List_View_Config_Parameter__c(); @@ -205,7 +218,9 @@ public with sharing class ListViewCustomManual extends ListViewCustomManualAbstr Map columnData = (Map) column; String fieldName = (String) columnData.get('fieldNameOrPath'); - Boolean hidden = (Boolean) columnData.get('hidden'); + Boolean hidden = false; + if (columnData.get('hidden') != null) + hidden = (Boolean) columnData.get('hidden'); if (hidden == null || !hidden) { @@ -263,27 +278,25 @@ public with sharing class ListViewCustomManual extends ListViewCustomManualAbstr ListViewAbstract.RowsWrapper rows = new ListViewAbstract.RowsWrapper(listView, lvConfig); //get all columns from the list view - List allColumns = (List) JSON.deserializeUntyped(listView.Core_ListView_Columns__c); - List columns = new List(); + Map cols = ListViewHelper.getColumnsFromJSON(listView.Core_ListView_Columns__c); + List columns = new List(); Boolean addColumn = true; //set the COLUMN meta data into the rows wrapper Integer columnIndex = 1; - for (Object column: allColumns) + for (ListViewHelper.StandardColumn column: cols.values()) { - Map columnData = (Map) column; - - String fieldName = ((String) columnData.get('fieldNameOrPath')); - - FieldWrapper field = new FieldWrapper((String) columnData.get('label'), - (String) columnData.get('type'), - fieldName, + FieldWrapper field = new FieldWrapper(column.label, + column.type, + column.fieldNameOrPath, columnIndex); - if (sortDataByFieldName.containsKey(fieldName)) + field.setFunction(column.function); + + if (sortDataByFieldName.containsKey(column.fieldNameOrPath)) { - field.sortIndex = String.valueOf(sortDataByFieldName.get(fieldName).sortIndex); - field.sortDir = sortDataByFieldName.get(fieldName).sortDirection; + field.sortIndex = String.valueOf(sortDataByFieldName.get(column.fieldNameOrPath).sortIndex); + field.sortDir = sortDataByFieldName.get(column.fieldNameOrPath).sortDirection; } rows.addFieldMetaData(field); @@ -311,55 +324,57 @@ public with sharing class ListViewCustomManual extends ListViewCustomManualAbstr } } - - if (dataRows == null) return rows; - System.debug(LoggingLevel.FINE, 'SObject row count - ' + dataRows.size()); + //List fields = rows.getFieldMetaData(); //for each row of data for (SObject row: dataRows) { - System.debug(LoggingLevel.FINE, 'SObject row Id - ' + row.Id); + System.debug(LoggingLevel.FINE, 'SObject row - ' + row); Map popFields = row.getPopulatedFieldsAsMap(); //a map of all populated fields - Map normalFields = new Map(); //a map of all NON-CHILD fields Map childFields = new Map(); //a map of all CHILD fields + + //Get CHILD data for the row. These will be handled last for (String field: popFields.keySet()) { //if its a relationship field if (popFields.get(field) instanceof List) { childFields.put(field, popFields.get(field)); - } else { - normalFields.put(field, popFields.get(field)); } } + System.debug(LoggingLevel.DEBUG, 'Child Fields - ' + childFields.keySet().toString()); + RowWrapper rowWrapper = new RowWrapper((String.valueOf(row.get('Id')))); rowWrapper.isDeleted = false; rowWrapper.recordTypeId = HelperSchema.getRecordTypeId(row); - //this for loop ONLY GOES THROUGH NONE CHILD FIELDS for (FieldWrapper columnMetaData: rows.getFieldMetaData()) { - //if its either a CORE field. i.e. Opportunity.Name or a lookup field i.e. Opportunity.Account.Name - //has to be done this way as CORE fields could come in blank but we need to still identify them so that we can inline edit them - if (!columnMetaData.name.contains('.') || normalFields.keySet().contains(columnMetaData.name.substringBefore('.'))) + //specific logic to get column name as it could be Name (returns Name), Account.Name (returns Name), Opportunity.Account.Name (returns Opportunity.Account.Name) + String columnName = getColumnName(listView.Object_Name__c, columnMetaData); + + if (columnName != null) { - HelperSchema.FieldData fieldData = HelperSchema.getFieldData(row, columnMetaData.name); + HelperSchema.FieldData fieldData = HelperSchema.getFieldData(row, columnName); + System.debug(LoggingLevel.FINE, 'None Child Field Parent Obj Type - ' + fieldData.parentObjType + ', Field Data Type - ' + fieldData.getType() + ', Value - ' + fieldData.value); FieldWrapper field = new FieldWrapper(fieldData.label, - fieldData.value, - fieldData.getType(), - columnMetaData.name, - columnMetaData.columnIndex, - fieldData.parentObjType); + fieldData.value, + fieldData.getType(), + columnMetaData.name, + columnMetaData.columnIndex, + fieldData.parentObjType); field.setIsEditable(fieldData.isEditable); + field.setFunction(columnMetaData.getFunction()); + //also set the editable state into the column data if (rows.getFieldMetaData(field.getName()) != null) rows.getFieldMetaData(field.getName()).setIsEditable(fieldData.isEditable); @@ -373,8 +388,6 @@ public with sharing class ListViewCustomManual extends ListViewCustomManualAbstr field.setObjValueId(rowWrapper.getSalesforceId()); //if this is not a lookup then set its own wrappers SFDC Id } - System.debug(LoggingLevel.FINE, 'Field - ' + field); - if (ListViewAbstract.validNameColumnNames.contains(field.getName().substringAfterLast('.')) || ListViewAbstract.validNameColumnNames.contains(field.getName()) || (field.objValue != null //if we have a lookup object @@ -390,10 +403,10 @@ public with sharing class ListViewCustomManual extends ListViewCustomManualAbstr field.sortDir = sortDataByFieldName.get(columnMetaData.name).sortDirection; } - rowWrapper.addField(field); - + System.debug(LoggingLevel.FINE, 'Row Field - ' + field); + + rowWrapper.addField(field); } - } //now go through all CHILD FIELDS @@ -409,10 +422,18 @@ public with sharing class ListViewCustomManual extends ListViewCustomManualAbstr RowWrapper clonedWrapper = rowWrapper.cloneWrapper(deleteHeaderData); rows.addRow(clonedWrapper); - if (deleteHeaderData == false) + //HEADER ROW + if (deleteHeaderData == false) { deleteHeaderData = true; //once we have cloned the first wrapper with all fields this ensures all other rows have their header fields removed. - else + + //CHILD ROW + } else { clonedWrapper.setIsEditable(false); //no cloned wrappers can be editable after the header row. + if (lvConfig.getParameter(ListViewHelper.PARAM_CHILD_RECORD_COLOR) != null) + clonedWrapper.highlightColor = lvConfig.getParameter(ListViewHelper.PARAM_CHILD_RECORD_COLOR).getValue(); + else + clonedWrapper.highlightColor = '#eaeaea'; + } //this for loop ONLY GOES THROUGH CHILD FIELDS for (FieldWrapper columnMetaData: rows.getFieldMetaData()) @@ -433,12 +454,11 @@ public with sharing class ListViewCustomManual extends ListViewCustomManualAbstr columnMetaData.columnIndex, fieldData.parentObjType); field.longName = columnMetaData.name; + field.setFunction(columnMetaData.getFunction()); if (fieldData.objValueId != null) field.setObjValueId(fieldData.objValueId); - System.debug(LoggingLevel.FINE, 'Field - ' + field); - if ((ListViewAbstract.validNameColumnNames.contains(fieldData.name) //if we have the name of the object || fieldData.parentObjType == 'Task' && fieldData.name == 'Subject') && fieldData.objValue != null //if we have field data. @@ -540,6 +560,7 @@ public with sharing class ListViewCustomManual extends ListViewCustomManualAbstr fieldWrapper.columnIndex, ''); totalField.setIsTotalsRow(true); + totalField.setFunction(fieldWrapper.getFunction()); System.debug(LoggingLevel.DEBUG, 'Added total field - ' + totalField); @@ -589,4 +610,39 @@ public with sharing class ListViewCustomManual extends ListViewCustomManualAbstr return rows; } + /** + * @description Method to find the column name that is used to find the data in the SObject row. This is needed + * because the column name could be "weird" based on what is provided by the user. For example + * Account.Name is valid as its the same as Name + * Contacts.Name is invalid as its a child column name + * Account.Name is valid as its a lookup on the contact to account + * @author tom@ansleyllc.com | 08-14-2024 + * @param objectName + * @param columnMetaData + * @return String + **/ + private static String getColumnName(String objectName, FieldWrapper columnMetaData) + { + //if no lookups (Opportunity.Name) or explicit names (Account.Name for account object) then return the name + if (!columnMetaData.name.contains('.')) + return columnMetaData.name; + + List fieldTokens = columnMetaData.name.split('\\.'); + + //if explicit name then remove name of object and return + if (fieldTokens[0] == objectName) + return columnMetaData.name.substringAfter(objectName + '.'); + + Schema.DescribeSObjectResult objSchema = HelperSchema.getObjectSchema(objectName); + List objRels = objSchema.getChildRelationships(); + for (Schema.ChildRelationship objRel: objRels) + { + if (objRel.getRelationshipName() == fieldTokens[0]) + { + return null; + } + } + + return columnMetaData.name; + } } \ No newline at end of file diff --git a/force-app/main/default/classes/ListViewCustomToolingAbstract.cls b/force-app/main/default/classes/ListViewCustomToolingAbstract.cls index 86b72af..72573f0 100644 --- a/force-app/main/default/classes/ListViewCustomToolingAbstract.cls +++ b/force-app/main/default/classes/ListViewCustomToolingAbstract.cls @@ -1,7 +1,7 @@ /** * @description : * @author : tom@ansleyllc.com - * @last modified on : 04-28-2024 + * @last modified on : 08-14-2024 * @last modified by : tom@ansleyllc.com * Modifications Log * Ver Date Author Modification @@ -73,7 +73,7 @@ public with sharing abstract class ListViewCustomToolingAbstract extends ListVie param.Parameter_Label__c = ListViewHelper.PARAM_TOTAL_COLUMNS; params.add(param); } else { - params.add(paramsByName.get('TotalColumns')); + params.add(paramsByName.get(ListViewHelper.PARAM_TOTAL_COLUMNS)); } if (paramsByName.get(ListViewHelper.PARAM_TOTAL_COLUMN_COLOR) == null) @@ -86,7 +86,7 @@ public with sharing abstract class ListViewCustomToolingAbstract extends ListVie param.Parameter_Label__c = ListViewHelper.PARAM_TOTAL_COLUMN_COLOR; params.add(param); } else { - params.add(paramsByName.get('TotalColumnsColor')); + params.add(paramsByName.get(ListViewHelper.PARAM_TOTAL_COLUMN_COLOR)); } if (paramsByName.get(ListViewHelper.PARAM_RETURN_SIZE) == null) diff --git a/force-app/main/default/classes/ListViewCustomToolingQuery.cls b/force-app/main/default/classes/ListViewCustomToolingQuery.cls index 1ebc6a7..cea6106 100644 --- a/force-app/main/default/classes/ListViewCustomToolingQuery.cls +++ b/force-app/main/default/classes/ListViewCustomToolingQuery.cls @@ -1,7 +1,7 @@ /** * @description : * @author : tom@ansleyllc.com - * @last modified on : 04-28-2024 + * @last modified on : 08-14-2024 * @last modified by : tom@ansleyllc.com * Modifications Log * Ver Date Author Modification @@ -143,6 +143,9 @@ public with sharing class ListViewCustomToolingQuery extends ListViewCustomTooli fieldName, columnIndex); + if (columnData.containsKey('function')) + field.setFunction((String) columnData.get('function')); + if (sortDataByFieldName.containsKey(fieldName)) { field.sortIndex = String.valueOf(sortDataByFieldName.get(fieldName).sortIndex); @@ -170,9 +173,10 @@ public with sharing class ListViewCustomToolingQuery extends ListViewCustomTooli { Map columnData = (Map) column; - String fieldType = (String) columnData.get('type'); - String fieldName = (String) columnData.get('fieldNameOrPath'); - String label = (String) columnData.get('label'); + String fieldType = (String) columnData.get('type'); + String fieldName = (String) columnData.get('fieldNameOrPath'); + String label = (String) columnData.get('label'); + String function = (String) columnData.get('function'); System.debug(LoggingLevel.FINE, 'Field - ' + fieldName + ', Type - ' + fieldType); @@ -193,6 +197,7 @@ public with sharing class ListViewCustomToolingQuery extends ListViewCustomTooli columnIndex, fieldData.parentObjType); field.objValueId = fieldData.objValueId; + field.setFunction(function); if (field.getName() == 'Name' || field.getName() == 'Id' || field.getType() == ListViewHelper.TYPE_LOOKUP) { field.createURL(); @@ -214,22 +219,22 @@ public with sharing class ListViewCustomToolingQuery extends ListViewCustomTooli //if totals have been configured if (lvConfig != null - && lvConfig.getParameter('TotalColumns') != null - && !String.isEmpty(lvConfig.getParameter('TotalColumns').getValue())) + && lvConfig.getParameter(ListViewHelper.PARAM_TOTAL_COLUMNS) != null + && !String.isEmpty(lvConfig.getParameter(ListViewHelper.PARAM_TOTAL_COLUMNS).getValue())) { rows.hasTotalsRow = true; System.debug(LoggingLevel.FINE, 'Starting total column calculations!'); - String totalColumns = lvConfig.getParameter('TotalColumns').getValue(); + String totalColumns = lvConfig.getParameter(ListViewHelper.PARAM_TOTAL_COLUMNS).getValue(); System.debug(LoggingLevel.FINE, 'TotalColumns - ' + totalColumns); RowWrapper totalWrapper = new RowWrapper('000000000000000000'); totalWrapper.highlight = true; - if (lvConfig.getParameter('TotalColumnsColor') != null) - totalWrapper.highlightColor = lvConfig.getParameter('TotalColumnsColor').getValue(); + if (lvConfig.getParameter(ListViewHelper.PARAM_TOTAL_COLUMN_COLOR) != null) + totalWrapper.highlightColor = lvConfig.getParameter(ListViewHelper.PARAM_TOTAL_COLUMN_COLOR).getValue(); else totalWrapper.highlightColor = '#FFFFFF'; totalWrapper.isTotals = true; @@ -270,6 +275,7 @@ public with sharing class ListViewCustomToolingQuery extends ListViewCustomTooli fieldWrapper.columnIndex, ''); totalField.setIsTotalsRow(true); + totalField.setFunction(fieldWrapper.getFunction()); System.debug(LoggingLevel.FINE, 'Added total field - ' + totalField); diff --git a/force-app/main/default/classes/ListViewCustomToolingQueryTest.cls b/force-app/main/default/classes/ListViewCustomToolingQueryTest.cls index 0868522..8ce9aab 100644 --- a/force-app/main/default/classes/ListViewCustomToolingQueryTest.cls +++ b/force-app/main/default/classes/ListViewCustomToolingQueryTest.cls @@ -1,7 +1,7 @@ /** * @description : * @author : tom@ansleyllc.com - * @last modified on : 01-25-2022 + * @last modified on : 08-14-2024 * @last modified by : tom@ansleyllc.com * Modifications Log * Ver Date Author Modification @@ -25,7 +25,7 @@ global with sharing class ListViewCustomToolingQueryTest { List_View_Config__c config = [SELECT Id FROM List_View_Config__c WHERE Primary_Key__c = 'ApexLog:ApexLogs']; List_View_Config_Parameter__c param = new List_View_Config_Parameter__c(); - param.Parameter_Name__c = 'TotalColumns'; + param.Parameter_Name__c = ListViewHelper.PARAM_TOTAL_COLUMNS; param.Parameter_Type__c = ListViewHelper.TYPE_STRING; param.Parameter_Value__c = 'DurationMilliseconds'; param.List_View_Config__c = config.Id; diff --git a/force-app/main/default/classes/ListViewFunction.cls b/force-app/main/default/classes/ListViewFunction.cls new file mode 100644 index 0000000..2a78be6 --- /dev/null +++ b/force-app/main/default/classes/ListViewFunction.cls @@ -0,0 +1,5 @@ +global abstract with sharing class ListViewFunction { + + global abstract Object get(Object value); + +} \ No newline at end of file diff --git a/force-app/main/default/classes/ListViewFunction.cls-meta.xml b/force-app/main/default/classes/ListViewFunction.cls-meta.xml new file mode 100644 index 0000000..651b172 --- /dev/null +++ b/force-app/main/default/classes/ListViewFunction.cls-meta.xml @@ -0,0 +1,5 @@ + + + 61.0 + Active + diff --git a/force-app/main/default/classes/ListViewFunction2.cls b/force-app/main/default/classes/ListViewFunction2.cls new file mode 100644 index 0000000..2778221 --- /dev/null +++ b/force-app/main/default/classes/ListViewFunction2.cls @@ -0,0 +1,5 @@ +global abstract with sharing class ListViewFunction2 { + + global abstract Object get(Object value, Map context); + +} \ No newline at end of file diff --git a/force-app/main/default/classes/ListViewFunction2.cls-meta.xml b/force-app/main/default/classes/ListViewFunction2.cls-meta.xml new file mode 100644 index 0000000..651b172 --- /dev/null +++ b/force-app/main/default/classes/ListViewFunction2.cls-meta.xml @@ -0,0 +1,5 @@ + + + 61.0 + Active + diff --git a/force-app/main/default/classes/ListViewHelper.cls b/force-app/main/default/classes/ListViewHelper.cls index 0f3bc97..521e618 100644 --- a/force-app/main/default/classes/ListViewHelper.cls +++ b/force-app/main/default/classes/ListViewHelper.cls @@ -1,6 +1,6 @@ /** * @author : tom@ansleyllc.com - * @last modified on : 08-07-2024 + * @last modified on : 08-14-2024 * @last modified by : tom@ansleyllc.com * Modifications Log * Ver Date Author Modification @@ -24,6 +24,8 @@ global with sharing class ListViewHelper { public static String LATEST_QUERY = ''; + private static Boolean hasEnterprise = null; + private static Map functions = new Map(); public static final Id coreRTId = Schema.SObjectType.List_View__c.getRecordTypeInfosByDeveloperName().get('Core').getRecordTypeId(); public static final Id customRTId = Schema.SObjectType.List_View__c.getRecordTypeInfosByDeveloperName().get('Custom').getRecordTypeId(); @@ -77,6 +79,7 @@ global with sharing class ListViewHelper public static final String PARAM_REFRESH_RATE = 'RefreshRate'; public static final String PARAM_SINGLE_CLICK_REFRESH = 'SingleClickAutoDataRefresh'; public static final String PARAM_TOTAL_COLUMN_COLOR = 'TotalColumnsColor'; + public static final String PARAM_CHILD_RECORD_COLOR = 'ChildRowColor'; public static final String PARAM_EXPORT_HEADER_TYPE = 'ExportHeaderType'; public static final String PARAM_ALL_ROWS = 'AllRows'; public static final String PARAM_PDF_ORIENT_PORTRAIT = 'PDFOrientationPortrait'; @@ -100,6 +103,27 @@ global with sharing class ListViewHelper public static Integer offset = -1; //used to hold the offset that is set when performing a query. + public static Object processFunction(String apexClass, Object value, Map context) + { + //if (!ListViewHelper.hasEnterprise()) return value; + + ListViewFunction2 function; + if (ListViewHelper.functions.containsKey(apexClass)) { //get from cache if possible + function = ListViewHelper.functions.get(apexClass); + } else { + try { + Type t = HelperSchema.getClassType(apexClass); + function = (ListViewFunction2) t.newInstance(); + } catch (Exception e) { + System.debug(LoggingLevel.ERROR, e.getMessage() + ' - ' + e.getStackTraceString()); + throw new ListViewException('The ' + apexClass + ' could not be found when trying to process a column function.'); + } + ListViewHelper.functions.put(apexClass, function); + } + + return function.get(value, context); + } + public static String getPrimaryKey(List_View__c listView) { return listView.Object_Name__c.replace(' ', '_') + ':' + listView.API_Name__c; @@ -112,12 +136,16 @@ global with sharing class ListViewHelper public static Boolean hasEnterprise() { - if (HelperSchema.isPackageInstalled(HelperSchema.SLVE_NAMESPACE)) - return true; - else if (HelperSchema.isObject('simpli_lv_ent__List_Views_Org_Connection__c')) //a hack for the enterprise org as its not installed - return true; - else - return false; + if (ListViewHelper.hasEnterprise == null) + { + if (HelperSchema.isPackageInstalled(HelperSchema.SLVE_NAMESPACE)) + ListViewHelper.hasEnterprise = true; + else if (HelperSchema.isObject('simpli_lv_ent__List_Views_Org_Connection__c')) //a hack for the enterprise org as its not installed + ListViewHelper.hasEnterprise = true; + else + ListViewHelper.hasEnterprise = false; + } + return ListViewHelper.hasEnterprise; } /* @@ -1316,9 +1344,9 @@ global with sharing class ListViewHelper limitStr = ' LIMIT 1'; //if we have a list view limit then use it - } else if (lvConfigParams.get('ReturnSize') != null) + } else if (lvConfigParams.get(ListViewHelper.PARAM_RETURN_SIZE) != null) { - Integer returnSize = Integer.valueOf(lvConfigParams.get('ReturnSize')); + Integer returnSize = Integer.valueOf(lvConfigParams.get(ListViewHelper.PARAM_RETURN_SIZE)); limitStr = ' LIMIT ' + returnSize; if (offset == null) offset = -1; @@ -1366,10 +1394,10 @@ global with sharing class ListViewHelper //------------------------------------------------------ String allRowsStr = ''; if (offset == -1 //Cannot use ALL ROWS with OFFSET - && lvConfigParams.get('AllRows') != null //if we have AllRows variable - && lvConfigParams.get('AllRows') == ListViewHelper.TTRUE //if AllRows = true - && lvConfigParams.get('ReturnSize') != null //if we have ReturnSize variable - && (Integer.valueOf(lvConfigParams.get('ReturnSize')) < 750)) //if ReturnSize < 750 + && lvConfigParams.get(ListViewHelper.PARAM_ALL_ROWS) != null //if we have AllRows variable + && lvConfigParams.get(ListViewHelper.PARAM_ALL_ROWS) == ListViewHelper.TTRUE //if AllRows = true + && lvConfigParams.get(ListViewHelper.PARAM_RETURN_SIZE) != null //if we have ReturnSize variable + && (Integer.valueOf(lvConfigParams.get(ListViewHelper.PARAM_RETURN_SIZE)) < 750)) //if ReturnSize < 750 { allRowsStr += ' ALL ROWS'; } @@ -1585,9 +1613,9 @@ global with sharing class ListViewHelper limitStr = ' LIMIT 1'; //if we have a list view limit then use it - } else if (lvConfigParams.get('ReturnSize') != null) + } else if (lvConfigParams.get(ListViewHelper.PARAM_RETURN_SIZE) != null) { - Integer returnSize = Integer.valueOf(lvConfigParams.get('ReturnSize')); + Integer returnSize = Integer.valueOf(lvConfigParams.get(ListViewHelper.PARAM_RETURN_SIZE)); limitStr = ' LIMIT ' + returnSize; if (offset == null) offset = -1; @@ -1633,10 +1661,10 @@ global with sharing class ListViewHelper //------------------------------------------------------ String allRowsStr = ''; if (offset == -1 //Cannot use ALL ROWS with OFFSET - && lvConfigParams.get('AllRows') != null //if we have AllRows variable - && lvConfigParams.get('AllRows') == ListViewHelper.TTRUE //if AllRows = true - && lvConfigParams.get('ReturnSize') != null //if we have ReturnSize variable - && (Integer.valueOf(lvConfigParams.get('ReturnSize')) < 750)) //if ReturnSize < 750 + && lvConfigParams.get(ListViewHelper.PARAM_ALL_ROWS) != null //if we have AllRows variable + && lvConfigParams.get(ListViewHelper.PARAM_ALL_ROWS) == ListViewHelper.TTRUE //if AllRows = true + && lvConfigParams.get(ListViewHelper.PARAM_RETURN_SIZE) != null //if we have ReturnSize variable + && (Integer.valueOf(lvConfigParams.get(ListViewHelper.PARAM_RETURN_SIZE)) < 750)) //if ReturnSize < 750 { allRowsStr += ' ALL ROWS'; } @@ -1811,9 +1839,9 @@ global with sharing class ListViewHelper if (soql.containsIgnoreCase(' LIMIT ')) limitStr = soql.substring(soql.lastIndexOf('LIMIT')); - if (lvConfigParams.get('ReturnSize') != null) + if (lvConfigParams.get(ListViewHelper.PARAM_RETURN_SIZE) != null) { - Integer returnSize = Integer.valueOf(lvConfigParams.get('ReturnSize')); + Integer returnSize = Integer.valueOf(lvConfigParams.get(ListViewHelper.PARAM_RETURN_SIZE)); limitStr = ' LIMIT ' + returnSize; if (offset == null) offset = -1; @@ -2505,6 +2533,14 @@ global with sharing class ListViewHelper param.Parameter_Label__c = 'Total Row Color'; params.add(param); + param = new List_View_Config_Parameter__c(); + param.Parameter_Name__c = ListViewHelper.PARAM_CHILD_RECORD_COLOR; + param.Parameter_Type__c = 'Color'; + param.Parameter_Value__c = '#eaeaea'; + param.List_View_Config__c = config.Id; + param.Parameter_Label__c = 'Child Row Color'; + params.add(param); + //--------------------------------------- // //--------------------------------------- @@ -2532,6 +2568,14 @@ global with sharing class ListViewHelper param.Parameter_Label__c = 'Total Row Color'; params.add(param); + param = new List_View_Config_Parameter__c(); + param.Parameter_Name__c = ListViewHelper.PARAM_CHILD_RECORD_COLOR; + param.Parameter_Type__c = 'Color'; + param.Parameter_Value__c = '#eaeaea'; + param.List_View_Config__c = config2.Id; + param.Parameter_Label__c = 'Child Row Color'; + params.add(param); + HelperDatabase.insertRecords(params); List conditions = new List(); @@ -3589,7 +3633,18 @@ global with sharing class ListViewHelper return visible; } + public static Map getColumnsFromJSON(String columnJSON) + { + //get all columns from the list view + List allColumns = (List) JSON.deserialize(columnJSON, List.class); + + Map columns = new Map(); + for (StandardColumn column: allColumns) + columns.put(column.fieldNameOrPath, column); + return columns; + + } @@ -3601,7 +3656,13 @@ global with sharing class ListViewHelper //================================================================================================================ // INNER CLASSES //================================================================================================================ - + public class StandardColumn { + public String fieldNameOrPath; + public Boolean hidden; + public String type; + public String label; + public String function; + } public class ColumnSortData implements Comparable { diff --git a/force-app/main/default/classes/ListViewTriggerHandler.cls b/force-app/main/default/classes/ListViewTriggerHandler.cls index da8ffa3..fb0a839 100644 --- a/force-app/main/default/classes/ListViewTriggerHandler.cls +++ b/force-app/main/default/classes/ListViewTriggerHandler.cls @@ -1,7 +1,7 @@ /** * @description : * @author : tom@ansleyllc.com - * @last modified on : 01-11-2022 + * @last modified on : 08-09-2024 * @last modified by : tom@ansleyllc.com * Modifications Log * Ver Date Author Modification @@ -9,51 +9,56 @@ **/ public with sharing class ListViewTriggerHandler { - /* - * Method which takes a set of List_View__c records that are being inserted - * and executes logic BEFORE the records have been INSERTED. - * @param newRecords the list of List_View__c records - */ - public static void onBeforeInsert(List newRecords) - { - ListViewBL.setFields(newRecords); + /* + * Method which takes a set of List_View__c records that are being inserted + * and executes logic BEFORE the records have been INSERTED. + * @param newRecords the list of List_View__c records + */ + public static void onBeforeInsert(List newRecords) + { + ListViewBL.setFields(newRecords); - ListViewBL.validate(newRecords); //validate at end so that we can set all fields before hand. - } - - /* - * Method which takes a set of List_View__c records that are being inserted - * and executes logic AFTER the records have been INSERTED. - * @param newRecords the map of List_View__c records and their new id's - */ - public static void onAfterInsert(Map newRecords) - { - ListViewBL.updateCoreListViewId(newRecords); - - ListViewBL.updateListViewConfigs(newRecords); - } - - /* - * Method which takes a set of List_View__c records that are being updated - * and executes logic BEFORE the records have been UPDATED. - * @param newRecords the map of new List_View__c records - * @param oldRecords the map of old List_View__c records - */ - public static void onBeforeUpdate(Map newRecords, Map oldRecords) - { - ListViewBL.updateFields(newRecords, oldRecords); + ListViewBL.validate(newRecords); //validate at end so that we can set all fields before hand. + } + + /* + * Method which takes a set of List_View__c records that are being inserted + * and executes logic AFTER the records have been INSERTED. + * @param newRecords the map of List_View__c records and their new id's + */ + public static void onAfterInsert(Map newRecords) + { + ListViewBL.updateCoreListViewId(newRecords); - ListViewBL.validate(newRecords.values()); //validate at end so that we can set all fields before hand. - } + ListViewBL.updateListViewConfigs(newRecords); + } - /* - * Method which takes a set of List_View__c records that are being updated - * and executes logic AFTER the records have been UPDATED. - * @param newRecords the map of new List_View__c records - * @param oldRecords the map of old List_View__c records - */ - public static void onAfterUpdate(Map newRecords, Map oldRecords) - { - ListViewBL.updateListViewConfigs(newRecords); - } - } \ No newline at end of file + /* + * Method which takes a set of List_View__c records that are being updated + * and executes logic BEFORE the records have been UPDATED. + * @param newRecords the map of new List_View__c records + * @param oldRecords the map of old List_View__c records + */ + public static void onBeforeUpdate(Map newRecords, Map oldRecords) + { + ListViewBL.updateFields(newRecords, oldRecords); + + ListViewBL.validate(newRecords.values()); //validate at end so that we can set all fields before hand. + } + + /* + * Method which takes a set of List_View__c records that are being updated + * and executes logic AFTER the records have been UPDATED. + * @param newRecords the map of new List_View__c records + * @param oldRecords the map of old List_View__c records + */ + public static void onAfterUpdate(Map newRecords, Map oldRecords) + { + ListViewBL.updateListViewConfigs(newRecords); + } + + public static void onAfterDelete(Map oldRecords) + { + ListViewBL.deleteListViewConfigs(oldRecords); + } +} \ No newline at end of file diff --git a/force-app/main/default/classes/ListViewsPicklistController.cls-meta.xml b/force-app/main/default/classes/ListViewsPicklistController.cls-meta.xml index 7d5f9e8..651b172 100644 --- a/force-app/main/default/classes/ListViewsPicklistController.cls-meta.xml +++ b/force-app/main/default/classes/ListViewsPicklistController.cls-meta.xml @@ -2,4 +2,4 @@ 61.0 Active - \ No newline at end of file + diff --git a/force-app/main/default/classes/ScriptInstall.cls b/force-app/main/default/classes/ScriptInstall.cls index 7f3ee09..a53ebc2 100644 --- a/force-app/main/default/classes/ScriptInstall.cls +++ b/force-app/main/default/classes/ScriptInstall.cls @@ -1,74 +1,74 @@ -/** - * @description : - * @author : tom@ansleyllc.com - * @last modified on : 08-01-2024 - * @last modified by : tom@ansleyllc.com - * Modifications Log - * Ver Date Author Modification - * 1.0 10-10-2020 tom@ansleyllc.com Initial Version - * 2.0 07-22-2021 tom@ansleyllc.com Added inline-editing and is initialized org wide config updates, Added Edit All action update - * 3.0 08-02-2021 tom@ansleyllc.com Added display text search org wide config updates - * 4.0 08-16-2021 tom@ansleyllc.com Added permission check for each action before display - * 5.0 08-20-2021 tom@ansleyllc.com Updated strings to static final variables, added hyperlink action config -**/ -global with sharing class ScriptInstall implements InstallHandler { - - /* - ScriptInstall install = new ScriptInstall(); - install.install(null); - - ScriptInstall install = new ScriptInstall(); - install.upgrade(null); - - */ - global void onInstall(InstallContext context) - { - try { - if(context == null || context.previousVersion() == null) - { - install(context); - } - - if(context == null || context.isUpgrade()) - { - upgrade(context); - } - - } catch (Exception e) - { - String message = 'Exception during ScriptInstall.onInstall() ' + ListViewException.getExtendedString(e); - try { - ListViewErrorHelper.createFutureUsageError(message); - } catch (Exception ex) { } - HelperEmail.sendEmail('tom@ansleyllc.com', message, 'Failure On Install!'); - throw e; - } - } - - public void install(InstallContext context) - { - //insert core configuration - ListViewHelper.insertCoreConfiguration(); - - String schedStr = HelperDate.getBatchJobScheduleString(HelperDate.MINUTE, 2); - - //run the batch job once to have all list views brought in - if (!Test.isRunningTest()) - { - //CANNOT GET EITHER OF THESE TO WORK CORRECTLY! LISTVIEW OBJECT DOES NOT SEEM TO BE VISIBLE ON INSTALL SCRIPT RUNNING - //System.schedule('ListView Initial Load - ' + schedStr, schedStr, new ListViewProcessBatch()); - //Database.executeBatch(new ListViewProcessBatch()); - } - - HelperEmail.sendInstallEmail('Installed'); - - } - - public void upgrade(InstallContext context) - { - CacheHelper.clearAllCache(); - - //send email notifying of the upgrade - HelperEmail.sendInstallEmail('Upgraded'); - } +/** + * @description : + * @author : tom@ansleyllc.com + * @last modified on : 08-01-2024 + * @last modified by : tom@ansleyllc.com + * Modifications Log + * Ver Date Author Modification + * 1.0 10-10-2020 tom@ansleyllc.com Initial Version + * 2.0 07-22-2021 tom@ansleyllc.com Added inline-editing and is initialized org wide config updates, Added Edit All action update + * 3.0 08-02-2021 tom@ansleyllc.com Added display text search org wide config updates + * 4.0 08-16-2021 tom@ansleyllc.com Added permission check for each action before display + * 5.0 08-20-2021 tom@ansleyllc.com Updated strings to static final variables, added hyperlink action config +**/ +global with sharing class ScriptInstall implements InstallHandler { + + /* + ScriptInstall install = new ScriptInstall(); + install.install(null); + + ScriptInstall install = new ScriptInstall(); + install.upgrade(null); + + */ + global void onInstall(InstallContext context) + { + try { + if(context == null || context.previousVersion() == null) + { + install(context); + } + + if(context == null || context.isUpgrade()) + { + upgrade(context); + } + + } catch (Exception e) + { + String message = 'Exception during ScriptInstall.onInstall() ' + ListViewException.getExtendedString(e); + try { + ListViewErrorHelper.createFutureUsageError(message); + } catch (Exception ex) { } + HelperEmail.sendEmail('tom@ansleyllc.com', message, 'Failure On Install!'); + throw e; + } + } + + public void install(InstallContext context) + { + //insert core configuration + ListViewHelper.insertCoreConfiguration(); + + String schedStr = HelperDate.getBatchJobScheduleString(HelperDate.MINUTE, 2); + + //run the batch job once to have all list views brought in + if (!Test.isRunningTest()) + { + //CANNOT GET EITHER OF THESE TO WORK CORRECTLY! LISTVIEW OBJECT DOES NOT SEEM TO BE VISIBLE ON INSTALL SCRIPT RUNNING + //System.schedule('ListView Initial Load - ' + schedStr, schedStr, new ListViewProcessBatch()); + //Database.executeBatch(new ListViewProcessBatch()); + } + + HelperEmail.sendInstallEmail('Installed'); + + } + + public void upgrade(InstallContext context) + { + CacheHelper.clearAllCache(); + + //send email notifying of the upgrade + HelperEmail.sendInstallEmail('Upgraded'); + } } \ No newline at end of file diff --git a/force-app/main/default/lwc/simpliUIListViews/simpliUIListViews.css b/force-app/main/default/lwc/simpliUIListViews/simpliUIListViews.css index bca6c5e..624c5d5 100644 --- a/force-app/main/default/lwc/simpliUIListViews/simpliUIListViews.css +++ b/force-app/main/default/lwc/simpliUIListViews/simpliUIListViews.css @@ -40,9 +40,118 @@ .tablehorizontalscroll { width: max-content; +} + +.tablenohorizontalscroll { + width: auto; min-width: 100%; } +.relatedlistscrollwrapper { + width: 100%; + min-height: 80px; + max-height: 300px; + border-bottom: 1px solid #ddd; + border-left: 1px solid #ddd; + overflow: auto; +} + +.relatedlistdisplayallwrapper { + width: 100%; + min-height: 80px; + border-bottom: 1px solid #ddd; + border-left: 1px solid #ddd; + overflow: auto; +} + +.splitviewscrollwrapper { + width: 100%; + min-height: 80px; + max-height: 67vh; + border-bottom: 1px solid #ddd; + border-left: 1px solid #ddd; + overflow: auto; +} + +.splitviewobjectlistdropdown { + width: 100%; + padding-bottom: 5px; + padding-left: 5px; + padding-right: 5px; +} + +.regularobjectlistdropdown { + min-width: 20%; + padding-left: 5px; + padding-right: 5px; +} + +.splitviewlistviewdropdown { + width: 70%; + padding-left: 5px; +} + +.regularlistviewdropdown { + min-width: 20%; +} + +thead th { + position: sticky; + top: 0; +} + +th { + z-index: 1; +} + +.slds-card__header { + padding-top: 0px; +} + +.mainwrapper { + position: relative; +} + +.button-short { + line-height: inherit; +} + +.toastMessage.forceActionsText { + white-space: pre-line !important; +} + +.dataSpinnerHolder { + position: relative; + display: inline-block; + margin-left: 30px; + vertical-align: middle; + white-space: nowrap; +} + +.pageSpinnerHolder { + position: absolute; + min-height: 180px; + min-width: 180px; + z-index: 100; +} + +.applistscrollwrapper { + width: 100%; + overflow-y: auto; + max-height: 60vh; +} + +.virtualapplistscrollwrapper { + width: 100%; + overflow-y: auto; + border-top: red solid 4px; + max-height: 60vh; +} + +.tablehorizontalscroll { + width: max-content; +} + .tablenohorizontalscroll { width: auto; min-width: 100%; diff --git a/force-app/main/default/lwc/simpliUIListViewsFlowFrame/simpliUIListViewsFlowFrame.js b/force-app/main/default/lwc/simpliUIListViewsFlowFrame/simpliUIListViewsFlowFrame.js index 7b6312b..a16a3da 100644 --- a/force-app/main/default/lwc/simpliUIListViewsFlowFrame/simpliUIListViewsFlowFrame.js +++ b/force-app/main/default/lwc/simpliUIListViewsFlowFrame/simpliUIListViewsFlowFrame.js @@ -1,5 +1,6 @@ /* eslint-disable no-console */ import {LightningElement, api, track} from 'lwc'; + import * as SLVHelper from 'c/simpliUIListViewsHelper'; import getListViewAction from '@salesforce/apex/ListViewController.getListViewAction';