Skip to content

Commit

Permalink
View Log Entry Metadata custom permission (#659)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jongpie committed Apr 2, 2024
1 parent 6453b17 commit 8233697
Show file tree
Hide file tree
Showing 12 changed files with 350 additions and 50 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

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

## Unlocked Package - v4.13.5
## Unlocked Package - v4.13.6

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

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

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

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@ public without sharing class LogEntryMetadataViewerController {
* @param sourceMetadata Either the value `Origin` or `Exception`
* @return An instance of `LogEntryMetadataViewerController.LogEntryMetadata`
*/
// @AuraEnabled
@AuraEnabled(cacheable=true)
public static LogEntryMetadata getMetadata(Id recordId, String sourceMetadata) {
LogEntryMetadata metadata = new LogEntryMetadata();
if (Schema.ApexClass.SObjectType.getDescribe().isAccessible() == false || Schema.ApexTrigger.SObjectType.getDescribe().isAccessible() == false) {
// TODO decide if it makes more sense to return null
if (canViewLogEntryMetadata() == false) {
return metadata;
}

Expand All @@ -31,27 +29,29 @@ public without sharing class LogEntryMetadataViewerController {
switch on sourceMetadata {
when 'Exception' {
sourceApiName = logEntry.ExceptionSourceApiName__c;
sourceMetadataType = String.isBlank(logEntry.ExceptionSourceMetadataType__c)
? null
: LoggerStackTrace.SourceMetadataType.valueOf(logEntry.ExceptionSourceMetadataType__c);
sourceMetadataType = getSourceMetadataType(logEntry.ExceptionSourceMetadataType__c);
}
when 'Origin' {
sourceApiName = logEntry.OriginSourceApiName__c;
sourceMetadataType = String.isBlank(logEntry.OriginSourceMetadataType__c)
? null
: LoggerStackTrace.SourceMetadataType.valueOf(logEntry.OriginSourceMetadataType__c);
sourceMetadataType = getSourceMetadataType(logEntry.OriginSourceMetadataType__c);
}
}

if (sourceMetadataType == null || String.isBlank(sourceApiName)) {
return metadata;
if (String.isNotBlank(sourceApiName) && sourceMetadataType != null) {
querySourceMetadata(logEntry, metadata, sourceMetadataType, sourceApiName);
}

querySourceMetadata(logEntry, metadata, sourceMetadataType, sourceApiName);

return metadata;
}

private static Boolean canViewLogEntryMetadata() {
return Schema.ApexClass.SObjectType.getDescribe().isAccessible() || System.FeatureManagement.checkPermission('CanViewLogEntryMetadata');
}

private static LoggerStackTrace.SourceMetadataType getSourceMetadataType(String sourceMetadata) {
return String.isBlank(sourceMetadata) ? null : LoggerStackTrace.SourceMetadataType.valueOf(sourceMetadata);
}

@SuppressWarnings('PMD.ExcessiveParameterList')
private static void querySourceMetadata(
LogEntry__c logEntry,
Expand All @@ -76,14 +76,12 @@ public without sharing class LogEntryMetadataViewerController {
}
}

if (codeBodyField == null || metadataRecords == null || metadataRecords.isEmpty()) {
return;
}

SObject metadataRecord = metadataRecords.get(0);
if (codeBodyField != null && metadataRecords != null && metadataRecords.isEmpty() == false) {
SObject metadataRecord = metadataRecords.get(0);

logEntryMetadata.HasCodeBeenModified = ((Datetime) metadataRecord.get(lastModifiedDateField)) > logEntry.Timestamp__c;
logEntryMetadata.Code = (String) metadataRecord.get(codeBodyField);
logEntryMetadata.HasCodeBeenModified = ((Datetime) metadataRecord.get(lastModifiedDateField)) > logEntry.Timestamp__c;
logEntryMetadata.Code = (String) metadataRecord.get(codeBodyField);
}
}

// TODO consider combining with LogEntryHandler.SourceMetadataSnippet (which could become a top-level class)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<CustomPermission xmlns="http://soap.sforce.com/2006/04/metadata">
<isLicensed>false</isLicensed>
<label>Nebula Logger: Can View Log Entry Metadata</label>
</CustomPermission>
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@
<enabled>true</enabled>
<name>CanModifyLoggerSettings</name>
</customPermissions>
<customPermissions>
<enabled>true</enabled>
<name>CanViewLogEntryMetadata</name>
</customPermissions>
<customSettingAccesses>
<enabled>true</enabled>
<name>LoggerSettings__c</name>
Expand Down
2 changes: 1 addition & 1 deletion nebula-logger/core/main/logger-engine/classes/Logger.cls
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
global with sharing class Logger {
// There's no reliable way to get the version number dynamically in Apex
@TestVisible
private static final String CURRENT_VERSION_NUMBER = 'v4.13.5';
private static final String CURRENT_VERSION_NUMBER = 'v4.13.6';
private static final System.LoggingLevel FALLBACK_LOGGING_LEVEL = System.LoggingLevel.DEBUG;
private static final List<LogEntryEventBuilder> LOG_ENTRIES_BUFFER = new List<LogEntryEventBuilder>();
private static final String MISSING_SCENARIO_ERROR_MESSAGE = 'No logger scenario specified. A scenario is required for logging in this org.';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//------------------------------------------------------------------------------------------------//
import FORM_FACTOR from '@salesforce/client/formFactor';

const CURRENT_VERSION_NUMBER = 'v4.13.5';
const CURRENT_VERSION_NUMBER = 'v4.13.6';

// JavaScript equivalent to the Apex class ComponentLogger.ComponentLogEntry
const ComponentLogEntry = class {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1230,19 +1230,6 @@ private class LogEntryHandler_Tests {
return namespacePrefix;
}

private class MockLogManagementDataSelector extends LogManagementDataSelector {
private Integer apexClassesQueryCount = 0;

public override List<ApexClass> getApexClasses(Set<String> apexClassNames) {
this.apexClassesQueryCount++;
return super.getApexClasses(apexClassNames);
}

public Integer getApexClassesQueryCount() {
return apexClassesQueryCount;
}
}

// Helper class for testing stack trace parsing & ApexClass querying for an inner class
private class SomeInnerClass {
public LoggerStackTrace getLoggerStackTrace() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ private class LogEntryMetadataViewerController_Tests {
}

@IsTest
static void it_returns_metadata_for_log_entry_when_source_metadata_is_exception() {
ApexClass mockExceptionApexClass = createMockApexClass('Some_Fake_Apex_Class');
static void it_returns_apex_class_metadata_for_log_entry_when_source_metadata_is_exception() {
Schema.ApexClass mockExceptionApexClass = createMockApexClass('Some_Fake_Apex_Class');
System.Assert.isNotNull(mockExceptionApexClass.Name);
MOCK_SELECTOR.setMockApexClass(mockExceptionApexClass);
LogEntry__c mockLogEntry = createMockLogEntry(null, mockExceptionApexClass);
Expand All @@ -28,9 +28,28 @@ private class LogEntryMetadataViewerController_Tests {
System.Assert.isFalse(logEntryMetadata.HasCodeBeenModified);
}

@IsTest
static void it_returns_apex_trigger_metadata_for_log_entry_when_source_metadata_is_exception() {
Schema.ApexTrigger mockExceptionApexTrigger = createMockApexTrigger('Some_Fake_Apex_Trigger');
System.Assert.isNotNull(mockExceptionApexTrigger.Name);
MOCK_SELECTOR.setMockApexTrigger(mockExceptionApexTrigger);
LogEntry__c mockLogEntry = createMockLogEntry(null, mockExceptionApexTrigger);
System.Assert.areEqual(mockExceptionApexTrigger.Name, mockLogEntry.ExceptionSourceApiName__c);
System.Assert.isNull(mockLogEntry.OriginSourceApiName__c);
MOCK_SELECTOR.setMockLogEntry(mockLogEntry);

LogEntryMetadataViewerController.LogEntryMetadata logEntryMetadata = LogEntryMetadataViewerController.getMetadata(
mockLogEntry.Id,
SOURCE_METADATA_EXCEPTION
);

System.Assert.areEqual(mockExceptionApexTrigger.Body, logEntryMetadata.Code);
System.Assert.isFalse(logEntryMetadata.HasCodeBeenModified);
}

@IsTest
static void it_indicates_when_exception_source_metadata_has_been_modified_after_log_entry_timestamp() {
ApexClass mockExceptionApexClass = createMockApexClass('Some_Fake_Apex_Class');
Schema.ApexClass mockExceptionApexClass = createMockApexClass('Some_Fake_Apex_Class');
System.Assert.isNotNull(mockExceptionApexClass.Name);
MOCK_SELECTOR.setMockApexClass(mockExceptionApexClass);
LogEntry__c mockLogEntry = createMockLogEntry(null, mockExceptionApexClass);
Expand All @@ -49,8 +68,8 @@ private class LogEntryMetadataViewerController_Tests {
}

@IsTest
static void it_returns_metadata_for_log_entry_when_source_metadata_is_origin() {
ApexClass mockOriginSourceApexClass = createMockApexClass('Some_Fake_Apex_Class');
static void it_returns_apex_class_metadata_for_log_entry_when_source_metadata_is_origin() {
Schema.ApexClass mockOriginSourceApexClass = createMockApexClass('Some_Fake_Apex_Class');
System.Assert.isNotNull(mockOriginSourceApexClass.Name);
MOCK_SELECTOR.setMockApexClass(mockOriginSourceApexClass);
LogEntry__c mockLogEntry = createMockLogEntry(mockOriginSourceApexClass, null);
Expand All @@ -69,7 +88,7 @@ private class LogEntryMetadataViewerController_Tests {

@IsTest
static void it_indicates_when_origin_source_metadata_has_been_modified_after_log_entry_timestamp() {
ApexClass mockOriginSourceApexClass = createMockApexClass('Some_Fake_Apex_Class');
Schema.ApexClass mockOriginSourceApexClass = createMockApexClass('Some_Fake_Apex_Class');
System.Assert.isNotNull(mockOriginSourceApexClass.Name);
MOCK_SELECTOR.setMockApexClass(mockOriginSourceApexClass);
LogEntry__c mockLogEntry = createMockLogEntry(mockOriginSourceApexClass, null);
Expand Down Expand Up @@ -97,6 +116,16 @@ private class LogEntryMetadataViewerController_Tests {
return mockLogEntry;
}

private static LogEntry__c createMockLogEntry(Schema.ApexTrigger originApexTrigger, Schema.ApexTrigger exceptionApexTrigger) {
LogEntry__c mockLogEntry = (LogEntry__c) LoggerMockDataCreator.createDataBuilder(Schema.LogEntry__c.SObjectType).populateRequiredFields().getRecord();
mockLogEntry.ExceptionSourceApiName__c = exceptionApexTrigger?.Name;
mockLogEntry.ExceptionSourceMetadataType__c = exceptionApexTrigger == null ? null : LoggerStackTrace.SourceMetadataType.ApexTrigger.name();
mockLogEntry.OriginSourceApiName__c = originApexTrigger?.Name;
mockLogEntry.OriginSourceMetadataType__c = originApexTrigger == null ? null : LoggerStackTrace.SourceMetadataType.ApexTrigger.name();
mockLogEntry.Timestamp__c = System.now();
return mockLogEntry;
}

private static Schema.ApexClass createMockApexClass(String mockApexClassName) {
Schema.ApexClass mockApexClass = new Schema.ApexClass(
Body = 'Wow, look at this code for a mock version of apex class ' + mockApexClassName,
Expand All @@ -105,15 +134,28 @@ private class LogEntryMetadataViewerController_Tests {
return (Schema.ApexClass) LoggerMockDataCreator.setReadOnlyField(mockApexClass, Schema.ApexClass.LastModifiedDate, System.now().addDays(-7));
}

private static Schema.ApexTrigger createMockApexTrigger(String mockApexTriggerName) {
Schema.ApexTrigger mockApexTrigger = new Schema.ApexTrigger(
Body = 'Wow, look at this code for a mock version of apex trigger ' + mockApexTriggerName,
Name = mockApexTriggerName
);
return (Schema.ApexTrigger) LoggerMockDataCreator.setReadOnlyField(mockApexTrigger, Schema.ApexTrigger.LastModifiedDate, System.now().addDays(-7));
}

// LogEntryMetadataViewerController uses a few queries via LogManagementDataSelector - this class mocks the query results
private class MockLogManagementDataSelector extends LogManagementDataSelector {
private Schema.ApexClass mockApexClass;
private Schema.ApexTrigger mockApexTrigger;
private LogEntry__c mockLogEntry;

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

public override List<Schema.ApexTrigger> getApexTriggers(Set<String> apexTriggerNames) {
return new List<Schema.ApexTrigger>{ this.mockApexTrigger };
}

public override LogEntry__c getLogEntryById(Id logEntryId) {
return this.mockLogEntry;
}
Expand All @@ -122,6 +164,10 @@ private class LogEntryMetadataViewerController_Tests {
this.mockApexClass = apexClass;
}

public void setMockApexTrigger(Schema.ApexTrigger apexTrigger) {
this.mockApexTrigger = apexTrigger;
}

public void setMockLogEntry(LogEntry__c logEntry) {
this.mockLogEntry = logEntry;
}
Expand Down
Loading

0 comments on commit 8233697

Please sign in to comment.