Skip to content

Commit 8233697

Browse files
authored
View Log Entry Metadata custom permission (#659)
* Added new custom permission CanViewLogEntryMetadata so that users without query access to ApexClass and ApexTrigger can still see the source code in the LWC logEntryMetadataViewer - Updated the permission set `LoggerAdmin` to assign the new custom permission `CanViewLogEntryMetadata` - at least for now, this is only being added to the LoggerAdmin permission set * Added some more test methods in LogEntryMetadataViewerController_Tests * Cleaned up some code in LogEntryMetadataViewerController
1 parent 6453b17 commit 8233697

12 files changed

Lines changed: 350 additions & 50 deletions

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55

66
The most robust logger for Salesforce. Works with Apex, Lightning Components, Flow, Process Builder & Integrations. Designed for Salesforce admins, developers & architects.
77

8-
## Unlocked Package - v4.13.5
8+
## Unlocked Package - v4.13.6
99

10-
[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y000001MkGnQAK)
11-
[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y000001MkGnQAK)
10+
[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y000001MkGxQAK)
11+
[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y000001MkGxQAK)
1212
[![View Documentation](./images/btn-view-documentation.png)](https://jongpie.github.io/NebulaLogger/)
1313

14-
`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y000001MkGnQAK`
14+
`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y000001MkGxQAK`
1515

16-
`sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y000001MkGnQAK`
16+
`sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y000001MkGxQAK`
1717

1818
---
1919

nebula-logger/core/main/log-management/classes/LogEntryMetadataViewerController.cls

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,10 @@ public without sharing class LogEntryMetadataViewerController {
1515
* @param sourceMetadata Either the value `Origin` or `Exception`
1616
* @return An instance of `LogEntryMetadataViewerController.LogEntryMetadata`
1717
*/
18-
// @AuraEnabled
1918
@AuraEnabled(cacheable=true)
2019
public static LogEntryMetadata getMetadata(Id recordId, String sourceMetadata) {
2120
LogEntryMetadata metadata = new LogEntryMetadata();
22-
if (Schema.ApexClass.SObjectType.getDescribe().isAccessible() == false || Schema.ApexTrigger.SObjectType.getDescribe().isAccessible() == false) {
23-
// TODO decide if it makes more sense to return null
21+
if (canViewLogEntryMetadata() == false) {
2422
return metadata;
2523
}
2624

@@ -31,27 +29,29 @@ public without sharing class LogEntryMetadataViewerController {
3129
switch on sourceMetadata {
3230
when 'Exception' {
3331
sourceApiName = logEntry.ExceptionSourceApiName__c;
34-
sourceMetadataType = String.isBlank(logEntry.ExceptionSourceMetadataType__c)
35-
? null
36-
: LoggerStackTrace.SourceMetadataType.valueOf(logEntry.ExceptionSourceMetadataType__c);
32+
sourceMetadataType = getSourceMetadataType(logEntry.ExceptionSourceMetadataType__c);
3733
}
3834
when 'Origin' {
3935
sourceApiName = logEntry.OriginSourceApiName__c;
40-
sourceMetadataType = String.isBlank(logEntry.OriginSourceMetadataType__c)
41-
? null
42-
: LoggerStackTrace.SourceMetadataType.valueOf(logEntry.OriginSourceMetadataType__c);
36+
sourceMetadataType = getSourceMetadataType(logEntry.OriginSourceMetadataType__c);
4337
}
4438
}
4539

46-
if (sourceMetadataType == null || String.isBlank(sourceApiName)) {
47-
return metadata;
40+
if (String.isNotBlank(sourceApiName) && sourceMetadataType != null) {
41+
querySourceMetadata(logEntry, metadata, sourceMetadataType, sourceApiName);
4842
}
4943

50-
querySourceMetadata(logEntry, metadata, sourceMetadataType, sourceApiName);
51-
5244
return metadata;
5345
}
5446

47+
private static Boolean canViewLogEntryMetadata() {
48+
return Schema.ApexClass.SObjectType.getDescribe().isAccessible() || System.FeatureManagement.checkPermission('CanViewLogEntryMetadata');
49+
}
50+
51+
private static LoggerStackTrace.SourceMetadataType getSourceMetadataType(String sourceMetadata) {
52+
return String.isBlank(sourceMetadata) ? null : LoggerStackTrace.SourceMetadataType.valueOf(sourceMetadata);
53+
}
54+
5555
@SuppressWarnings('PMD.ExcessiveParameterList')
5656
private static void querySourceMetadata(
5757
LogEntry__c logEntry,
@@ -76,14 +76,12 @@ public without sharing class LogEntryMetadataViewerController {
7676
}
7777
}
7878

79-
if (codeBodyField == null || metadataRecords == null || metadataRecords.isEmpty()) {
80-
return;
81-
}
82-
83-
SObject metadataRecord = metadataRecords.get(0);
79+
if (codeBodyField != null && metadataRecords != null && metadataRecords.isEmpty() == false) {
80+
SObject metadataRecord = metadataRecords.get(0);
8481

85-
logEntryMetadata.HasCodeBeenModified = ((Datetime) metadataRecord.get(lastModifiedDateField)) > logEntry.Timestamp__c;
86-
logEntryMetadata.Code = (String) metadataRecord.get(codeBodyField);
82+
logEntryMetadata.HasCodeBeenModified = ((Datetime) metadataRecord.get(lastModifiedDateField)) > logEntry.Timestamp__c;
83+
logEntryMetadata.Code = (String) metadataRecord.get(codeBodyField);
84+
}
8785
}
8886

8987
// TODO consider combining with LogEntryHandler.SourceMetadataSnippet (which could become a top-level class)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<CustomPermission xmlns="http://soap.sforce.com/2006/04/metadata">
3+
<isLicensed>false</isLicensed>
4+
<label>Nebula Logger: Can View Log Entry Metadata</label>
5+
</CustomPermission>

nebula-logger/core/main/log-management/permissionsets/LoggerAdmin.permissionset-meta.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@
108108
<enabled>true</enabled>
109109
<name>CanModifyLoggerSettings</name>
110110
</customPermissions>
111+
<customPermissions>
112+
<enabled>true</enabled>
113+
<name>CanViewLogEntryMetadata</name>
114+
</customPermissions>
111115
<customSettingAccesses>
112116
<enabled>true</enabled>
113117
<name>LoggerSettings__c</name>

nebula-logger/core/main/logger-engine/classes/Logger.cls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
global with sharing class Logger {
1616
// There's no reliable way to get the version number dynamically in Apex
1717
@TestVisible
18-
private static final String CURRENT_VERSION_NUMBER = 'v4.13.5';
18+
private static final String CURRENT_VERSION_NUMBER = 'v4.13.6';
1919
private static final System.LoggingLevel FALLBACK_LOGGING_LEVEL = System.LoggingLevel.DEBUG;
2020
private static final List<LogEntryEventBuilder> LOG_ENTRIES_BUFFER = new List<LogEntryEventBuilder>();
2121
private static final String MISSING_SCENARIO_ERROR_MESSAGE = 'No logger scenario specified. A scenario is required for logging in this org.';

nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//------------------------------------------------------------------------------------------------//
55
import FORM_FACTOR from '@salesforce/client/formFactor';
66

7-
const CURRENT_VERSION_NUMBER = 'v4.13.5';
7+
const CURRENT_VERSION_NUMBER = 'v4.13.6';
88

99
// JavaScript equivalent to the Apex class ComponentLogger.ComponentLogEntry
1010
const ComponentLogEntry = class {

nebula-logger/core/tests/log-management/classes/LogEntryHandler_Tests.cls

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,19 +1230,6 @@ private class LogEntryHandler_Tests {
12301230
return namespacePrefix;
12311231
}
12321232

1233-
private class MockLogManagementDataSelector extends LogManagementDataSelector {
1234-
private Integer apexClassesQueryCount = 0;
1235-
1236-
public override List<ApexClass> getApexClasses(Set<String> apexClassNames) {
1237-
this.apexClassesQueryCount++;
1238-
return super.getApexClasses(apexClassNames);
1239-
}
1240-
1241-
public Integer getApexClassesQueryCount() {
1242-
return apexClassesQueryCount;
1243-
}
1244-
}
1245-
12461233
// Helper class for testing stack trace parsing & ApexClass querying for an inner class
12471234
private class SomeInnerClass {
12481235
public LoggerStackTrace getLoggerStackTrace() {

nebula-logger/core/tests/log-management/classes/LogEntryMetadataViewerController_Tests.cls

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ private class LogEntryMetadataViewerController_Tests {
1010
}
1111

1212
@IsTest
13-
static void it_returns_metadata_for_log_entry_when_source_metadata_is_exception() {
14-
ApexClass mockExceptionApexClass = createMockApexClass('Some_Fake_Apex_Class');
13+
static void it_returns_apex_class_metadata_for_log_entry_when_source_metadata_is_exception() {
14+
Schema.ApexClass mockExceptionApexClass = createMockApexClass('Some_Fake_Apex_Class');
1515
System.Assert.isNotNull(mockExceptionApexClass.Name);
1616
MOCK_SELECTOR.setMockApexClass(mockExceptionApexClass);
1717
LogEntry__c mockLogEntry = createMockLogEntry(null, mockExceptionApexClass);
@@ -28,9 +28,28 @@ private class LogEntryMetadataViewerController_Tests {
2828
System.Assert.isFalse(logEntryMetadata.HasCodeBeenModified);
2929
}
3030

31+
@IsTest
32+
static void it_returns_apex_trigger_metadata_for_log_entry_when_source_metadata_is_exception() {
33+
Schema.ApexTrigger mockExceptionApexTrigger = createMockApexTrigger('Some_Fake_Apex_Trigger');
34+
System.Assert.isNotNull(mockExceptionApexTrigger.Name);
35+
MOCK_SELECTOR.setMockApexTrigger(mockExceptionApexTrigger);
36+
LogEntry__c mockLogEntry = createMockLogEntry(null, mockExceptionApexTrigger);
37+
System.Assert.areEqual(mockExceptionApexTrigger.Name, mockLogEntry.ExceptionSourceApiName__c);
38+
System.Assert.isNull(mockLogEntry.OriginSourceApiName__c);
39+
MOCK_SELECTOR.setMockLogEntry(mockLogEntry);
40+
41+
LogEntryMetadataViewerController.LogEntryMetadata logEntryMetadata = LogEntryMetadataViewerController.getMetadata(
42+
mockLogEntry.Id,
43+
SOURCE_METADATA_EXCEPTION
44+
);
45+
46+
System.Assert.areEqual(mockExceptionApexTrigger.Body, logEntryMetadata.Code);
47+
System.Assert.isFalse(logEntryMetadata.HasCodeBeenModified);
48+
}
49+
3150
@IsTest
3251
static void it_indicates_when_exception_source_metadata_has_been_modified_after_log_entry_timestamp() {
33-
ApexClass mockExceptionApexClass = createMockApexClass('Some_Fake_Apex_Class');
52+
Schema.ApexClass mockExceptionApexClass = createMockApexClass('Some_Fake_Apex_Class');
3453
System.Assert.isNotNull(mockExceptionApexClass.Name);
3554
MOCK_SELECTOR.setMockApexClass(mockExceptionApexClass);
3655
LogEntry__c mockLogEntry = createMockLogEntry(null, mockExceptionApexClass);
@@ -49,8 +68,8 @@ private class LogEntryMetadataViewerController_Tests {
4968
}
5069

5170
@IsTest
52-
static void it_returns_metadata_for_log_entry_when_source_metadata_is_origin() {
53-
ApexClass mockOriginSourceApexClass = createMockApexClass('Some_Fake_Apex_Class');
71+
static void it_returns_apex_class_metadata_for_log_entry_when_source_metadata_is_origin() {
72+
Schema.ApexClass mockOriginSourceApexClass = createMockApexClass('Some_Fake_Apex_Class');
5473
System.Assert.isNotNull(mockOriginSourceApexClass.Name);
5574
MOCK_SELECTOR.setMockApexClass(mockOriginSourceApexClass);
5675
LogEntry__c mockLogEntry = createMockLogEntry(mockOriginSourceApexClass, null);
@@ -69,7 +88,7 @@ private class LogEntryMetadataViewerController_Tests {
6988

7089
@IsTest
7190
static void it_indicates_when_origin_source_metadata_has_been_modified_after_log_entry_timestamp() {
72-
ApexClass mockOriginSourceApexClass = createMockApexClass('Some_Fake_Apex_Class');
91+
Schema.ApexClass mockOriginSourceApexClass = createMockApexClass('Some_Fake_Apex_Class');
7392
System.Assert.isNotNull(mockOriginSourceApexClass.Name);
7493
MOCK_SELECTOR.setMockApexClass(mockOriginSourceApexClass);
7594
LogEntry__c mockLogEntry = createMockLogEntry(mockOriginSourceApexClass, null);
@@ -97,6 +116,16 @@ private class LogEntryMetadataViewerController_Tests {
97116
return mockLogEntry;
98117
}
99118

119+
private static LogEntry__c createMockLogEntry(Schema.ApexTrigger originApexTrigger, Schema.ApexTrigger exceptionApexTrigger) {
120+
LogEntry__c mockLogEntry = (LogEntry__c) LoggerMockDataCreator.createDataBuilder(Schema.LogEntry__c.SObjectType).populateRequiredFields().getRecord();
121+
mockLogEntry.ExceptionSourceApiName__c = exceptionApexTrigger?.Name;
122+
mockLogEntry.ExceptionSourceMetadataType__c = exceptionApexTrigger == null ? null : LoggerStackTrace.SourceMetadataType.ApexTrigger.name();
123+
mockLogEntry.OriginSourceApiName__c = originApexTrigger?.Name;
124+
mockLogEntry.OriginSourceMetadataType__c = originApexTrigger == null ? null : LoggerStackTrace.SourceMetadataType.ApexTrigger.name();
125+
mockLogEntry.Timestamp__c = System.now();
126+
return mockLogEntry;
127+
}
128+
100129
private static Schema.ApexClass createMockApexClass(String mockApexClassName) {
101130
Schema.ApexClass mockApexClass = new Schema.ApexClass(
102131
Body = 'Wow, look at this code for a mock version of apex class ' + mockApexClassName,
@@ -105,15 +134,28 @@ private class LogEntryMetadataViewerController_Tests {
105134
return (Schema.ApexClass) LoggerMockDataCreator.setReadOnlyField(mockApexClass, Schema.ApexClass.LastModifiedDate, System.now().addDays(-7));
106135
}
107136

137+
private static Schema.ApexTrigger createMockApexTrigger(String mockApexTriggerName) {
138+
Schema.ApexTrigger mockApexTrigger = new Schema.ApexTrigger(
139+
Body = 'Wow, look at this code for a mock version of apex trigger ' + mockApexTriggerName,
140+
Name = mockApexTriggerName
141+
);
142+
return (Schema.ApexTrigger) LoggerMockDataCreator.setReadOnlyField(mockApexTrigger, Schema.ApexTrigger.LastModifiedDate, System.now().addDays(-7));
143+
}
144+
108145
// LogEntryMetadataViewerController uses a few queries via LogManagementDataSelector - this class mocks the query results
109146
private class MockLogManagementDataSelector extends LogManagementDataSelector {
110147
private Schema.ApexClass mockApexClass;
148+
private Schema.ApexTrigger mockApexTrigger;
111149
private LogEntry__c mockLogEntry;
112150

113151
public override List<Schema.ApexClass> getApexClasses(Set<String> apexClassNames) {
114152
return new List<Schema.ApexClass>{ this.mockApexClass };
115153
}
116154

155+
public override List<Schema.ApexTrigger> getApexTriggers(Set<String> apexTriggerNames) {
156+
return new List<Schema.ApexTrigger>{ this.mockApexTrigger };
157+
}
158+
117159
public override LogEntry__c getLogEntryById(Id logEntryId) {
118160
return this.mockLogEntry;
119161
}
@@ -122,6 +164,10 @@ private class LogEntryMetadataViewerController_Tests {
122164
this.mockApexClass = apexClass;
123165
}
124166

167+
public void setMockApexTrigger(Schema.ApexTrigger apexTrigger) {
168+
this.mockApexTrigger = apexTrigger;
169+
}
170+
125171
public void setMockLogEntry(LogEntry__c logEntry) {
126172
this.mockLogEntry = logEntry;
127173
}

0 commit comments

Comments
 (0)