-
-
Notifications
You must be signed in to change notification settings - Fork 232
Expand file tree
/
Copy pathRelatedLogEntriesController.cls
More file actions
303 lines (260 loc) · 11.7 KB
/
RelatedLogEntriesController.cls
File metadata and controls
303 lines (260 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
//------------------------------------------------------------------------------------------------//
// This file is part of the Nebula Logger project, released under the MIT License. //
// See LICENSE file or go to https://github.com/jongpie/NebulaLogger for full license details. //
//------------------------------------------------------------------------------------------------//
/**
* @group Log Management
* @description Controller class for the lightning web component `related-log-entries`
*/
@SuppressWarnings('PMD.ExcessivePublicCount')
public with sharing class RelatedLogEntriesController {
private static final Schema.SObjectType LOG_SOBJECT_TYPE = Schema.Log__c.SObjectType;
private static final Schema.SObjectType LOG_ENTRY_SOBJECT_TYPE = Schema.LogEntry__c.SObjectType;
private static final String DEFAULT_SORT_FIELD_NAME = String.valueOf(Schema.LogEntry__c.Timestamp__c);
private static final String DEFAULT_SORT_DIRECTION = 'DESC';
/**
* @description Used by the component relatedLogEntries to get log entries for a particular record (based on record ID)
* @param recordId The recordId to search for amidst all Log Entry fields
* @param fieldSetName The API/developer name of the field set
* @param rowLimit The max number of rows to query
* @param sortByFieldName The field to sort by
* @param sortDirection The direction to sort by (asc or desc)
* @param search An optional search term to filter by
* @param shouldEnableStrictSearch When true, used to filter returned LogEntry__c records where RecordId__c == recordId
* @return The instance of LogEntryQueryResult, containing matching records and metadata
*/
@SuppressWarnings('PMD.ExcessiveParameterList')
@AuraEnabled(cacheable=true)
public static LogEntryQueryResult getQueryResult(
Id recordId,
String fieldSetName,
Integer rowLimit,
String sortByFieldName,
String sortDirection,
String search,
Boolean shouldEnableStrictSearch
) {
FieldSetMetadata fieldSetMetdata = new FieldSetMetadata(LOG_ENTRY_SOBJECT_TYPE, fieldSetName);
String fieldsClause = getFieldsClause(fieldSetMetdata.fields);
String orderByClause = getOrderByClause(sortByFieldName, sortDirection);
List<LogEntry__c> records = search(recordId, search, fieldsClause, orderByClause, rowLimit, shouldEnableStrictSearch);
return new LogEntryQueryResult(fieldSetMetdata, records);
}
@SuppressWarnings('PMD.ExcessiveParameterList')
private static List<LogEntry__c> search(
Id recordId,
String searchTerm,
String fieldsClause,
String orderByClause,
Integer rowLimit,
Boolean shouldEnableStrictSearch
) {
// SOSL can search for a 15-character ID, but for some reason, using the 18-character version returns no results (╯‵□′)╯︵┻━┻
String fullSearchExpression = '*' + String.escapeSingleQuotes(recordId.to15()) + '*';
if (String.isNotBlank(searchTerm)) {
fullSearchExpression += ' AND *' + String.escapeSingleQuotes(searchTerm) + '*';
}
fullSearchExpression = '\'' + fullSearchExpression + '\'';
List<Object> searchTextReplacements = new List<Object>{
fullSearchExpression,
String.valueOf(Schema.LogEntry__c.SObjectType),
fieldsClause,
shouldEnableStrictSearch ? 'WHERE RecordId__c = :recordId' : '',
orderByClause,
rowLimit
};
String logEntrySearch = String.format('FIND {0} IN ALL FIELDS RETURNING {1} ({2} {3} ORDER BY {4} LIMIT {5})', searchTextReplacements);
List<LogEntry__c> matches = (List<LogEntry__c>) System.Search.query(logEntrySearch).get(0);
// Somewhat redundant security check for FLS (but extra security > less security)
return System.Security.stripInaccessible(System.AccessType.READABLE, matches).getRecords();
}
private static String getFieldsClause(List<FieldMetadata> fields) {
// Make sure these fields are always included, even if they're not in the fieldset, in case the LWC needs them
Set<String> fieldNames = new Set<String>{
LogEntry__c.DatabaseResultJson__c.toString(),
LogEntry__c.RecordId__c.toString(),
LogEntry__c.RecordJson__c.toString()
};
for (FieldMetadata fieldMetadata : fields) {
fieldNames.add(fieldMetadata.type == 'picklist' ? ('toLabel(' + fieldMetadata.fieldName + ')') : fieldMetadata.fieldName);
// For lookups, also include the display name of parent object
if (fieldMetadata.lookupDisplayFieldName != null) {
fieldNames.add(fieldMetadata.lookupDisplayFieldName);
}
}
return String.join(fieldNames, ',');
}
private static String getOrderByClause(String sortByFieldName, String sortDirection) {
sortByFieldName = String.isNotBlank(sortByFieldName) ? sortByFieldName : DEFAULT_SORT_FIELD_NAME;
sortDirection = String.isNotBlank(sortDirection) ? sortDirection : DEFAULT_SORT_DIRECTION;
Schema.SObjectfield field = LOG_ENTRY_SOBJECT_TYPE.getDescribe().fields.getMap().get(sortByFieldName);
// For lookups, sort by the parent record's display field name (Name, CaseNumber, Subject, etc.)
if (field.getDescribe().getType() == Schema.DisplayType.REFERENCE) {
sortByFieldName = getDisplayFieldApiName(field.getDescribe());
}
return sortByFieldName + ' ' + sortDirection;
}
private static String getDisplayFieldApiName(Schema.DescribeFieldResult lookupFieldDescribe) {
String relationshipName = lookupFieldDescribe.getRelationshipName();
Schema.SObjectType lookupSObjectType = lookupFieldDescribe.getReferenceTo().get(0);
// Use username instead of name for user
if (lookupSObjectType == Schema.User.SObjectType) {
return relationshipName + '.' + Schema.User.Username.toString();
}
// There are several commonly used names for the display field name - typically, Name, but check for others
List<String> educatedGuesses = new List<String>{
'Name',
'Title',
'Subject',
'AssetRelationshipNumber',
'CaseNumber',
'ContractNumber',
'OrderItemNumber',
'OrderNumber',
'DeveloperName',
'ApiName',
'Domain',
'FriendlyName',
'FunctionName',
'Label',
'LocalPart',
'SolutionName',
'TestSuiteName'
};
String displayFieldApiName;
for (String fieldName : educatedGuesses) {
Schema.SObjectField field = lookupSObjectType.getDescribe().fields.getMap().get(fieldName);
if (field != null) {
Schema.DescribeFieldResult fieldDescribe = field.getDescribe();
if (fieldDescribe.isNameField()) {
displayFieldApiName = fieldDescribe.getName();
break;
}
}
}
return relationshipName + '.' + displayFieldApiName;
}
/**
* @description Inner, wrapper class that contains query result information after querying related log entries.
*/
public class LogEntryQueryResult {
/**
* @description Contains the fieldSet associated with this query.
*/
@AuraEnabled
public FieldSetMetadata fieldSet { get; set; }
/**
* @description Contains the result of the CRUD check, determining if the log entry is "accessible" for the current user.
*/
@AuraEnabled
public Boolean isAccessible { get; set; }
/**
* @description Contains the label of the log entry sObject, fetched using a describe call in the constructor.
*/
@AuraEnabled
public String label { get; set; }
/**
* @description Contains the plural label of the log entry sObject, fetched using a describe call in the constructor.
*/
@AuraEnabled
public String labelPlural { get; set; }
/**
* @description contains the log entry results from the query.
*/
@AuraEnabled
public List<LogEntry__c> records { get; set; }
private LogEntryQueryResult(FieldSetMetadata fieldSetMetadata, List<LogEntry__c> records) {
this.fieldSet = fieldSetMetadata;
this.isAccessible = LOG_ENTRY_SOBJECT_TYPE.getDescribe().isAccessible();
this.label = LOG_ENTRY_SOBJECT_TYPE.getDescribe().getLabel();
this.labelPlural = LOG_ENTRY_SOBJECT_TYPE.getDescribe().getLabelPlural();
this.records = records;
}
}
/**
* @description Inner, wrapper class, containing metadata around the list of fields used in the related log entry query.
*/
public class FieldSetMetadata {
/**
* @description A list of field related metadata
*/
@AuraEnabled
public List<FieldMetadata> fields { get; set; }
/**
* @description Contains the label of the desired field set, fetched using a describe call on the field set.
*/
@AuraEnabled
public String label { get; set; }
/**
* @description A string containing the API name of the field set, including the namespace prefix, if applicable.
*/
@AuraEnabled
public String name { get; set; }
private FieldSetMetadata(Schema.SObjectType sobjectType, String fieldSetName) {
this.fields = new List<FieldMetadata>();
Schema.FieldSet fieldSet = sobjectType.getDescribe().fieldSets.getMap().get(fieldSetName);
for (Schema.FieldSetMember fieldSetMember : fieldSet.getFields()) {
// Enforce field-level security (FLS)
if (fieldSetMember.getSObjectField().getDescribe().isAccessible()) {
this.fields.add(new FieldMetadata(fieldSetMember));
}
}
String namespacePrefix = String.isBlank(fieldSet.getNamespace()) ? '' : fieldSet.getNamespace() + '__';
this.label = fieldSet.getLabel();
this.name = namespacePrefix + fieldSet.getName();
}
}
/**
* @description An inner, wrapper class containing metadata information about an individual field.
*/
public class FieldMetadata {
/**
* @description A string containing the API name of the field, in particular the field path as it relates to the parent field set.
*/
@AuraEnabled
public String fieldName { get; set; }
/**
* @description Boolean that returns true if this field is the standard Name field for its parent object.
*/
@AuraEnabled
public Boolean isNameField { get; set; }
/**
* @description A string containing the label of the field.
*/
@AuraEnabled
public String label { get; set; }
/**
* @description A string used for lookup fields to indicate the display name of the lookup / relationship.
*/
@AuraEnabled
public String lookupDisplayFieldName { get; set; }
/**
* @description Boolean that returns true if this field is sortable.
*/
@AuraEnabled
public Boolean sortable { get; set; }
/**
* @description If the field is a lookup or master detail relationship, this string will return the relationship API name. For instance: Lookup__r instead of Lookup__c.
*/
@AuraEnabled
public String relationshipName { get; set; }
/**
* @description Returns the type of the field, matching the Schema.DisplayType ENUM values, but in all lowercase letters.
*/
@AuraEnabled
public String type { get; set; }
private FieldMetadata(Schema.FieldSetMember fieldSetMember) {
Schema.DescribeFieldResult fieldDescribe = fieldSetMember.getSObjectField().getDescribe();
this.fieldName = fieldSetMember.getFieldPath();
this.isNameField = fieldDescribe.isNameField();
this.label = fieldSetMember.getLabel();
this.relationshipName = fieldDescribe.getRelationshipName();
this.sortable = fieldDescribe.isSortable();
this.type = fieldDescribe.getType().name().toLowerCase();
if (fieldDescribe.getReferenceTo().size() == 1) {
// Handle single-object (normal) lookups
this.lookupDisplayFieldName = getDisplayFieldApiName(fieldDescribe);
}
}
}
}