diff --git a/.gitignore b/.gitignore index 7aa0a23e4..a121452b5 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ yarn.lock # Files to exclude *.log **/lwc/jsconfig.json +.config diff --git a/README.md b/README.md index 3622c250c..6cbeaca76 100644 --- a/README.md +++ b/README.md @@ -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.0 +## Unlocked Package - v4.13.1 -[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y000001Mk8dQAC) -[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y000001Mk8dQAC) +[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y000001MkE3QAK) +[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y000001MkE3QAK) [![View Documentation](./images/btn-view-documentation.png)](https://jongpie.github.io/NebulaLogger/) -`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y000001Mk8dQAC` +`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y000001MkE3QAK` -`sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y000001Mk8dQAC` +`sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y000001MkE3QAK` --- diff --git a/docs/apex/Configuration/LoggerParameter.md b/docs/apex/Configuration/LoggerParameter.md index dca352184..ddaf9a6a8 100644 --- a/docs/apex/Configuration/LoggerParameter.md +++ b/docs/apex/Configuration/LoggerParameter.md @@ -50,6 +50,10 @@ The name of the Platform Cache partition to use for caching (when platform cache Controls if Nebula Logger queries `ApexClass` data. When set to `false`, any `ApexClass` fields on `LogEntryEvent__e` and `Log__c` will not be populated Controlled by the custom metadata record `LoggerParameter.QueryApexClassData`, or `true` as the default +#### `QUERY_APEX_TRIGGER_DATA` → `Boolean` + +Controls if Nebula Logger queries `ApexTrigger` data. When set to `false`, any `ApexTrigger` fields on `LogEntryEvent__e` and `Log__c` will not be populated Controlled by the custom metadata record `LoggerParameter.QueryApexTriggerData`, or `true` as the default + #### `QUERY_AUTH_SESSION_DATA` → `Boolean` Controls if Nebula Logger queries `Schema.AuthSession` data. When set to `false`, any `Schema.AuthSession` fields on `LogEntryEvent__e` and `Log__c` will not be populated Controlled by the custom metadata record `LoggerParameter.QueryAuthSessionData`, or `true` as the default diff --git a/docs/apex/Log-Management/LogEntryHandler.md b/docs/apex/Log-Management/LogEntryHandler.md index 2c570285f..25ceae000 100644 --- a/docs/apex/Log-Management/LogEntryHandler.md +++ b/docs/apex/Log-Management/LogEntryHandler.md @@ -10,6 +10,18 @@ Manages setting fields on `LogEntry__c` before insert & before update ### Methods +#### `apply(LogEntry__c logEntry, Schema.ApexClass topLevelApexClass)` → `void` + +#### `apply(LogEntry__c logEntry, Schema.ApexTrigger apexTrigger)` → `void` + +#### `apply(LogEntry__c logEntry, Schema.ApexClass apexClass)` → `void` + +#### `apply(LogEntry__c logEntry, Schema.ApexTrigger apexTrigger)` → `void` + +#### `apply(LogEntry__c logEntry, Schema.ApexClass apexClass)` → `void` + +#### `apply(LogEntry__c logEntry, Schema.ApexTrigger apexTrigger)` → `void` + #### `getSObjectType()` → `Schema.SObjectType` Returns SObject Type that the handler is responsible for processing @@ -25,3 +37,37 @@ Schema.SObjectType The instance of `SObjectType` --- + +### Inner Classes + +#### LogEntryHandler.SourceMetadataSnippet class + +--- + +##### Constructors + +###### `SourceMetadataSnippet(LoggerStackTrace stackTrace, Schema.ApexClass apexClass)` + +###### `SourceMetadataSnippet(LoggerStackTrace stackTrace, Schema.ApexTrigger apexTrigger)` + +--- + +##### Properties + +###### `ApiVersion` → `String` + +###### `Code` → `String` + +###### `EndingLineNumber` → `Integer` + +###### `Language` → `LoggerStackTrace.Source` + +###### `StackTrace` → `Logger` + +###### `StartingLineNumber` → `Integer` + +###### `TargetLineNumber` → `Integer` + +###### `TotalLinesOfCode` → `Integer` + +--- diff --git a/docs/apex/Log-Management/LogEntryMetadataViewerController.md b/docs/apex/Log-Management/LogEntryMetadataViewerController.md new file mode 100644 index 000000000..be1c84598 --- /dev/null +++ b/docs/apex/Log-Management/LogEntryMetadataViewerController.md @@ -0,0 +1,48 @@ +--- +layout: default +--- + +## LogEntryMetadataViewerController class + +Controller class for the LWC `logEntryMetadataViewer` + +--- + +### Methods + +#### `getMetadata(Id recordId, String sourceMetadata)` → `LogEntryMetadata` + +Returns an instance of the inner class `LogEntryMetadataViewerController.LogEntryMetadata`, which contains information about the log entry's origin and exception Apex classes + +##### Parameters + +| Param | Description | +| ---------------- | ---------------------------------------- | +| `recordId` | The `ID` of the `LogEntry__c` record | +| `sourceMetadata` | Either the value `Origin` or `Exception` | + +##### Return + +**Type** + +LogEntryMetadata + +**Description** + +An instance of `LogEntryMetadataViewerController.LogEntryMetadata` + +--- + +### Inner Classes + +#### LogEntryMetadataViewerController.LogEntryMetadata class + +--- + +##### Properties + +###### `Code` → `String` + +###### `HasCodeBeenModified` → `Boolean` + +--- diff --git a/docs/apex/Log-Management/LogManagementDataSelector.md b/docs/apex/Log-Management/LogManagementDataSelector.md index 1599e4002..348f4b30b 100644 --- a/docs/apex/Log-Management/LogManagementDataSelector.md +++ b/docs/apex/Log-Management/LogManagementDataSelector.md @@ -31,7 +31,7 @@ List<SObject> `List<SObject>` containing any records in the specified `SObjectType` -#### `getApexClasses(List apexClassNames)` → `List` +#### `getApexClasses(Set apexClassNames)` → `List` Returns a list of `ApexClass` records @@ -51,6 +51,26 @@ List<ApexClass> `List<ApexClass>` containing any matching records +#### `getApexTriggers(Set apexTriggerNames)` → `List` + +Returns a list of `ApexTrigger` records + +##### Parameters + +| Param | Description | +| ------------------ | --------------------------------------- | +| `apexTriggerNames` | The names of the Apex triggers to query | + +##### Return + +**Type** + +List<ApexTrigger> + +**Description** + +`List<ApexTrigger>` containing any matching records + #### `getById(Schema.SObjectType sobjectType, Set fieldNames, List recordIds)` → `List` Dynamically queries & returns records in the specified `SObjectType` based on the specified record IDs @@ -257,6 +277,26 @@ List<LogEntry\_\_c> The matching `List<LogEntry__c>` records +#### `getLogEntryById(Id logEntryId)` → `LogEntry__c` + +Returns a `LogEntry__c` record + +##### Parameters + +| Param | Description | +| ------------ | --------------------------------------------- | +| `logEntryId` | The `ID` of the `LogEntry__c` record to query | + +##### Return + +**Type** + +LogEntry\_\_c + +**Description** + +The matching `LogEntry__c` record + #### `getLoggerScenariosById(List logScenarioIds)` → `List` Returns a `List<LoggerScenario__c>` of records with the specified log scenario IDs diff --git a/docs/apex/Logger-Engine/LogEntryEventBuilder.md b/docs/apex/Logger-Engine/LogEntryEventBuilder.md index 2fa923b6a..650bec0df 100644 --- a/docs/apex/Logger-Engine/LogEntryEventBuilder.md +++ b/docs/apex/Logger-Engine/LogEntryEventBuilder.md @@ -16,7 +16,7 @@ Builder class that generates each `LogEntryEvent__e` record #### `LogEntryEventBuilder(LoggerSettings__c userSettings, System.LoggingLevel entryLoggingLevel, Boolean shouldSave, Set ignoredOrigins)` -Used by `Logger` to instantiate a new instance of `LogEntryEventBuilder` +`Deprecated` - Formally used by `Logger` to instantiate a new instance of `LogEntryEventBuilder` ##### Parameters @@ -27,6 +27,18 @@ Used by `Logger` to instantiate a new instance of `LogEntryEventBuilder` | `shouldSave` | Indicates if the builder's instance of `LogEntryEvent__e` should be saved | | `ignoredOrigins` | A `Set<String>` of the names of any Apex classes that should be ignored when parsing the entry's origin | +#### `LogEntryEventBuilder(LoggerSettings__c userSettings, System.LoggingLevel entryLoggingLevel, Boolean shouldSave)` + +Used by `Logger` to instantiate a new instance of `LogEntryEventBuilder` + +##### Parameters + +| Param | Description | +| ------------------- | --------------------------------------------------------------------------------------- | +| `userSettings` | The instance of `LoggerSettings__c` for the current to use to control any feature flags | +| `entryLoggingLevel` | The `LoggingLevel` value to use for the log entry | +| `shouldSave` | Indicates if the builder's instance of `LogEntryEvent__e` should be saved | + --- ### Methods diff --git a/docs/apex/Logger-Engine/Logger.md b/docs/apex/Logger-Engine/Logger.md index b7992f20e..7a1c59cd4 100644 --- a/docs/apex/Logger-Engine/Logger.md +++ b/docs/apex/Logger-Engine/Logger.md @@ -5776,28 +5776,6 @@ List of records to save. --- -##### Methods - -###### `Uuid()` → `public` - -Default constructor - -###### `getValue()` → `String` - -Getter returning the uuid value - -####### Return - -**Type** - -String - -**Description** - -A string containing the UUID value. - ---- - #### Logger.StatusApiResponseProduct class --- diff --git a/docs/apex/Logger-Engine/LoggerStackTrace.md b/docs/apex/Logger-Engine/LoggerStackTrace.md new file mode 100644 index 000000000..7a1dea2ca --- /dev/null +++ b/docs/apex/Logger-Engine/LoggerStackTrace.md @@ -0,0 +1,128 @@ +--- +layout: default +--- + +## LoggerStackTrace class + +Class used for tracking & parsing stack traces + +### Related + +[Logger](Logger) + +LogEntryBuilder + +--- + +### Constructors + +#### `LoggerStackTrace()` + +Constructor that automatically generates & parses stack trace information based on the calling code + +#### `LoggerStackTrace(Exception apexException)` + +Constructor that parses stack trace information from the provided `Exception` + +##### Parameters + +| Param | Description | +| --------------- | ------------------------------------ | +| `apexException` | An instance of any `Exception` class | + +#### `LoggerStackTrace(String apexStackTraceString)` + +Constructor that parses stack trace information from the provided `String` + +##### Parameters + +| Param | Description | +| ---------------------- | -------------------------------------------------------- | +| `apexStackTraceString` | The original stack trace value generated by the platform | + +#### `LoggerStackTrace(SourceLanguage language, String sourceStackTraceString)` + +--- + +### Enums + +#### SourceLanguage + +#### SourceMetadataType + +--- + +### Properties + +#### `Language` → `Source` + +#### `Location` → `String` + +#### `ParsedStackTraceString` → `String` + +#### `Source` → `public` + +--- + +### Methods + +#### `AnonymousBlock()` → `public` + +#### `ApexClass()` → `public` + +#### `ApexTrigger()` → `public` + +#### `AuraDefinitionBundle()` → `public` + +#### `LightningComponentBundle()` → `public` + +#### `ignoreOrigin(System.Type apexType)` → `void` + +Adds the specified Apex type to the the current transaction's list of ignored origin locations. Any ignored types will be removed from the StackTrace\_\_c field, and will be skipped when determining the log entry's origin location + +##### Parameters + +| Param | Description | +| ---------- | ------------------------------------ | +| `apexType` | The Apex type of the class to ignore | + +#### `ignoreOrigin(SourceLanguage language, String origin)` → `void` + +Adds the specified string-based origin to the the current transaction's list of ignored origin locations for the specified source language. Any ignored types will be removed from the StackTrace\_\_c field, and will be skipped when determining the log entry's origin location + +##### Parameters + +| Param | Description | +| ---------- | ---------------------------------------------------------------------------- | +| `language` | The source language (Apex or JavaScript) | +| `origin` | The string-based name of the Apex type or lightning component name to ignore | + +#### `ignoreOrigins(Set apexTypes)` → `void` + +Adds the specified Apex types to the list of ignored origin locations for the current transaction. Any ignored types will be removed from the StackTrace\_\_c field, and will be skipped when determining the log entry's origin location + +##### Parameters + +| Param | Description | +| ----------- | ---------------------------------------------------------- | +| `apexTypes` | A `Set` containing the Apex types of the classes to ignore | + +--- + +### Inner Classes + +#### LoggerStackTrace.SourceMetadata class + +--- + +##### Properties + +###### `ActionName` → `String` + +###### `ApiName` → `String` + +###### `LineNumber` → `Integer` + +###### `MetadataType` → `Source` + +--- diff --git a/docs/apex/index.md b/docs/apex/index.md index ae12391fc..e20382a88 100644 --- a/docs/apex/index.md +++ b/docs/apex/index.md @@ -54,6 +54,10 @@ Abstract class used by trigger handlers for shared logic Proxy class used as a middle layer between some problematic SObject Types and the rest of Nebula Logger's codebase. Each inner class maps to a corresponding `SObjectType` that is difficult to work with Apex for some reason or another, such as not being mockable or creatable, or not existing in all orgs. +### [LoggerStackTrace](Logger-Engine/LoggerStackTrace) + +Class used for tracking & parsing stack traces + ## Log Management ### [LogBatchPurgeController](Log-Management/LogBatchPurgeController) @@ -84,6 +88,10 @@ Dynamically returns `LogEntry__c` field sets in App Builder when configuring the Manages setting fields on `LogEntry__c` before insert & before update +### [LogEntryMetadataViewerController](Log-Management/LogEntryMetadataViewerController) + +Controller class for the LWC `logEntryMetadataViewer` + ### [LogEntryTagHandler](Log-Management/LogEntryTagHandler) Handles trigger events for the `LogEntryTag__c` object diff --git a/nebula-logger/core/main/configuration/classes/LoggerParameter.cls b/nebula-logger/core/main/configuration/classes/LoggerParameter.cls index ae24508f2..1f03f6dee 100644 --- a/nebula-logger/core/main/configuration/classes/LoggerParameter.cls +++ b/nebula-logger/core/main/configuration/classes/LoggerParameter.cls @@ -166,6 +166,21 @@ public class LoggerParameter { private set; } + /** + * @description Controls if Nebula Logger queries `ApexTrigger` data. + * When set to `false`, any `ApexTrigger` fields on `LogEntryEvent__e` and `Log__c` will not be populated + * Controlled by the custom metadata record `LoggerParameter.QueryApexTriggerData`, or `true` as the default + */ + public static final Boolean QUERY_APEX_TRIGGER_DATA { + get { + if (QUERY_APEX_TRIGGER_DATA == null) { + QUERY_APEX_TRIGGER_DATA = getBoolean('QueryApexTriggerData', true); + } + return QUERY_APEX_TRIGGER_DATA; + } + private set; + } + /** * @description Controls if Nebula Logger queries `Schema.AuthSession` data. * When set to `false`, any `Schema.AuthSession` fields on `LogEntryEvent__e` and `Log__c` will not be populated diff --git a/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.QueryApexClassData.md-meta.xml b/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.QueryApexClassData.md-meta.xml index 970ed57be..43f4a1559 100644 --- a/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.QueryApexClassData.md-meta.xml +++ b/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.QueryApexClassData.md-meta.xml @@ -10,7 +10,7 @@ Description__c When set to 'true' (default), the ApexClass object will be queried to track additional details about Apex classes that logged data - the queried data is stored in fields on LogEntryEvent__e and LogEntry__c. + >When set to 'true' (default), the ApexClass object will be queried to track additional details about Apex classes that logged data - the queried data is stored in fields on LogEntry__c. When set to 'false', the ApexClass object will not be queried, and the related fields will be null. diff --git a/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.QueryApexTriggerData.md-meta.xml b/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.QueryApexTriggerData.md-meta.xml new file mode 100644 index 000000000..98442d09b --- /dev/null +++ b/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.QueryApexTriggerData.md-meta.xml @@ -0,0 +1,21 @@ + + + + false + + Description__c + When set to 'true' (default), the ApexTrigger object will be queried to track additional details about Apex triggers that logged data - the queried data is stored in fields on LogEntry__c. + +When set to 'false', the ApexTrigger object will not be queried, and the related fields will be null. + + + Value__c + true + + diff --git a/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls b/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls index aee8dc3a8..ada18b2ac 100644 --- a/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls +++ b/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls @@ -263,7 +263,11 @@ public without sharing class LogEntryEventHandler extends LoggerSObjectHandler { DatabaseResultType__c = logEntryEvent.DatabaseResultType__c, EpochTimestamp__c = logEntryEvent.EpochTimestamp__c, EventUuid__c = logEntryEvent.EventUuid, + ExceptionLocation__c = logEntryEvent.ExceptionLocation__c, ExceptionMessage__c = logEntryEvent.ExceptionMessage__c, + ExceptionSourceActionName__c = logEntryEvent.ExceptionSourceActionName__c, + ExceptionSourceApiName__c = logEntryEvent.ExceptionSourceApiName__c, + ExceptionSourceMetadataType__c = logEntryEvent.ExceptionSourceMetadataType__c, ExceptionStackTrace__c = logEntryEvent.ExceptionStackTrace__c, ExceptionType__c = logEntryEvent.ExceptionType__c, HttpRequestBody__c = logEntryEvent.HttpRequestBody__c, @@ -317,6 +321,9 @@ public without sharing class LogEntryEventHandler extends LoggerSObjectHandler { MessageTruncated__c = logEntryEvent.MessageTruncated__c, Name = null, // Salesforce will auto-set the record ID as the name when null OriginLocation__c = logEntryEvent.OriginLocation__c, + OriginSourceActionName__c = logEntryEvent.OriginSourceActionName__c, + OriginSourceApiName__c = logEntryEvent.OriginSourceApiName__c, + OriginSourceMetadataType__c = logEntryEvent.OriginSourceMetadataType__c, OriginType__c = logEntryEvent.OriginType__c, RecordCollectionSize__c = logEntryEvent.RecordCollectionSize__c, RecordCollectionType__c = logEntryEvent.RecordCollectionType__c, diff --git a/nebula-logger/core/main/log-management/classes/LogEntryHandler.cls b/nebula-logger/core/main/log-management/classes/LogEntryHandler.cls index 05c2e5f43..538211d87 100644 --- a/nebula-logger/core/main/log-management/classes/LogEntryHandler.cls +++ b/nebula-logger/core/main/log-management/classes/LogEntryHandler.cls @@ -26,6 +26,7 @@ public without sharing class LogEntryHandler extends LoggerSObjectHandler { this.logEntries = (List) triggerNew; this.setApexClassFields(); + this.setApexTriggerFields(); this.setComponentFields(); this.setFlowDefinitionFields(); this.setFlowVersionFields(); @@ -44,16 +45,18 @@ public without sharing class LogEntryHandler extends LoggerSObjectHandler { } private void setApexClassFields() { - List apexClassNames = new List(); - List apexLogEntries = new List(); + String apexClassSObjectTypeName = Schema.ApexClass.SObjectType.getDescribe().getName(); + Set apexClassNames = new Set(); + Set apexClassLogEntries = new Set(); for (LogEntry__c logEntry : this.logEntries) { - if (logEntry.OriginType__c == 'Apex' && String.isNotBlank(logEntry.OriginLocation__c)) { - // OriginLocation__c contains the class name + method name. Examples: - // MyClass.someMethod - // MyClass.MyInnerClass.someMethod - String apexClassName = logEntry.OriginLocation__c.substringBefore('.'); - apexClassNames.add(apexClassName); - apexLogEntries.add(logEntry); + if (logEntry.OriginSourceMetadataType__c == apexClassSObjectTypeName && String.isNotBlank(logEntry.OriginSourceApiName__c)) { + apexClassLogEntries.add(logEntry); + apexClassNames.add(logEntry.OriginSourceApiName__c); + } + + if (logEntry.ExceptionSourceMetadataType__c == apexClassSObjectTypeName && String.isNotBlank(logEntry.ExceptionSourceApiName__c)) { + apexClassLogEntries.add(logEntry); + apexClassNames.add(logEntry.ExceptionSourceApiName__c); } } @@ -70,28 +73,50 @@ public without sharing class LogEntryHandler extends LoggerSObjectHandler { return; } - for (LogEntry__c logEntry : apexLogEntries) { - String topLevelApexClassName = logEntry.OriginLocation__c?.substringBefore('.'); - ApexClass topLevelApexClass = classNameToApexClass.get(topLevelApexClassName); - if (topLevelApexClass == null) { - continue; + for (LogEntry__c logEntry : apexClassLogEntries) { + ApexClass originApexClass = classNameToApexClass.get(logEntry.OriginSourceApiName__c); + new OriginApexMetadataFieldApplier().apply(logEntry, originApexClass); + + ApexClass exceptionApexClass = classNameToApexClass.get(logEntry.ExceptionSourceApiName__c); + new ExceptionApexMetadataFieldApplier().apply(logEntry, exceptionApexClass); + } + } + + private void setApexTriggerFields() { + String apexTriggerSObjectTypeName = Schema.ApexTrigger.SObjectType.getDescribe().getName(); + Set apexTriggerNames = new Set(); + Set apexTriggerLogEntries = new Set(); + for (LogEntry__c logEntry : this.logEntries) { + if (logEntry.OriginSourceMetadataType__c == apexTriggerSObjectTypeName && String.isNotBlank(logEntry.OriginSourceApiName__c)) { + apexTriggerLogEntries.add(logEntry); + apexTriggerNames.add(logEntry.OriginSourceApiName__c); } - String methodName = logEntry.OriginLocation__c.substringAfterLast('.'); - String innerApexClassName = logEntry.OriginLocation__c.substringAfter(topLevelApexClassName + '.').substringBeforeLast('.'); - if (innerApexClassName == topLevelApexClassName || innerApexClassName == methodName) { - innerApexClassName = null; - } else if (innerApexClassName.startsWith(topLevelApexClassName + '.')) { - innerApexClassName = innerApexClassName.substringAfter(topLevelApexClassName + '.'); + if (logEntry.ExceptionSourceMetadataType__c == apexTriggerSObjectTypeName && String.isNotBlank(logEntry.ExceptionSourceApiName__c)) { + apexTriggerLogEntries.add(logEntry); + apexTriggerNames.add(logEntry.ExceptionSourceApiName__c); } + } - logEntry.ApexClassApiVersion__c = 'v' + topLevelApexClass.ApiVersion; - logEntry.ApexClassCreatedDate__c = topLevelApexClass.CreatedDate; - logEntry.ApexClassId__c = topLevelApexClass.Id; - logEntry.ApexClassLastModifiedDate__c = topLevelApexClass.LastModifiedDate; - logEntry.ApexClassName__c = topLevelApexClassName; - logEntry.ApexInnerClassName__c = innerApexClassName; - logEntry.ApexMethodName__c = methodName; + if (apexTriggerNames.isEmpty()) { + return; + } + + Map triggerNameToApexTrigger = new Map(); + for (ApexTrigger apexTrigger : LogManagementDataSelector.getInstance().getApexTriggers(apexTriggerNames)) { + triggerNameToApexTrigger.put(apexTrigger.Name, apexTrigger); + } + + if (triggerNameToApexTrigger.isEmpty()) { + return; + } + + for (LogEntry__c logEntry : apexTriggerLogEntries) { + ApexTrigger originApexTrigger = triggerNameToApexTrigger.get(logEntry.OriginSourceApiName__c); + new OriginApexMetadataFieldApplier().apply(logEntry, originApexTrigger); + + ApexTrigger exceptionApexTrigger = triggerNameToApexTrigger.get(logEntry.ExceptionSourceApiName__c); + new ExceptionApexMetadataFieldApplier().apply(logEntry, exceptionApexTrigger); } } @@ -108,8 +133,8 @@ public without sharing class LogEntryHandler extends LoggerSObjectHandler { List flowApiNames = new List(); List flowLogEntries = new List(); for (LogEntry__c logEntry : this.logEntries) { - if (logEntry.OriginType__c == 'Flow' && String.isNotBlank(logEntry.OriginLocation__c)) { - flowApiNames.add(logEntry.OriginLocation__c); + if (logEntry.OriginSourceMetadataType__c == 'Flow' && String.isNotBlank(logEntry.OriginSourceApiName__c)) { + flowApiNames.add(logEntry.OriginSourceApiName__c); flowLogEntries.add(logEntry); } } @@ -128,11 +153,12 @@ public without sharing class LogEntryHandler extends LoggerSObjectHandler { } for (LogEntry__c logEntry : flowLogEntries) { - FlowDefinitionView flowDefinition = flowApiNameToDefinition.get(logEntry.OriginLocation__c); + FlowDefinitionView flowDefinition = flowApiNameToDefinition.get(logEntry.OriginSourceApiName__c); if (flowDefinition == null) { continue; } + // Older Flow-specific fields logEntry.FlowActiveVersionId__c = flowDefinition.ActiveVersionId; logEntry.FlowDescription__c = flowDefinition.Description; logEntry.FlowDurableId__c = flowDefinition.DurableId; @@ -144,6 +170,10 @@ public without sharing class LogEntryHandler extends LoggerSObjectHandler { logEntry.FlowTriggerOrder__c = flowDefinition.TriggerOrder; logEntry.FlowTriggerSObjectType__c = flowDefinition.TriggerObjectOrEvent?.QualifiedApiName; logEntry.FlowTriggerType__c = flowDefinition.TriggerType; + + // Newer, general-purpose 'origin source' fields + logEntry.OriginSourceId__c = flowDefinition.ActiveVersionId; + logEntry.OriginSourceLastModifiedDate__c = flowDefinition.LastModifiedDate; } } @@ -151,8 +181,8 @@ public without sharing class LogEntryHandler extends LoggerSObjectHandler { List flowActiveVersionIds = new List(); List flowLogEntries = new List(); for (LogEntry__c logEntry : this.logEntries) { - if (logEntry.OriginType__c == 'Flow' && String.isNotBlank(logEntry.FlowActiveVersionId__c)) { - flowActiveVersionIds.add(logEntry.FlowActiveVersionId__c); + if (logEntry.OriginSourceMetadataType__c == 'Flow' && String.isNotBlank(logEntry.OriginSourceId__c)) { + flowActiveVersionIds.add(logEntry.OriginSourceId__c); flowLogEntries.add(logEntry); } } @@ -179,9 +209,13 @@ public without sharing class LogEntryHandler extends LoggerSObjectHandler { continue; } + // Older Flow-specific fields logEntry.FlowVersionApiVersionRuntime__c = 'v' + flowVersionView.ApiVersionRuntime + '.0'; logEntry.FlowVersionRunInMode__c = flowVersionView.RunInMode; logEntry.FlowVersionNumber__c = flowVersionView.VersionNumber; + + // Newer, general-purpose 'origin source' fields + logEntry.OriginSourceApiVersion__c = 'v' + flowVersionView.ApiVersionRuntime + '.0'; } } @@ -266,12 +300,14 @@ public without sharing class LogEntryHandler extends LoggerSObjectHandler { // So, this code handles maintaining some checkbox fields via Apex instead for (LogEntry__c logEntry : this.logEntries) { logEntry.HasDatabaseResultJson__c = logEntry.DatabaseResultJson__c != null; + logEntry.HasExceptionSourceSnippet__c = logEntry.ExceptionSourceSnippet__c != null; logEntry.HasExceptionStackTrace__c = logEntry.ExceptionStackTrace__c != null; logEntry.HasHttpRequestBody__c = logEntry.HttpRequestBody__c != null; logEntry.HasHttpResponseBody__c = logEntry.HttpResponseBody__c != null; logEntry.HasHttpResponseHeaderKeys__c = logEntry.HttpResponseHeaderKeys__c != null; logEntry.HasHttpResponseHeaders__c = logEntry.HttpResponseHeaders__c != null; logEntry.HasInlineTags__c = logEntry.Tags__c != null; + logEntry.HasOriginSourceSnippet__c = logEntry.OriginSourceSnippet__c != null; logEntry.HasRecordJson__c = logEntry.RecordJson__c != null; logEntry.HasRestRequestBody__c = logEntry.RestRequestBody__c != null; logEntry.HasRestRequestHeaderKeys__c = logEntry.RestRequestHeaderKeys__c != null; @@ -361,4 +397,178 @@ public without sharing class LogEntryHandler extends LoggerSObjectHandler { } return sobjectType; } + + // TODO consider making this a top-level, generically-named class (LoggerSourceMetadataSnippet?) + @SuppressWarnings('PMD.ApexDoc, PMD.PropertyNamingConventions') + public class SourceMetadataSnippet { + public String Code { get; private set; } + public String ApiVersion { get; private set; } + public Integer TotalLinesOfCode { get; private set; } + public Integer StartingLineNumber { get; private set; } + public Integer TargetLineNumber { get; private set; } + public Integer EndingLineNumber { get; private set; } + + public transient LoggerStackTrace StackTrace { get; private set; } + // TODO decide if this is needed, it's duplicated from LoggerStackTrace.Language + // But ultimately, the frontend needs to know the language to display in PrismJS + public LoggerStackTrace.SourceLanguage Language { + get { + return this.StackTrace?.Language; + } + } + + public SourceMetadataSnippet(LoggerStackTrace stackTrace, Schema.ApexClass apexClass) { + this(stackTrace, apexClass.Body, apexClass.ApiVersion); + } + + public SourceMetadataSnippet(LoggerStackTrace stackTrace, Schema.ApexTrigger apexTrigger) { + this(stackTrace, apexTrigger.Body, apexTrigger.ApiVersion); + } + + private SourceMetadataSnippet(LoggerStackTrace stackTrace, String sourceCode, Decimal sourceApiVersion) { + this.ApiVersion = 'v' + sourceApiVersion; + this.StackTrace = stackTrace; + + List allCodeLines = sourceCode.split('\n'); + this.setLineNumbers(stackTrace.Source.LineNumber, allCodeLines); + this.setCode(allCodeLines); + } + + private List setLineNumbers(Integer targetLineNumber, List allCodeLines) { + Integer numberOfCodeLines = allCodeLines.size(); + + // TODO consider making these values configurable with 2 new LoggerParameter__mdt records + final Integer targetLeadingLines = 8; + final Integer targetTrailingLines = 4; + + Integer calculcatedStartingLineNumber = targetLineNumber - targetLeadingLines; + if (calculcatedStartingLineNumber <= 0) { + calculcatedStartingLineNumber = 1; + } + + Integer calculatedEndingLineNumber = targetLineNumber + targetTrailingLines; + if (calculatedEndingLineNumber > numberOfCodeLines) { + calculatedEndingLineNumber = numberOfCodeLines; + } + + this.TotalLinesOfCode = numberOfCodeLines; + this.StartingLineNumber = calculcatedStartingLineNumber; + this.TargetLineNumber = targetLineNumber; + this.EndingLineNumber = calculatedEndingLineNumber; + + return allCodeLines; + } + + private void setCode(List allCodeLines) { + List snippetCodeLines = new List(); + for (Integer targetLineNumber = this.startingLineNumber; targetLineNumber <= this.endingLineNumber; targetLineNumber++) { + // Code lines start with 1, but indexes in arrays start with 0 + Integer targetLineNumberIndex = targetLineNumber - 1; + snippetCodeLines.add(allCodeLines.get(targetLineNumberIndex)); + } + + this.Code = String.join(snippetCodeLines, '\n'); + } + } + + // Helper classes that are used to apply field values for each entry's related ApexClass or ApexTrigger + @SuppressWarnings('PMD.ApexDoc') + private abstract class ApexMetadataFieldApplier { + public abstract void apply(LogEntry__c logEntry, Schema.ApexClass topLevelApexClass); + public abstract void apply(LogEntry__c logEntry, Schema.ApexTrigger apexTrigger); + } + + @SuppressWarnings('PMD.ApexDoc') + private class ExceptionApexMetadataFieldApplier extends ApexMetadataFieldApplier { + public override void apply(LogEntry__c logEntry, Schema.ApexClass apexClass) { + if (apexClass == null) { + return; + } + + logEntry.ExceptionSourceApiVersion__c = 'v' + apexClass.ApiVersion; + logEntry.ExceptionSourceCreatedById__c = apexClass.CreatedById; + logEntry.ExceptionSourceCreatedByUsername__c = apexClass.CreatedBy.Username; + logEntry.ExceptionSourceCreatedDate__c = apexClass.CreatedDate; + logEntry.ExceptionSourceId__c = apexClass.Id; + logEntry.ExceptionSourceLastModifiedById__c = apexClass.LastModifiedById; + logEntry.ExceptionSourceLastModifiedByUsername__c = apexClass.LastModifiedBy.Username; + logEntry.ExceptionSourceLastModifiedDate__c = apexClass.LastModifiedDate; + + LoggerStackTrace exceptionStackTrace = new LoggerStackTrace(logEntry.ExceptionStackTrace__c); + logEntry.ExceptionSourceSnippet__c = JSON.serializePretty(new SourceMetadataSnippet(exceptionStackTrace, apexClass)); + } + + public override void apply(LogEntry__c logEntry, Schema.ApexTrigger apexTrigger) { + if (apexTrigger == null) { + return; + } + + logEntry.ExceptionSourceApiVersion__c = 'v' + apexTrigger.ApiVersion; + logEntry.ExceptionSourceCreatedById__c = apexTrigger.CreatedById; + logEntry.ExceptionSourceCreatedByUsername__c = apexTrigger.CreatedBy.Username; + logEntry.ExceptionSourceCreatedDate__c = apexTrigger.CreatedDate; + logEntry.ExceptionSourceId__c = apexTrigger.Id; + logEntry.ExceptionSourceLastModifiedById__c = apexTrigger.LastModifiedById; + logEntry.ExceptionSourceLastModifiedByUsername__c = apexTrigger.LastModifiedBy.Username; + logEntry.ExceptionSourceLastModifiedDate__c = apexTrigger.LastModifiedDate; + + LoggerStackTrace exceptionStackTrace = new LoggerStackTrace(logEntry.ExceptionStackTrace__c); + logEntry.ExceptionSourceSnippet__c = JSON.serializePretty(new SourceMetadataSnippet(exceptionStackTrace, apexTrigger)); + } + } + + @SuppressWarnings('PMD.ApexDoc') + private class OriginApexMetadataFieldApplier extends ApexMetadataFieldApplier { + public override void apply(LogEntry__c logEntry, Schema.ApexClass apexClass) { + if (apexClass == null) { + return; + } + + LoggerStackTrace originStackTrace = new LoggerStackTrace(logEntry.StackTrace__c); + + // Older ApexClass-specific fields + logEntry.ApexClassApiVersion__c = 'v' + apexClass.ApiVersion; + logEntry.ApexClassCreatedDate__c = apexClass.CreatedDate; + logEntry.ApexClassId__c = apexClass.Id; + logEntry.ApexClassLastModifiedDate__c = apexClass.LastModifiedDate; + if (originStackTrace.Source.MetadataType == LoggerStackTrace.SourceMetadataType.ApexClass) { + logEntry.ApexClassName__c = originStackTrace.Source.ApiName; + // TODO need to revisit how to best handle ApexInnerClassName__c + // logEntry.ApexInnerClassName__c = originStackTraceApexClass?.InnerClassName; + logEntry.ApexMethodName__c = originStackTrace.Source.ActionName; + } + + // Newer, general-purpose 'origin source' fields + logEntry.OriginSourceActionName__c = originStackTrace.Source.ActionName; + logEntry.OriginSourceApiVersion__c = 'v' + apexClass.ApiVersion; + logEntry.OriginSourceCreatedById__c = apexClass.CreatedById; + logEntry.OriginSourceCreatedByUsername__c = apexClass.CreatedBy.Username; + logEntry.OriginSourceCreatedDate__c = apexClass.CreatedDate; + logEntry.OriginSourceId__c = apexClass.Id; + logEntry.OriginSourceLastModifiedById__c = apexClass.LastModifiedById; + logEntry.OriginSourceLastModifiedByUsername__c = apexClass.LastModifiedBy.Username; + logEntry.OriginSourceLastModifiedDate__c = apexClass.LastModifiedDate; + logEntry.OriginSourceSnippet__c = JSON.serializePretty(new SourceMetadataSnippet(originStackTrace, apexClass)); + } + + public override void apply(LogEntry__c logEntry, Schema.ApexTrigger apexTrigger) { + if (apexTrigger == null) { + return; + } + + LoggerStackTrace originStackTrace = new LoggerStackTrace(logEntry.StackTrace__c); + + // Currently, there aren't any ApexTrigger-specific fields on LogEntry__c, + // so only set the newer, general-purpose 'origin source' fields + logEntry.OriginSourceApiVersion__c = 'v' + apexTrigger.ApiVersion; + logEntry.OriginSourceCreatedById__c = apexTrigger.CreatedById; + logEntry.OriginSourceCreatedByUsername__c = apexTrigger.CreatedBy.Username; + logEntry.OriginSourceCreatedDate__c = apexTrigger.CreatedDate; + logEntry.OriginSourceId__c = apexTrigger.Id; + logEntry.OriginSourceLastModifiedById__c = apexTrigger.LastModifiedById; + logEntry.OriginSourceLastModifiedByUsername__c = apexTrigger.LastModifiedBy.Username; + logEntry.OriginSourceLastModifiedDate__c = apexTrigger.LastModifiedDate; + logEntry.OriginSourceSnippet__c = JSON.serializePretty(new SourceMetadataSnippet(originStackTrace, apexTrigger)); + } + } } diff --git a/nebula-logger/core/main/log-management/classes/LogEntryMetadataViewerController.cls b/nebula-logger/core/main/log-management/classes/LogEntryMetadataViewerController.cls new file mode 100644 index 000000000..64e3bb8dc --- /dev/null +++ b/nebula-logger/core/main/log-management/classes/LogEntryMetadataViewerController.cls @@ -0,0 +1,97 @@ +//------------------------------------------------------------------------------------------------// +// 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 LWC `logEntryMetadataViewer` + */ +public without sharing class LogEntryMetadataViewerController { + /** + * @description Returns an instance of the inner class `LogEntryMetadataViewerController.LogEntryMetadata`, + * which contains information about the log entry's origin and exception Apex classes + * @param recordId The `ID` of the `LogEntry__c` record + * @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 + return metadata; + } + + LogEntry__c logEntry = LogManagementDataSelector.getInstance().getLogEntryById(recordId); + + String sourceApiName; + LoggerStackTrace.SourceMetadataType sourceMetadataType; + switch on sourceMetadata { + when 'Exception' { + sourceApiName = logEntry.ExceptionSourceApiName__c; + sourceMetadataType = String.isBlank(logEntry.ExceptionSourceMetadataType__c) + ? null + : LoggerStackTrace.SourceMetadataType.valueOf(logEntry.ExceptionSourceMetadataType__c); + } + when 'Origin' { + sourceApiName = logEntry.OriginSourceApiName__c; + sourceMetadataType = String.isBlank(logEntry.OriginSourceMetadataType__c) + ? null + : LoggerStackTrace.SourceMetadataType.valueOf(logEntry.OriginSourceMetadataType__c); + } + } + + if (sourceMetadataType == null || String.isBlank(sourceApiName)) { + return metadata; + } + + querySourceMetadata(logEntry, metadata, sourceMetadataType, sourceApiName); + + return metadata; + } + + @SuppressWarnings('PMD.ExcessiveParameterList') + private static void querySourceMetadata( + LogEntry__c logEntry, + LogEntryMetadata logEntryMetadata, + LoggerStackTrace.SourceMetadataType sourceMetadataType, + String sourceApiName + ) { + Set sourceApiNames = new Set{ sourceApiName }; + List metadataRecords; + Schema.SObjectField codeBodyField; + Schema.SObjectField lastModifiedDateField; + switch on sourceMetadataType { + when ApexClass { + codeBodyField = Schema.ApexClass.Body; + lastModifiedDateField = Schema.ApexClass.LastModifiedDate; + metadataRecords = LogManagementDataSelector.getInstance().getApexClasses(sourceApiNames); + } + when ApexTrigger { + codeBodyField = Schema.ApexTrigger.Body; + lastModifiedDateField = Schema.ApexTrigger.LastModifiedDate; + metadataRecords = LogManagementDataSelector.getInstance().getApexTriggers(sourceApiNames); + } + } + + if (codeBodyField == null || metadataRecords == null || metadataRecords.isEmpty()) { + return; + } + + SObject metadataRecord = metadataRecords.get(0); + + 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) + @SuppressWarnings('PMD.ApexDoc, PMD.PropertyNamingConventions') + public class LogEntryMetadata { + @AuraEnabled + public String Code { get; set; } + @AuraEnabled + public Boolean HasCodeBeenModified { get; set; } + } +} diff --git a/nebula-logger/core/main/log-management/classes/LogEntryMetadataViewerController.cls-meta.xml b/nebula-logger/core/main/log-management/classes/LogEntryMetadataViewerController.cls-meta.xml new file mode 100644 index 000000000..df13efa80 --- /dev/null +++ b/nebula-logger/core/main/log-management/classes/LogEntryMetadataViewerController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + Active + diff --git a/nebula-logger/core/main/log-management/classes/LogManagementDataSelector.cls b/nebula-logger/core/main/log-management/classes/LogManagementDataSelector.cls index e6a9624a3..ef1d8890e 100644 --- a/nebula-logger/core/main/log-management/classes/LogManagementDataSelector.cls +++ b/nebula-logger/core/main/log-management/classes/LogManagementDataSelector.cls @@ -53,19 +53,37 @@ public without sharing virtual class LogManagementDataSelector { * @param apexClassNames The names of the Apex classes to query * @return `List` containing any matching records */ - public virtual List getApexClasses(List apexClassNames) { + public virtual List getApexClasses(Set apexClassNames) { if (LoggerParameter.QUERY_APEX_CLASS_DATA == false) { return new List(); } return [ - SELECT ApiVersion, CreatedById, CreatedDate, Id, LastModifiedById, LastModifiedDate, Name + SELECT ApiVersion, Body, CreatedById, CreatedBy.Username, CreatedDate, Id, LastModifiedById, LastModifiedBy.Username, LastModifiedDate, Name FROM ApexClass WHERE Name IN :apexClassNames ORDER BY NamespacePrefix NULLS LAST ]; } + /** + * @description Returns a list of `ApexTrigger` records + * @param apexTriggerNames The names of the Apex triggers to query + * @return `List` containing any matching records + */ + public virtual List getApexTriggers(Set apexTriggerNames) { + if (LoggerParameter.QUERY_APEX_TRIGGER_DATA == false) { + return new List(); + } + + return [ + SELECT ApiVersion, Body, CreatedById, CreatedBy.Username, CreatedDate, Id, LastModifiedById, LastModifiedBy.Username, LastModifiedDate, Name + FROM ApexTrigger + WHERE Name IN :apexTriggerNames + ORDER BY NamespacePrefix NULLS LAST + ]; + } + /** * @description Returns a cached copy of the `ApexEmailNotification` records in the org * @return The cached `List` records @@ -202,6 +220,21 @@ public without sharing virtual class LogManagementDataSelector { return (Log__c) Database.query(String.escapeSingleQuotes(query)); } + /** + * @description Returns a `LogEntry__c` record + * @param logEntryId The `ID` of the `LogEntry__c` record to query + * @return The matching `LogEntry__c` record + */ + public virtual LogEntry__c getLogEntryById(Id logEntryId) { + String queryTemplate = 'SELECT {0} FROM {1} WHERE Id = :logEntryId'; + + List logEntryFieldNames = new List(Schema.LogEntry__c.SObjectType.getDescribe().fields.getMap().keySet()); + List textReplacements = new List{ String.join(logEntryFieldNames, ','), Schema.LogEntry__c.SObjectType.getDescribe().getName() }; + + String query = String.format(queryTemplate, textReplacements); + return (LogEntry__c) Database.query(String.escapeSingleQuotes(query)); + } + /** * @description Returns a `List` records for the specified `Log__c` ID * @param logId The `ID` of the `Log__c` record of the `LogEntry__c` records to query diff --git a/nebula-logger/core/main/log-management/customindex/LogEntry__c.ExceptionLocation__c.indx-meta.xml b/nebula-logger/core/main/log-management/customindex/LogEntry__c.ExceptionLocation__c.indx-meta.xml new file mode 100644 index 000000000..4b67a08a0 --- /dev/null +++ b/nebula-logger/core/main/log-management/customindex/LogEntry__c.ExceptionLocation__c.indx-meta.xml @@ -0,0 +1,4 @@ + + + true + diff --git a/nebula-logger/core/main/log-management/flexipages/LogEntryRecordPage.flexipage-meta.xml b/nebula-logger/core/main/log-management/flexipages/LogEntryRecordPage.flexipage-meta.xml index e76aa72ae..913700c0f 100644 --- a/nebula-logger/core/main/log-management/flexipages/LogEntryRecordPage.flexipage-meta.xml +++ b/nebula-logger/core/main/log-management/flexipages/LogEntryRecordPage.flexipage-meta.xml @@ -34,24 +34,21 @@ Region - - - - uiBehavior - required - - Record.Log__c - RecordLog__cField - - uiBehavior readonly - Record.TransactionEntryNumber__c - RecordTransactionEntryNumber__cField + Record.MessageTruncated__c + RecordMessageTruncated__cField + + + {!Record.MessageTruncated__c} + EQUAL + true + + @@ -60,48 +57,80 @@ uiBehavior readonly - Record.LoggedByUsernameLink__c - RecordLoggedByUsernameLink__cField + Record.MessageMasked__c + RecordMessageMasked__cField + + + {!Record.MessageMasked__c} + EQUAL + true + + uiBehavior - readonly + none - Record.Timestamp__c - RecordTimestamp__cField + Record.Message__c + RecordMessage__cField uiBehavior - readonly + none - Record.EntryScenario__c - RecordEntryScenario__cField + Record.StackTrace__c + RecordStackTrace_cField - {!Record.EntryScenarioLink__c} - NE + {!Record.HasStackTrace__c} + EQUAL + true - Facet-cb40f95d-9915-4ba5-815c-f3e53bcc4001 + Facet-30caba7c-11da-4321-bccd-e97e901d03f4 + Facet + + + + + + body + Facet-30caba7c-11da-4321-bccd-e97e901d03f4 + + flexipage:column + flexipage_column3 + + + Facet-3a75f17f-589a-49f4-bdec-38f730e94676 Facet + + + + uiBehavior + none + + Record.OriginSourceApiName__c + RecordOriginSourceApiName_cField2 + + uiBehavior readonly - Record.LoggingLevelWithImage__c - RecordLoggingLevelWithImage__cField + Record.FlowLabel__c + RecordFlowLabel__cField @@ -110,8 +139,8 @@ uiBehavior readonly - Record.Origin__c - RecordOrigin__cField + Record.FlowProcessType__c + RecordFlowProcessType__cField @@ -120,31 +149,52 @@ uiBehavior readonly - Record.Trigger__c - RecordTrigger__cField - - - {!Record.TriggerIsExecuting__c} - EQUAL - true - - + Record.FlowTriggerType__c + RecordFlowTriggerType__cField + + + + + + uiBehavior + none + + Record.FlowTriggerSObjectType__c + RecordFlowTriggerSObjectType_cField + + + + + + uiBehavior + none + + Record.FlowTriggerOrder__c + RecordFlowTriggerOrder_cField + + + + + + uiBehavior + none + + Record.FlowRecordTriggerType__c + RecordFlowRecordTriggerType_cField + Facet-17586de5-b418-46c3-8e43-07f6a5e7321a + Facet + + uiBehavior readonly - Record.UniqueId__c - RecordUniqueId_cField - - - {!Record.UniqueId__c} - NE - - + Record.FlowVersionRunInMode__c + RecordFlowVersionRunInMode__cField @@ -153,17 +203,21 @@ uiBehavior readonly - Record.EventUuid__c - RecordEventUuid__cField - - - {!Record.EventUuid__c} - NE - - + Record.FlowVersionApiVersionRuntime__c + RecordFlowVersionApiVersionRuntime__cField + + + + + + uiBehavior + readonly + + Record.FlowVersionNumber__c + RecordFlowVersionNumber__cField - Facet-f419a303-8d53-44e5-8647-63a464804568 + Facet-f9b96f42-358a-4ffe-bc2c-a20f42bd59b8 Facet @@ -171,23 +225,23 @@ body - Facet-cb40f95d-9915-4ba5-815c-f3e53bcc4001 + Facet-17586de5-b418-46c3-8e43-07f6a5e7321a flexipage:column - flexipage_column + flexipage_column8 body - Facet-f419a303-8d53-44e5-8647-63a464804568 + Facet-f9b96f42-358a-4ffe-bc2c-a20f42bd59b8 flexipage:column - flexipage_column2 + flexipage_column9 - Facet-506803d6-0468-4ac1-a044-ede56cf84c05 + Facet-5f20b7f6-e40f-49b2-905b-5e2ca6c609e9 Facet @@ -197,28 +251,28 @@ uiBehavior none - Record.Message__c - RecordMessage__cField + Record.OriginSourceApiName__c + RecordOriginSourceApiName_cField uiBehavior - readonly + none - Record.MessageTruncated__c - RecordMessageTruncated__cField + Record.OriginSourceActionName__c + RecordOriginSourceActionName_cField uiBehavior - readonly + none - Record.MessageMasked__c - RecordMessageMasked__cField + Record.OriginSourceCreatedByLink__c + RecordOriginSourceCreatedByLink_cField @@ -227,18 +281,55 @@ uiBehavior none - Record.StackTrace__c - RecordStackTrace__cField - - - {!Record.HasStackTrace__c} - EQUAL - true - - + Record.OriginSourceCreatedDate__c + RecordOriginSourceCreatedDate_cField - Facet-30caba7c-11da-4321-bccd-e97e901d03f4 + Facet-65bc1c15-e5f3-4330-9bf7-c91d56d272f9 + Facet + + + + + + uiBehavior + none + + Record.OriginSourceMetadataType__c + RecordOriginSourceType_cField + + + + + + uiBehavior + none + + Record.OriginSourceApiVersion__c + RecordOriginSourceApiVersion_cField + + + + + + uiBehavior + none + + Record.OriginSourceLastModifiedByLink__c + RecordOriginSourceLastModifiedByLink_cField + + + + + + uiBehavior + none + + Record.OriginSourceLastModifiedDate__c + RecordOriginSourceLastModifiedDate_cField + + + Facet-400f4865-a661-422c-8e16-4eba064be4ad Facet @@ -246,13 +337,23 @@ body - Facet-30caba7c-11da-4321-bccd-e97e901d03f4 + Facet-65bc1c15-e5f3-4330-9bf7-c91d56d272f9 flexipage:column - flexipage_column3 + flexipage_column21 - Facet-3a75f17f-589a-49f4-bdec-38f730e94676 + + + + body + Facet-400f4865-a661-422c-8e16-4eba064be4ad + + flexipage:column + flexipage_column2 + + + Facet-c174106c-7ba2-4781-a50f-001c5fa0c38a Facet @@ -283,7 +384,7 @@ none Record.ExceptionStackTrace__c - RecordExceptionStackTrace__cField + RecordExceptionStackTrace_cField {!Record.HasExceptionStackTrace__c} @@ -317,55 +418,41 @@ uiBehavior readonly - Record.DatabaseResultCollectionType__c - RecordDatabaseResultCollectionType__cField + Record.ExceptionSourceApiName__c + RecordExceptionSourceApiName_cField uiBehavior - readonly + none - Record.DatabaseResultType__c - RecordDatabaseResultType__cField + Record.ExceptionSourceActionName__c + RecordExceptionSourceActionName_cField uiBehavior - readonly + none - Record.DatabaseResultCollectionSize__c - RecordDatabaseResultCollectionSize_cField1 + Record.ExceptionSourceCreatedByLink__c + RecordExceptionSourceCreatedByLink_cField uiBehavior - readonly + none - Record.DatabaseResultJson__c - RecordDatabaseResultJson__cField + Record.ExceptionSourceCreatedDate__c + RecordExceptionSourceCreatedDate_cField - Facet-839d3f77-0b78-43a9-9477-6b63126dcf12 - Facet - - - - - - body - Facet-839d3f77-0b78-43a9-9477-6b63126dcf12 - - flexipage:column - flexipage_column11 - - - Facet-b0b8c0ec-36e3-469b-9106-00d34530d793 + Facet-b31ccc94-fd23-40e9-aa1c-3fe3b1b5b56f Facet @@ -373,128 +460,228 @@ uiBehavior - readonly + none - Record.RecordCollectionType__c - RecordRecordCollectionType__cField + Record.ExceptionSourceMetadataType__c + RecordExceptionSourceMetadataType_cField uiBehavior - readonly + none - Record.RecordSObjectType__c - RecordRecordSObjectType__cField + Record.ExceptionSourceApiVersion__c + RecordExceptionSourceApiVersion_cField uiBehavior - readonly + none - Record.RecordCollectionSize__c - RecordRecordCollectionSize_cField1 + Record.ExceptionSourceLastModifiedByLink__c + RecordExceptionSourceLastModifiedByLink_cField uiBehavior - readonly + none - Record.RecordId__c - RecordRecordId__cField + Record.ExceptionSourceLastModifiedDate__c + RecordExceptionSourceLastModifiedDate_cField + + + Facet-28bcdefa-7228-496e-8459-446e879ab522 + Facet + + + + + + body + Facet-b31ccc94-fd23-40e9-aa1c-3fe3b1b5b56f + + flexipage:column + flexipage_column24 + + + + + + body + Facet-28bcdefa-7228-496e-8459-446e879ab522 + + flexipage:column + flexipage_column25 + + + Facet-7952b55d-c8c4-4c6d-aaf4-0c11b5bed24e + Facet + + + + + + columns + Facet-3a75f17f-589a-49f4-bdec-38f730e94676 + + + horizontalAlignment + false + + + label + Message Details + + flexipage:fieldSection + flexipage_fieldSection2 + + + + + + sourceMetadata + Origin + + logEntryMetadataViewer + c_logEntryMetadataViewer - {!Record.HasRecordId__c} + {!Record.HasOriginSourceSnippet__c} EQUAL true - - - - - - uiBehavior - readonly - - Record.RecordSObjectClassification__c - RecordRecordSObjectClassification__cField - + - - - uiBehavior - readonly - - Record.RecordSObjectTypeNamespace__c - RecordRecordSObjectTypeNamespace__cField - + + + columns + Facet-5f20b7f6-e40f-49b2-905b-5e2ca6c609e9 + + + horizontalAlignment + false + + + label + Flow Information + + flexipage:fieldSection + flexipage_fieldSection5 + + + {!Record.OriginType__c} + EQUAL + Flow + + + - - - uiBehavior - readonly - - Record.RecordLink__c - RecordRecordLink__cField - + + + columns + Facet-c174106c-7ba2-4781-a50f-001c5fa0c38a + + + horizontalAlignment + false + + + label + Origin Source Details + + flexipage:fieldSection + flexipage_fieldSection15 + + 1 AND 2 + + {!Record.OriginSourceApiName__c} + NE + + + {!Record.OriginType__c} + NE + Flow + + + - - - uiBehavior - readonly - - Record.RecordJson__c - RecordRecordJson__cField + + + columns + Facet-2a15cd64-78af-4727-8c6d-2913cb00a278 + + + horizontalAlignment + false + + + label + Exception Details + + flexipage:fieldSection + flexipage_fieldSection6 - {!Record.HasRecordJson__c} + {!Record.HasException__c} EQUAL true - + - - - uiBehavior - readonly - - Record.RecordJsonMasked__c - RecordRecordJsonMasked__cField + + + sourceMetadata + Exception + + logEntryMetadataViewer + c_logEntryMetadataViewer2 - {!Record.HasRecordJson__c} + {!Record.HasExceptionSourceSnippet__c} EQUAL true - + - Facet-1da92bf7-f9ed-4842-b880-7625370f3c70 - Facet - - - body - Facet-1da92bf7-f9ed-4842-b880-7625370f3c70 + columns + Facet-7952b55d-c8c4-4c6d-aaf4-0c11b5bed24e - flexipage:column - flexipage_column12 + + horizontalAlignment + false + + + label + Exception Source Details + + flexipage:fieldSection + flexipage_fieldSection17 + + + {!Record.HasException__c} + EQUAL + true + + - Facet-f6d04c17-cbcd-40c6-aa58-c46313ecd8c4 + Facet-7beef2bc-28d3-490f-96b8-7a8f85424802 Facet @@ -504,8 +691,8 @@ uiBehavior readonly - Record.HttpRequestEndpoint__c - RecordHttpRequestEndpoint_cField1 + Record.LimitsAggregateQueries__c + RecordLimitsAggregateQueries__cField @@ -514,8 +701,8 @@ uiBehavior readonly - Record.HttpRequestMethod__c - RecordHttpRequestMethod_cField1 + Record.LimitsAsyncCalls__c + RecordLimitsAsyncCalls__cField @@ -524,8 +711,8 @@ uiBehavior readonly - Record.HttpRequestCompressed__c - RecordHttpRequestCompressed_cField1 + Record.LimitsCallouts__c + RecordLimitsCallouts__cField @@ -534,8 +721,8 @@ uiBehavior readonly - Record.HttpRequestBody__c - RecordHttpRequestBody_cField1 + Record.LimitsCpuTime__c + RecordLimitsCpuTime__cField @@ -544,36 +731,18 @@ uiBehavior readonly - Record.HttpRequestBodyMasked__c - RecordHttpRequestBodyMasked_cField1 + Record.LimitsDmlRows__c + RecordLimitsDmlRows__cField - Facet-4cea44fd-c4f1-41ed-bada-eace6229f158 - Facet - - - - - - body - Facet-4cea44fd-c4f1-41ed-bada-eace6229f158 - - flexipage:column - flexipage_column1 - - - Facet-25eaa065-25af-4efc-9ce5-9d289a0fc3f0 - Facet - - uiBehavior readonly - Record.HttpResponseStatusCode__c - RecordHttpResponseStatusCode_cField1 + Record.LimitsDmlStatements__c + RecordLimitsDmlStatements__cField @@ -582,8 +751,8 @@ uiBehavior readonly - Record.HttpResponseStatus__c - RecordHttpResponseStatus_cField1 + Record.LimitsEmailInvocations__c + RecordLimitsEmailInvocations__cField @@ -592,32 +761,22 @@ uiBehavior readonly - Record.HttpResponseHeaderKeys__c - RecordHttpResponseHeaderKeys_cField1 - - - {!Record.HasHttpResponseHeaders__c} - EQUAL - false - - + Record.LimitsFutureCalls__c + RecordLimitsFutureCalls__cField + Facet-8e5572ea-8a8e-4426-8154-004cbbcb1314 + Facet + + uiBehavior readonly - Record.HttpResponseHeaders__c - RecordHttpResponseHeaders_cField1 - - - {!Record.HasHttpResponseHeaders__c} - EQUAL - true - - + Record.LimitsHeapSize__c + RecordLimitsHeapSize__cField @@ -626,8 +785,8 @@ uiBehavior readonly - Record.HttpResponseBody__c - RecordHttpResponseBody_cField1 + Record.LimitsMobilePushApexCalls__c + RecordLimitsMobilePushApexCalls__cField @@ -636,147 +795,107 @@ uiBehavior readonly - Record.HttpResponseBodyMasked__c - RecordHttpResponseBodyMasked_cField1 - - - Facet-1f2467a3-2a30-4aad-ace3-80afc0a517d2 - Facet - - - - - - body - Facet-1f2467a3-2a30-4aad-ace3-80afc0a517d2 - - flexipage:column - flexipage_column15 - - - Facet-6395ceae-5694-468a-911f-714456910736 - Facet - - - - - - uiBehavior - none - - Record.RestRequestResourcePath__c - RecordRestRequestResourcePath_cField - - - - - - uiBehavior - none - - Record.RestRequestUri__c - RecordRestRequestUri_cField + Record.LimitsPublishImmediateDmlStatements__c + RecordLimitsPublishImmediateDmlStatements__cField uiBehavior - none + readonly - Record.RestRequestMethod__c - RecordRestRequestMethod_cField + Record.LimitsQueueableJobs__c + RecordLimitsQueueableJobs__cField uiBehavior - none + readonly - Record.RestRequestRemoteAddress__c - RecordRestRequestRemoteAddress_cField + Record.LimitsSoqlQueries__c + RecordLimitsSoqlQueries__cField uiBehavior - none + readonly - Record.RestRequestParameters__c - RecordRestRequestParameters_cField + Record.LimitsSoqlQueryLocatorRows__c + RecordLimitsSoqlQueryLocatorRows__cField uiBehavior - none + readonly - Record.RestRequestHeaderKeys__c - RecordRestRequestHeaderKeys_cField - - - {!Record.HasRestRequestHeaders__c} - EQUAL - false - - + Record.LimitsSoqlQueryRows__c + RecordLimitsSoqlQueryRows__cField uiBehavior - none + readonly - Record.RestRequestHeaders__c - RecordRestRequestHeaders_cField - - - {!Record.HasRestRequestHeaderKeys__c} - EQUAL - true - - + Record.LimitsSoslSearches__c + RecordLimitsSoslSearches__cField + Facet-ce9617b4-fac3-4077-93b0-d2ced40ad0b1 + Facet + + - - - uiBehavior - none - - Record.RestRequestBody__c - RecordRestRequestBody_cField - + + + body + Facet-8e5572ea-8a8e-4426-8154-004cbbcb1314 + + flexipage:column + flexipage_column13 + - - - uiBehavior - none - - Record.RestRequestBodyMasked__c - RecordRestRequestBodyMasked_cField - + + + body + Facet-ce9617b4-fac3-4077-93b0-d2ced40ad0b1 + + flexipage:column + flexipage_column14 + - Facet-6411b37c-0886-4766-a9c5-5ede0e60cb97 + Facet-4fa5f475-f3a7-4ac0-b1cd-8285d7b369c7 Facet - body - Facet-6411b37c-0886-4766-a9c5-5ede0e60cb97 + columns + Facet-4fa5f475-f3a7-4ac0-b1cd-8285d7b369c7 - flexipage:column - flexipage_column20 + + horizontalAlignment + false + + + label + Transaction Limits + + flexipage:fieldSection + flexipage_fieldSection9 - Facet-f29c9a43-fb99-4d37-be66-925bb2bbeecb + Facet-c02369d8-c1c8-498e-b87b-eb5300fe4f5b Facet @@ -784,42 +903,34 @@ uiBehavior - none + readonly - Record.RestResponseStatusCode__c - RecordRestResponseStatusCode_cField + Record.RecordLink__c + RecordRecordLink__cField uiBehavior - none + readonly - Record.RestResponseHeaderKeys__c - RecordRestResponseHeaderKeys_cField - - - {!Record.HasHttpResponseHeaders__c} - EQUAL - false - - + Record.RecordSObjectClassification__c + RecordRecordSObjectClassification__cField uiBehavior - none + readonly - Record.RestResponseHeaders__c - RecordRestResponseHeaders_cField + Record.RecordSObjectTypeNamespace__c + RecordRecordSObjectTypeNamespace__cField - {!Record.HasRestResponseHeaders__c} - EQUAL - true + {!Record.RecordSObjectTypeNamespace__c} + NE @@ -828,23 +939,47 @@ uiBehavior - none + readonly - Record.RestResponseBody__c - RecordRestResponseBody_cField + Record.RecordCollectionSize__c + RecordRecordCollectionSize_cField1 uiBehavior - none + readonly - Record.RestResponseBodyMasked__c - RecordRestResponseBodyMasked_cField + Record.RecordJsonMasked__c + RecordRecordJsonMasked__cField + + + {!Record.RecordJsonMasked__c} + EQUAL + true + + - Facet-c8e76a2d-dfaa-4163-b3cf-bbe5fa9cbec8 + + + + uiBehavior + readonly + + Record.RecordJson__c + RecordRecordJson__cField + + + {!Record.HasRecordJson__c} + EQUAL + true + + + + + Facet-1da92bf7-f9ed-4842-b880-7625370f3c70 Facet @@ -852,39 +987,48 @@ body - Facet-c8e76a2d-dfaa-4163-b3cf-bbe5fa9cbec8 + Facet-1da92bf7-f9ed-4842-b880-7625370f3c70 flexipage:column - flexipage_column19 + flexipage_column12 - Facet-2181992b-5c3d-435c-a152-f9b0147323c2 + Facet-f6d04c17-cbcd-40c6-aa58-c46313ecd8c4 Facet - columns - Facet-506803d6-0468-4ac1-a044-ede56cf84c05 - - - horizontalAlignment - false + decorate + true - label - Information + richTextValue + <p style="text-align: center;"><strong>No Related Records Data Available for This Log Entry</strong></p><p style="text-align: center;"><br></p><p style="text-align: center;"><span class="ql-cursor"></span><a href="https://ideas.salesforce.com/s/idea/a0B8W00000H6VzyUAF/allow-conditional-tabs-for-lightning-app-builder-tab-component" rel="noopener noreferrer" target="_blank">Upvote this Idea</a> to make this experience better</p> - flexipage:fieldSection - flexipage_fieldSection + flexipage:richText + flexipage_richText2 + + 1 AND 2 + + {!Record.RecordId__c} + EQUAL + + + {!Record.HasRecordJson__c} + EQUAL + false + + columns - Facet-3a75f17f-589a-49f4-bdec-38f730e94676 + Facet-f6d04c17-cbcd-40c6-aa58-c46313ecd8c4 horizontalAlignment @@ -892,75 +1036,7 @@ label - Message Details - - flexipage:fieldSection - flexipage_fieldSection2 - - - - - - columns - Facet-2a15cd64-78af-4727-8c6d-2913cb00a278 - - - horizontalAlignment - false - - - label - Exception Information - - flexipage:fieldSection - flexipage_fieldSection6 - - - {!Record.HasException__c} - EQUAL - true - - - - - - - - columns - Facet-b0b8c0ec-36e3-469b-9106-00d34530d793 - - - horizontalAlignment - false - - - label - Database Result - - flexipage:fieldSection - flexipage_fieldSection7 - - - {!Record.HasDatabaseResult__c} - EQUAL - true - - - - - - - - columns - Facet-f6d04c17-cbcd-40c6-aa58-c46313ecd8c4 - - - horizontalAlignment - false - - - label - Related Record + Related Record Details flexipage:fieldSection flexipage_fieldSection8 @@ -979,85 +1055,86 @@ + Facet-dxcl7nfj6iq + Facet + + - - - columns - Facet-25eaa065-25af-4efc-9ce5-9d289a0fc3f0 - - - horizontalAlignment - false - - - label - HTTP Callout Request - - flexipage:fieldSection - flexipage_fieldSection1 - - - {!Record.HttpRequestEndpoint__c} - NE - - - + + + uiBehavior + readonly + + Record.DatabaseResultCollectionType__c + RecordDatabaseResultCollectionType__cField + + + + + + uiBehavior + readonly + + Record.DatabaseResultType__c + RecordDatabaseResultType__cField + + + + + + uiBehavior + readonly + + Record.DatabaseResultCollectionSize__c + RecordDatabaseResultCollectionSize_cField1 + + + + + + uiBehavior + readonly + + Record.DatabaseResultJson__c + RecordDatabaseResultJson__cField + + Facet-839d3f77-0b78-43a9-9477-6b63126dcf12 + Facet + + - columns - Facet-6395ceae-5694-468a-911f-714456910736 - - - horizontalAlignment - false - - - label - HTTP Callout Response + body + Facet-839d3f77-0b78-43a9-9477-6b63126dcf12 - flexipage:fieldSection - flexipage_fieldSection10 - - 1 OR 2 OR 3 - - {!Record.HttpResponseStatus__c} - NE - - - {!Record.HasHttpResponseBody__c} - EQUAL - true - - - {!Record.HasHttpResponseHeaderKeys__c} - EQUAL - true - - + flexipage:column + flexipage_column11 + Facet-b0b8c0ec-36e3-469b-9106-00d34530d793 + Facet + + - columns - Facet-f29c9a43-fb99-4d37-be66-925bb2bbeecb - - - horizontalAlignment - false + decorate + true - label - Apex REST Service Request + richTextValue + <p style="text-align: center;"><strong>No Database Results Data Available for This Log Entry</strong></p><p style="text-align: center;"><br></p><p style="text-align: center;"><span class="ql-cursor"></span><a href="https://ideas.salesforce.com/s/idea/a0B8W00000H6VzyUAF/allow-conditional-tabs-for-lightning-app-builder-tab-component" rel="noopener noreferrer" target="_blank">Upvote this Idea</a> to make this experience better</p> - flexipage:fieldSection - flexipage_fieldSection14 + flexipage:richText + flexipage_richText3 - {!Record.RestRequestResourcePath__c} - NE + {!Record.HasDatabaseResult__c} + EQUAL + false @@ -1066,7 +1143,7 @@ columns - Facet-2181992b-5c3d-435c-a152-f9b0147323c2 + Facet-b0b8c0ec-36e3-469b-9106-00d34530d793 horizontalAlignment @@ -1074,31 +1151,20 @@ label - Apex REST Service Response + Database Result Details flexipage:fieldSection - flexipage_fieldSection13 + flexipage_fieldSection7 - 1 OR 2 OR 3 - - {!Record.HasRestResponseBody__c} - EQUAL - true - - - {!Record.HasRestResponseHeaderKeys__c} - EQUAL - true - - {!Record.HasRestResponseHeaders__c} + {!Record.HasDatabaseResult__c} EQUAL true - Facet-7beef2bc-28d3-490f-96b8-7a8f85424802 + Facet-79a52362-9213-4bd3-9eb2-a054b03f85ba Facet @@ -1108,8 +1174,8 @@ uiBehavior readonly - Record.LimitsAggregateQueries__c - RecordLimitsAggregateQueries__cField + Record.HttpRequestEndpoint__c + RecordHttpRequestEndpoint_cField1 @@ -1118,8 +1184,8 @@ uiBehavior readonly - Record.LimitsAsyncCalls__c - RecordLimitsAsyncCalls__cField + Record.HttpRequestMethod__c + RecordHttpRequestMethod_cField1 @@ -1128,8 +1194,8 @@ uiBehavior readonly - Record.LimitsCallouts__c - RecordLimitsCallouts__cField + Record.HttpRequestCompressed__c + RecordHttpRequestCompressed_cField1 @@ -1138,8 +1204,8 @@ uiBehavior readonly - Record.LimitsCpuTime__c - RecordLimitsCpuTime__cField + Record.HttpRequestBody__c + RecordHttpRequestBody_cField1 @@ -1148,28 +1214,36 @@ uiBehavior readonly - Record.LimitsDmlRows__c - RecordLimitsDmlRows__cField + Record.HttpRequestBodyMasked__c + RecordHttpRequestBodyMasked_cField1 + Facet-4cea44fd-c4f1-41ed-bada-eace6229f158 + Facet + + - - - uiBehavior - readonly - - Record.LimitsDmlStatements__c - RecordLimitsDmlStatements__cField - + + + body + Facet-4cea44fd-c4f1-41ed-bada-eace6229f158 + + flexipage:column + flexipage_column1 + + Facet-25eaa065-25af-4efc-9ce5-9d289a0fc3f0 + Facet + + uiBehavior readonly - Record.LimitsEmailInvocations__c - RecordLimitsEmailInvocations__cField + Record.HttpResponseStatusCode__c + RecordHttpResponseStatusCode_cField1 @@ -1178,23 +1252,26 @@ uiBehavior readonly - Record.LimitsFutureCalls__c - RecordLimitsFutureCalls__cField + Record.HttpResponseStatus__c + RecordHttpResponseStatus_cField1 - Facet-8e5572ea-8a8e-4426-8154-004cbbcb1314 - Facet - - uiBehavior readonly - Record.LimitsHeapSize__c - RecordLimitsHeapSize__cField - + Record.HttpResponseHeaderKeys__c + RecordHttpResponseHeaderKeys_cField1 + + + {!Record.HasHttpResponseHeaders__c} + EQUAL + false + + + @@ -1202,8 +1279,15 @@ uiBehavior readonly - Record.LimitsMobilePushApexCalls__c - RecordLimitsMobilePushApexCalls__cField + Record.HttpResponseHeaders__c + RecordHttpResponseHeaders_cField1 + + + {!Record.HasHttpResponseHeaders__c} + EQUAL + true + + @@ -1212,8 +1296,8 @@ uiBehavior readonly - Record.LimitsPublishImmediateDmlStatements__c - RecordLimitsPublishImmediateDmlStatements__cField + Record.HttpResponseBody__c + RecordHttpResponseBody_cField1 @@ -1222,134 +1306,523 @@ uiBehavior readonly - Record.LimitsQueueableJobs__c - RecordLimitsQueueableJobs__cField + Record.HttpResponseBodyMasked__c + RecordHttpResponseBodyMasked_cField1 + Facet-1f2467a3-2a30-4aad-ace3-80afc0a517d2 + Facet + + + + + + body + Facet-1f2467a3-2a30-4aad-ace3-80afc0a517d2 + + flexipage:column + flexipage_column15 + + + Facet-6395ceae-5694-468a-911f-714456910736 + Facet + + + + + + decorate + true + + + richTextValue + <p style="text-align: center;"><strong>No HTTP Callout Data Available for This Log Entry</strong></p><p style="text-align: center;"><br></p><p style="text-align: center;"><span class="ql-cursor"></span><a href="https://ideas.salesforce.com/s/idea/a0B8W00000H6VzyUAF/allow-conditional-tabs-for-lightning-app-builder-tab-component" rel="noopener noreferrer" target="_blank">Upvote this Idea</a> to make this experience better</p> + + flexipage:richText + flexipage_richText + + 1 AND 2 AND 3 AND 4 + + {!Record.HttpRequestEndpoint__c} + EQUAL + + + {!Record.HasHttpResponseBody__c} + EQUAL + false + + + {!Record.HasHttpResponseHeaderKeys__c} + EQUAL + false + + + {!Record.HasHttpResponseHeaders__c} + EQUAL + false + + + + + + + + columns + Facet-25eaa065-25af-4efc-9ce5-9d289a0fc3f0 + + + horizontalAlignment + false + + + label + HTTP Callout Request + + flexipage:fieldSection + flexipage_fieldSection1 + + + {!Record.HttpRequestEndpoint__c} + NE + + + + + + + + columns + Facet-6395ceae-5694-468a-911f-714456910736 + + + horizontalAlignment + false + + + label + HTTP Callout Response + + flexipage:fieldSection + flexipage_fieldSection10 + + 1 OR 2 OR 3 + + {!Record.HttpResponseStatus__c} + NE + + + {!Record.HasHttpResponseBody__c} + EQUAL + true + + + {!Record.HasHttpResponseHeaderKeys__c} + EQUAL + true + + + + + Facet-8ec06805-0396-4b22-b718-23193da39f19 + Facet + + uiBehavior - readonly + none - Record.LimitsSoqlQueries__c - RecordLimitsSoqlQueries__cField + Record.RestRequestResourcePath__c + RecordRestRequestResourcePath_cField uiBehavior - readonly + none - Record.LimitsSoqlQueryLocatorRows__c - RecordLimitsSoqlQueryLocatorRows__cField + Record.RestRequestUri__c + RecordRestRequestUri_cField uiBehavior - readonly + none - Record.LimitsSoqlQueryRows__c - RecordLimitsSoqlQueryRows__cField + Record.RestRequestMethod__c + RecordRestRequestMethod_cField uiBehavior - readonly + none - Record.LimitsSoslSearches__c - RecordLimitsSoslSearches__cField + Record.RestRequestRemoteAddress__c + RecordRestRequestRemoteAddress_cField - Facet-ce9617b4-fac3-4077-93b0-d2ced40ad0b1 - Facet - - + + + + uiBehavior + none + + Record.RestRequestParameters__c + RecordRestRequestParameters_cField + + + + + + uiBehavior + none + + Record.RestRequestHeaderKeys__c + RecordRestRequestHeaderKeys_cField + + + {!Record.HasRestRequestHeaders__c} + EQUAL + false + + + + + + + + uiBehavior + none + + Record.RestRequestHeaders__c + RecordRestRequestHeaders_cField + + + {!Record.HasRestRequestHeaderKeys__c} + EQUAL + true + + + + + + + + uiBehavior + none + + Record.RestRequestBody__c + RecordRestRequestBody_cField + + + + + + uiBehavior + none + + Record.RestRequestBodyMasked__c + RecordRestRequestBodyMasked_cField + + + Facet-6411b37c-0886-4766-a9c5-5ede0e60cb97 + Facet + + + + + + body + Facet-6411b37c-0886-4766-a9c5-5ede0e60cb97 + + flexipage:column + flexipage_column20 + + + Facet-f29c9a43-fb99-4d37-be66-925bb2bbeecb + Facet + + + + + + uiBehavior + none + + Record.RestResponseStatusCode__c + RecordRestResponseStatusCode_cField + + + + + + uiBehavior + none + + Record.RestResponseHeaderKeys__c + RecordRestResponseHeaderKeys_cField + + + {!Record.HasHttpResponseHeaders__c} + EQUAL + false + + + + + + + + uiBehavior + none + + Record.RestResponseHeaders__c + RecordRestResponseHeaders_cField + + + {!Record.HasRestResponseHeaders__c} + EQUAL + true + + + + + + + + uiBehavior + none + + Record.RestResponseBody__c + RecordRestResponseBody_cField + + + + + + uiBehavior + none + + Record.RestResponseBodyMasked__c + RecordRestResponseBodyMasked_cField + + + Facet-c8e76a2d-dfaa-4163-b3cf-bbe5fa9cbec8 + Facet + + + + + + body + Facet-c8e76a2d-dfaa-4163-b3cf-bbe5fa9cbec8 + + flexipage:column + flexipage_column19 + + + Facet-2181992b-5c3d-435c-a152-f9b0147323c2 + Facet + + + + + + decorate + true + + + richTextValue + <p style="text-align: center;"><strong>No Apex REST Data Available for This Log Entry</strong></p><p style="text-align: center;"><br></p><p style="text-align: center;"><span class="ql-cursor"></span><a href="https://ideas.salesforce.com/s/idea/a0B8W00000H6VzyUAF/allow-conditional-tabs-for-lightning-app-builder-tab-component" rel="noopener noreferrer" target="_blank">Upvote this Idea</a> to make this experience better</p> + + flexipage:richText + flexipage_richText5 + + 1 AND 2 AND 3 AND 4 + + {!Record.RestRequestResourcePath__c} + EQUAL + + + {!Record.HasRestResponseBody__c} + EQUAL + false + + + {!Record.HasRestResponseHeaderKeys__c} + EQUAL + false + + + {!Record.HasRestResponseHeaders__c} + EQUAL + false + + + + + + + + columns + Facet-f29c9a43-fb99-4d37-be66-925bb2bbeecb + + + horizontalAlignment + false + + + label + Apex REST Service Request + + flexipage:fieldSection + flexipage_fieldSection14 + + + {!Record.RestRequestResourcePath__c} + NE + + + + + + + + columns + Facet-2181992b-5c3d-435c-a152-f9b0147323c2 + + + horizontalAlignment + false + + + label + Apex REST Service Response + + flexipage:fieldSection + flexipage_fieldSection13 + + 1 OR 2 OR 3 + + {!Record.HasRestResponseBody__c} + EQUAL + true + + + {!Record.HasRestResponseHeaderKeys__c} + EQUAL + true + + + {!Record.HasRestResponseHeaders__c} + EQUAL + true + + + + + Facet-e22db3df-2c92-4784-89d6-ea64fa3a0e25 + Facet + + + + + + active + true + + + body + Facet-7beef2bc-28d3-490f-96b8-7a8f85424802 + + + title + Standard.Tab.detail + + flexipage:tab + flexipage_tab2 + + + + active + false + body - Facet-8e5572ea-8a8e-4426-8154-004cbbcb1314 + Facet-c02369d8-c1c8-498e-b87b-eb5300fe4f5b - flexipage:column - flexipage_column13 + + title + Transaction Limits + + flexipage:tab + flexipage_tab1 body - Facet-ce9617b4-fac3-4077-93b0-d2ced40ad0b1 + Facet-dxcl7nfj6iq - flexipage:column - flexipage_column14 + + title + Related Records + + flexipage:tab + flexipage_tab3 - Facet-4fa5f475-f3a7-4ac0-b1cd-8285d7b369c7 - Facet - - - columns - Facet-4fa5f475-f3a7-4ac0-b1cd-8285d7b369c7 - - - horizontalAlignment - false + body + Facet-79a52362-9213-4bd3-9eb2-a054b03f85ba - label - Limits + title + Database Results - flexipage:fieldSection - flexipage_fieldSection9 + flexipage:tab + flexipage_tab4 - Facet-c02369d8-c1c8-498e-b87b-eb5300fe4f5b - Facet - - - - active - true - body - Facet-7beef2bc-28d3-490f-96b8-7a8f85424802 + Facet-8ec06805-0396-4b22-b718-23193da39f19 title - Standard.Tab.detail + HTTP Callout flexipage:tab - flexipage_tab2 + flexipage_tab5 - - active - false - body - Facet-c02369d8-c1c8-498e-b87b-eb5300fe4f5b + Facet-e22db3df-2c92-4784-89d6-ea64fa3a0e25 title - Limits + Apex REST flexipage:tab - flexipage_tab1 + flexipage_tab6 Facet-ec9d7630-d727-4e4a-9a1a-3ebabfc68c45 @@ -1410,8 +1883,8 @@ uiBehavior readonly - Record.ApexClassName__c - RecordApexClassName__cField + Record.Timestamp__c + RecordTimestamp__cField @@ -1420,76 +1893,44 @@ uiBehavior readonly - Record.ApexInnerClassName__c - RecordApexInnerClassName__cField - - - {!Record.ApexInnerClassName__c} - NE - - + Record.LoggingLevelWithImage__c + RecordLoggingLevelWithImage__cField - Facet-eb0dd886-ca25-4094-b405-b0653a844c4c - Facet - - uiBehavior readonly - Record.ApexMethodName__c - RecordApexMethodName__cField + Record.LoggedByUsernameLink__c + RecordLoggedByUsernameLink__cField uiBehavior - readonly + required - Record.ApexClassApiVersion__c - RecordApexClassApiVersion__cField + Record.Log__c + RecordLog__cField - Facet-a59c58c4-ed12-445a-b526-b1fdb0225dfd - Facet - - - - - - body - Facet-eb0dd886-ca25-4094-b405-b0653a844c4c - - flexipage:column - flexipage_column4 - - - - - - body - Facet-a59c58c4-ed12-445a-b526-b1fdb0225dfd - - flexipage:column - flexipage_column5 - - - Facet-a417c39b-b395-4d13-a87d-2d5bbb9d9d77 - Facet - - uiBehavior readonly - Record.FlowLabel__c - RecordFlowLabel__cField + Record.EventUuid__c + RecordEventUuid__cField + + + {!Record.EventUuid__c} + NE + + @@ -1498,8 +1939,14 @@ uiBehavior readonly - Record.FlowProcessType__c - RecordFlowProcessType__cField + Record.EntryScenario__c + RecordEntryScenario__cField + + + {!Record.EntryScenarioLink__c} + NE + + @@ -1508,41 +1955,62 @@ uiBehavior readonly - Record.FlowTriggerType__c - RecordFlowTriggerType__cField + Record.Trigger__c + RecordTrigger__cField + + + {!Record.TriggerIsExecuting__c} + EQUAL + true + + + Facet-cb40f95d-9915-4ba5-815c-f3e53bcc4001 + Facet + + - - - uiBehavior - none - - Record.FlowTriggerSObjectType__c - RecordFlowTriggerSObjectType_cField - + + + body + Facet-cb40f95d-9915-4ba5-815c-f3e53bcc4001 + + flexipage:column + flexipage_column + + Facet-506803d6-0468-4ac1-a044-ede56cf84c05 + Facet + + uiBehavior - none + readonly - Record.FlowTriggerOrder__c - RecordFlowTriggerOrder_cField + Record.ApexClassName__c + RecordApexClassName__cField uiBehavior - none + readonly - Record.FlowRecordTriggerType__c - RecordFlowRecordTriggerType_cField + Record.ApexInnerClassName__c + RecordApexInnerClassName__cField + + + {!Record.ApexInnerClassName__c} + NE + + - Facet-17586de5-b418-46c3-8e43-07f6a5e7321a + Facet-eb0dd886-ca25-4094-b405-b0653a844c4c Facet @@ -1552,18 +2020,8 @@ uiBehavior readonly - Record.FlowVersionRunInMode__c - RecordFlowVersionRunInMode__cField - - - - - - uiBehavior - readonly - - Record.FlowVersionApiVersionRuntime__c - RecordFlowVersionApiVersionRuntime__cField + Record.ApexMethodName__c + RecordApexMethodName__cField @@ -1572,11 +2030,11 @@ uiBehavior readonly - Record.FlowVersionNumber__c - RecordFlowVersionNumber__cField + Record.ApexClassApiVersion__c + RecordApexClassApiVersion__cField - Facet-f9b96f42-358a-4ffe-bc2c-a20f42bd59b8 + Facet-a59c58c4-ed12-445a-b526-b1fdb0225dfd Facet @@ -1584,23 +2042,23 @@ body - Facet-17586de5-b418-46c3-8e43-07f6a5e7321a + Facet-eb0dd886-ca25-4094-b405-b0653a844c4c flexipage:column - flexipage_column8 + flexipage_column4 body - Facet-f9b96f42-358a-4ffe-bc2c-a20f42bd59b8 + Facet-a59c58c4-ed12-445a-b526-b1fdb0225dfd flexipage:column - flexipage_column9 + flexipage_column5 - Facet-5f20b7f6-e40f-49b2-905b-5e2ca6c609e9 + Facet-a417c39b-b395-4d13-a87d-2d5bbb9d9d77 Facet @@ -1823,7 +2281,7 @@ columns - Facet-a417c39b-b395-4d13-a87d-2d5bbb9d9d77 + Facet-506803d6-0468-4ac1-a044-ede56cf84c05 horizontalAlignment @@ -1831,29 +2289,17 @@ label - Apex Class Information + Information flexipage:fieldSection - flexipage_fieldSection3 - - 1 AND 2 - - {!Record.OriginType__c} - EQUAL - Apex - - - {!Record.ApexClassName__c} - NE - - + flexipage_fieldSection columns - Facet-5f20b7f6-e40f-49b2-905b-5e2ca6c609e9 + Facet-a417c39b-b395-4d13-a87d-2d5bbb9d9d77 horizontalAlignment @@ -1861,15 +2307,24 @@ label - Flow Information + Apex Class Information flexipage:fieldSection - flexipage_fieldSection5 + flexipage_fieldSection3 + 1 AND 2 AND 3 {!Record.OriginType__c} EQUAL - Flow + Apex + + + {!Record.ApexClassName__c} + NE + + + {!Record.OriginSourceApiName__c} + EQUAL @@ -1891,11 +2346,16 @@ flexipage:fieldSection flexipage_fieldSection4 + 1 AND 2 {!Record.OriginType__c} EQUAL Component + + {!Record.OriginSourceApiName__c} + EQUAL + diff --git a/nebula-logger/core/main/log-management/lwc/logEntryMetadataViewer/logEntryMetadataViewer.html b/nebula-logger/core/main/log-management/lwc/logEntryMetadataViewer/logEntryMetadataViewer.html new file mode 100644 index 000000000..c43a6df18 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/logEntryMetadataViewer/logEntryMetadataViewer.html @@ -0,0 +1,90 @@ + + + diff --git a/nebula-logger/core/main/log-management/lwc/logEntryMetadataViewer/logEntryMetadataViewer.js b/nebula-logger/core/main/log-management/lwc/logEntryMetadataViewer/logEntryMetadataViewer.js new file mode 100644 index 000000000..e849baf26 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/logEntryMetadataViewer/logEntryMetadataViewer.js @@ -0,0 +1,166 @@ +/************************************************************************************************* + * 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. * + ************************************************************************************************/ + +import { LightningElement, api, wire } from 'lwc'; +import { getRecord, getFieldValue } from 'lightning/uiRecordApi'; +import getMetadata from '@salesforce/apex/LogEntryMetadataViewerController.getMetadata'; + +import LOG_ENTRY_OBJECT from '@salesforce/schema/LogEntry__c'; +import EXCEPTION_SOURCE_API_NAME_FIELD from '@salesforce/schema/LogEntry__c.ExceptionSourceApiName__c'; +import EXCEPTION_SOURCE_API_VERSION_FIELD from '@salesforce/schema/LogEntry__c.ExceptionSourceApiVersion__c'; +import EXCEPTION_SOURCE_METADATA_TYPE_FIELD from '@salesforce/schema/LogEntry__c.ExceptionSourceMetadataType__c'; +import EXCEPTION_SOURCE_SNIPPET_FIELD from '@salesforce/schema/LogEntry__c.ExceptionSourceSnippet__c'; +import ORIGIN_SOURCE_API_NAME_FIELD from '@salesforce/schema/LogEntry__c.OriginSourceApiName__c'; +import ORIGIN_SOURCE_API_VERSION_FIELD from '@salesforce/schema/LogEntry__c.OriginSourceApiVersion__c'; +import ORIGIN_SOURCE_METADATA_TYPE_FIELD from '@salesforce/schema/LogEntry__c.OriginSourceMetadataType__c'; +import ORIGIN_SOURCE_SNIPPET_FIELD from '@salesforce/schema/LogEntry__c.OriginSourceSnippet__c'; + +const LOG_ENTRY_FIELDS = [ + EXCEPTION_SOURCE_API_NAME_FIELD, + EXCEPTION_SOURCE_API_VERSION_FIELD, + EXCEPTION_SOURCE_METADATA_TYPE_FIELD, + EXCEPTION_SOURCE_SNIPPET_FIELD, + ORIGIN_SOURCE_API_NAME_FIELD, + ORIGIN_SOURCE_API_VERSION_FIELD, + ORIGIN_SOURCE_METADATA_TYPE_FIELD, + ORIGIN_SOURCE_SNIPPET_FIELD +]; + +export default class LogEntryMetadataViewer extends LightningElement { + @api recordId; + @api sourceMetadata; + + objectApiName = LOG_ENTRY_OBJECT; + sourceSnippet; + + showFullSourceMetadataModal = false; + showSourceMetadataModifiedWarning = false; + fullSourceMetadataTitle; + fullSourceMetadata; + + _logEntry; + _logEntryMetadata; + + get hasLoaded() { + return !!this._logEntry && !!this._logEntryMetadata; + } + + get sectionTitle() { + if (this.sourceMetadata === 'Exception') { + return 'Exception Source Metadata'; + } else if (this.sourceMetadata === 'Origin') { + return 'Origin Source Metadata'; + } + return ''; + } + + get hasFullSourceMetadata() { + return !!this._logEntryMetadata?.Code; + } + + get fullSourceModalNotificationClasses() { + const classNames = ['slds-notify', 'slds-notify_alert']; + classNames.push(this._logEntryMetadata?.HasCodeBeenModified ? 'slds-alert_warning' : 'slds-alert_offline'); + return classNames.join(' '); + } + + get fullSourceModalNotificationIcon() { + return this._logEntryMetadata?.HasCodeBeenModified ? 'utility:warning' : 'utility:success'; + } + + get fullSourceModalNotificationMessage() { + return this._logEntryMetadata?.HasCodeBeenModified + ? 'This Apex code has been modified since this log entry was generated.' + : 'This Apex code has not been modified since this log entry was generated.'; + } + + @wire(getMetadata, { + recordId: '$recordId', + sourceMetadata: '$sourceMetadata' + }) + wiredGetLogEntryMetadata({ error, data }) { + if (data) { + this._logEntryMetadata = data; + } else if (error) { + this._logEntryMetadata = undefined; + } + } + + @wire(getRecord, { + recordId: '$recordId', + fields: LOG_ENTRY_FIELDS + }) + async wiredGetLogEntry({ data }) { + if (data) { + this._logEntry = data; + + const { sourceApiNameField, sourceApiVersionField, sourceMetadataTypeField, sourceSnippetField } = this._getSourceFields(); + const sourceSnippetJson = getFieldValue(this._logEntry, sourceSnippetField); + + if (!sourceSnippetJson) { + return; + } + + const sourceMetadataType = getFieldValue(this._logEntry, sourceMetadataTypeField); + const sourceExtension = this._getSourceExtension(sourceMetadataType); + + const sourceApiName = getFieldValue(this._logEntry, sourceApiNameField); + const sourceApiVersion = getFieldValue(this._logEntry, sourceApiVersionField); + const sourceName = `${sourceApiName}.${sourceExtension} - ${sourceApiVersion}`; + this.sourceSnippet = { ...JSON.parse(sourceSnippetJson), ...{ Title: sourceName } }; + } + } + + handleShowFullSourceMetadataModal() { + this.showSourceMetadataModifiedWarning = this._logEntryMetadata.HasCodeBeenModified; + this.fullSourceMetadata = { ...this.sourceSnippet, Code: this._logEntryMetadata.Code }; + this.fullSourceMetadataTitle = 'Full Source: ' + this.fullSourceMetadata.Title; + this.showFullSourceMetadataModal = true; + } + + handleHideFullSourceMetadataModal() { + this.showFullSourceMetadataModal = false; + } + + handleKeyDown(event) { + if (event.code === 'Escape') { + this.handleHideFullSourceMetadataModal(); + } + } + + _getSourceFields() { + let sourceApiNameField; + let sourceApiVersionField; + let sourceMetadataTypeField; + let sourceSnippetField; + if (this.sourceMetadata === 'Exception') { + sourceApiNameField = EXCEPTION_SOURCE_API_NAME_FIELD; + sourceApiVersionField = EXCEPTION_SOURCE_API_VERSION_FIELD; + sourceMetadataTypeField = EXCEPTION_SOURCE_METADATA_TYPE_FIELD; + sourceSnippetField = EXCEPTION_SOURCE_SNIPPET_FIELD; + } else if (this.sourceMetadata === 'Origin') { + sourceApiNameField = ORIGIN_SOURCE_API_NAME_FIELD; + sourceApiVersionField = ORIGIN_SOURCE_API_VERSION_FIELD; + sourceMetadataTypeField = ORIGIN_SOURCE_METADATA_TYPE_FIELD; + sourceSnippetField = ORIGIN_SOURCE_SNIPPET_FIELD; + } + return { sourceApiNameField, sourceApiVersionField, sourceMetadataTypeField, sourceSnippetField }; + } + + _getSourceExtension(sourceMetadataType) { + let sourceExtension; + switch (sourceMetadataType) { + case 'ApexClass': + sourceExtension = 'cls'; + break; + case 'ApexTrigger': + sourceExtension = 'trigger'; + break; + default: + sourceExtension = ''; + } + return sourceExtension; + } +} diff --git a/nebula-logger/core/main/log-management/lwc/logEntryMetadataViewer/logEntryMetadataViewer.js-meta.xml b/nebula-logger/core/main/log-management/lwc/logEntryMetadataViewer/logEntryMetadataViewer.js-meta.xml new file mode 100644 index 000000000..d63c11819 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/logEntryMetadataViewer/logEntryMetadataViewer.js-meta.xml @@ -0,0 +1,15 @@ + + + 60.0 + true + Nebula Logger: Log Entry Metadata Viewer + lightning__RecordPage + + + + LogEntry__c + + + + + diff --git a/nebula-logger/core/main/log-management/lwc/logViewer/__tests__/logViewer.test.js b/nebula-logger/core/main/log-management/lwc/logViewer/__tests__/logViewer.test.js index 75766e1a8..668465748 100644 --- a/nebula-logger/core/main/log-management/lwc/logViewer/__tests__/logViewer.test.js +++ b/nebula-logger/core/main/log-management/lwc/logViewer/__tests__/logViewer.test.js @@ -6,6 +6,24 @@ const MOCK_GET_LOG = require('./data/LogViewerController.getLog.json'); document.execCommand = jest.fn(); +jest.mock( + 'lightning/platformResourceLoader', + () => { + return { + loadScript() { + return new Promise((resolve, _) => { + global.Prism = require('../../../staticresources/LoggerResources/prism.js'); + resolve(); + }); + }, + loadStyle() { + // No-op for now + return Promise.resolve(); + } + }; + }, + { virtual: true } +); jest.mock( '@salesforce/apex/LogViewerController.getLog', () => { @@ -62,7 +80,7 @@ describe('Logger JSON Viewer lwc tests', () => { expect(tab.value).toEqual('json'); tab.dispatchEvent(new CustomEvent('active')); await Promise.resolve('resolves dispatchEvent() for tab'); - const clipboardContent = JSON.parse(logViewer.shadowRoot.querySelector('pre').textContent); + const clipboardContent = JSON.parse(logViewer.shadowRoot.querySelector('c-logger-code-viewer').code); const reconstructedLog = { ...MOCK_GET_LOG.log }; reconstructedLog[MOCK_GET_LOG.logEntriesRelationshipName] = [...MOCK_GET_LOG.logEntries]; expect(clipboardContent).toEqual(reconstructedLog); @@ -92,7 +110,7 @@ describe('Logger JSON Viewer lwc tests', () => { expectedContentLines.push(columns.join('\n')); }); - const clipboardContent = logViewer.shadowRoot.querySelector('pre').textContent; + const clipboardContent = logViewer.shadowRoot.querySelector('c-logger-code-viewer').code; expect(clipboardContent).toEqual(expectedContentLines.join('\n\n' + '-'.repeat(36) + '\n\n')); expect(document.execCommand).toHaveBeenCalledWith('copy'); }); diff --git a/nebula-logger/core/main/log-management/lwc/logViewer/logViewer.css b/nebula-logger/core/main/log-management/lwc/logViewer/logViewer.css index d35f572f0..5314b11c9 100644 --- a/nebula-logger/core/main/log-management/lwc/logViewer/logViewer.css +++ b/nebula-logger/core/main/log-management/lwc/logViewer/logViewer.css @@ -1,9 +1,4 @@ .content { height: calc(100vh - 550px); -} - -.content pre { - height: 100%; - margin: auto 10px; overflow-y: scroll; } diff --git a/nebula-logger/core/main/log-management/lwc/logViewer/logViewer.html b/nebula-logger/core/main/log-management/lwc/logViewer/logViewer.html index 4a0c00dae..a60225b77 100644 --- a/nebula-logger/core/main/log-management/lwc/logViewer/logViewer.html +++ b/nebula-logger/core/main/log-management/lwc/logViewer/logViewer.html @@ -14,12 +14,12 @@
-
{currentMode.data}
+
-
{currentMode.data}
+
diff --git a/nebula-logger/core/main/log-management/lwc/loggerCodeViewer/__tests__/loggerCodeViewer.test.js b/nebula-logger/core/main/log-management/lwc/loggerCodeViewer/__tests__/loggerCodeViewer.test.js new file mode 100644 index 000000000..f3887fdf2 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/loggerCodeViewer/__tests__/loggerCodeViewer.test.js @@ -0,0 +1,60 @@ +import { createElement } from 'lwc'; +import LoggerCodeViewer from 'c/loggerCodeViewer'; + +jest.mock( + 'lightning/platformResourceLoader', + () => { + return { + loadScript() { + return new Promise((resolve, _) => { + global.Prism = require('../../../staticresources/LoggerResources/prism.js'); + resolve(); + }); + }, + loadStyle() { + // No-op for now + return Promise.resolve(); + } + }; + }, + { virtual: true } +); + +describe('c-logger-code-viewer', () => { + afterEach(() => { + while (document.body.firstChild) { + document.body.removeChild(document.body.firstChild); + } + jest.clearAllMocks(); + }); + + it('displays Prism code viewer with provided attributes', async () => { + const element = createElement('c-logger-code-viewer', { + is: LoggerCodeViewer + }); + element.language = 'cobol'; + element.startingLineNumber = '10'; + element.targetLineNumber = '18'; + let mockCode = ''; + for (let i = 0; i < element.targetLineNumber + 5; i++) { + mockCode += 'some line of "code", line number is ' + (i + 1) + '\n'; + } + element.code = mockCode; + + document.body.appendChild(element); + + await Promise.resolve('rerender after loading Prism script and style from static resource'); + await Promise.resolve("rerender after generating the code element's innerHTML and running Prism.highlightAll()"); + const prismCodeViewer = element.shadowRoot.querySelector('div.prism-viewer'); + expect(prismCodeViewer.classList.contains('line-numbers')).toBeTruthy(); + const prismCodeViewerPre = element.shadowRoot.querySelector('div.prism-viewer pre'); + expect(prismCodeViewerPre).toBeTruthy(); + expect(prismCodeViewerPre.dataset.start).toBe(element.startingLineNumber); + expect(prismCodeViewerPre.dataset.line).toBe(element.targetLineNumber); + expect(prismCodeViewerPre.dataset.lineOffset).toBe(element.targetLineNumber); + const prismCodeViewerCode = element.shadowRoot.querySelector('div.prism-viewer pre code'); + expect(prismCodeViewerCode).toBeTruthy(); + expect(prismCodeViewerCode.classList.contains('language-' + element.language)).toBeTruthy(); + expect(prismCodeViewerCode.innerHTML).toContain(mockCode); + }); +}); diff --git a/nebula-logger/core/main/log-management/lwc/loggerCodeViewer/loggerCodeViewer.css b/nebula-logger/core/main/log-management/lwc/loggerCodeViewer/loggerCodeViewer.css new file mode 100644 index 000000000..f93e2d044 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/loggerCodeViewer/loggerCodeViewer.css @@ -0,0 +1,4 @@ +:host { + --sds-c-card-color-background: #2d2d2d; + --slds-c-card-text-color: #ccc; +} diff --git a/nebula-logger/core/main/log-management/lwc/loggerCodeViewer/loggerCodeViewer.html b/nebula-logger/core/main/log-management/lwc/loggerCodeViewer/loggerCodeViewer.html new file mode 100644 index 000000000..b21ca2961 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/loggerCodeViewer/loggerCodeViewer.html @@ -0,0 +1,21 @@ + + + diff --git a/nebula-logger/core/main/log-management/lwc/loggerCodeViewer/loggerCodeViewer.js b/nebula-logger/core/main/log-management/lwc/loggerCodeViewer/loggerCodeViewer.js new file mode 100644 index 000000000..0d3d7f875 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/loggerCodeViewer/loggerCodeViewer.js @@ -0,0 +1,42 @@ +/************************************************************************************************* + * 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. * + ************************************************************************************************/ + +import { LightningElement, api } from 'lwc'; +import { loadScript, loadStyle } from 'lightning/platformResourceLoader'; +import loggerStaticResources from '@salesforce/resourceUrl/LoggerResources'; + +export default class LoggerCodeViewer extends LightningElement { + @api code; + @api language; + @api startingLineNumber; + @api targetLineNumber; + + isLoaded = false; + + _Prism; + + async renderedCallback() { + if (this.isLoaded) { + return; + } + + await Promise.all([loadStyle(this, loggerStaticResources + '/prism.css'), loadScript(this, loggerStaticResources + '/prism.js')]); + + const container = this.template.querySelector('.prism-viewer'); + // data-line and data-line-offset are effectively the same thing within Prism... + // but the core Prism code uses data-start for line numbers, + // and the line-highlight plugin uses data-line-offset for highlighting a line number + // (╯°□°)╯︵ ┻━┻ + // eslint-disable-next-line @lwc/lwc/no-inner-html + container.innerHTML = + `
` +
+            `${this.code}` +
+            `
`; + // eslint-disable-next-line no-undef + this._Prism = Prism; + await this._Prism.highlightAll(); + this.isLoaded = true; + } +} diff --git a/nebula-logger/core/main/log-management/lwc/loggerCodeViewer/loggerCodeViewer.js-meta.xml b/nebula-logger/core/main/log-management/lwc/loggerCodeViewer/loggerCodeViewer.js-meta.xml new file mode 100644 index 000000000..a60b4c157 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/loggerCodeViewer/loggerCodeViewer.js-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + false + diff --git a/nebula-logger/core/main/log-management/lwc/loggerHomeHeader/loggerHomeHeader.html b/nebula-logger/core/main/log-management/lwc/loggerHomeHeader/loggerHomeHeader.html index df46c3b70..2e5aebe8c 100644 --- a/nebula-logger/core/main/log-management/lwc/loggerHomeHeader/loggerHomeHeader.html +++ b/nebula-logger/core/main/log-management/lwc/loggerHomeHeader/loggerHomeHeader.html @@ -64,152 +64,152 @@

-
-

- Nebula Logger -

- - - - - - - -
- - -
-

- Organization -

- - - - - - -
- -
- -
-
- - - - - - - -
-
-
+ + + + + + + + + + + diff --git a/nebula-logger/core/main/log-management/lwc/loggerPageSection/__tests__/loggerPageSection.test.js b/nebula-logger/core/main/log-management/lwc/loggerPageSection/__tests__/loggerPageSection.test.js new file mode 100644 index 000000000..6c7379651 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/loggerPageSection/__tests__/loggerPageSection.test.js @@ -0,0 +1,62 @@ +import { createElement } from 'lwc'; +import LoggerPageSection from 'c/loggerPageSection'; + +describe('c-logger-page-section', () => { + afterEach(() => { + while (document.body.firstChild) { + document.body.removeChild(document.body.firstChild); + } + }); + + test.todo('revisit how to test since data is passed using slots'); + + // it('displays title and content', async () => { + // const element = createElement('c-logger-page-section', { + // is: LoggerPageSection + // }); + // const elementContent = 'some content'; + // const elementTitle = 'some content'; + // element.content = elementContent; + // element.title = elementTitle; + + // document.body.appendChild(element); + + // const toggleIcon = element.shadowRoot.querySelector('lightning-icon'); + // expect(toggleIcon.iconName).toBe('utility:chevrondown'); + // const titleSlot = element.shadowRoot.querySelector('slot[name="title"]'); + // expect(titleSlot.innerHTML).toBe(elementTitle); + // const contentSlot = element.shadowRoot.querySelector('slot[name="content"]'); + // expect(contentSlot.innerHTML).toBe(elementContent); + // }); + + // it('toggles content when title is clicked', async () => { + // const element = createElement('c-logger-page-section', { + // is: LoggerPageSection + // }); + // const elementContent = 'some content'; + // element.content = elementContent; + // document.body.appendChild(element); + // const titleAction = element.shadowRoot.querySelector('button.slds-section__title-action'); + // expect(titleAction).toBeTruthy(); + // let contentSlot = element.shadowRoot.querySelector('slot[name="content"]'); + // expect(contentSlot).toBeTruthy(); + // let isContentExpectedToBeDisplayed = true; + // // Toggle the section content a few times to verify showing & hiding work as expected + // for (let i = 0; i < 5; i++) { + // isContentExpectedToBeDisplayed = !isContentExpectedToBeDisplayed; + + // titleAction.click(); + + // await Promise.resolve('rerender template after button click'); + // const contentContainer = element.shadowRoot.querySelector('.slds-section__content'); + // const toggleIcon = element.shadowRoot.querySelector('lightning-icon'); + // if (isContentExpectedToBeDisplayed) { + // expect(toggleIcon.iconName).toBe('utility:chevrondown'); + // expect(contentContainer.className).not.toContain('slds-hide'); + // } else { + // expect(toggleIcon.iconName).toBe('utility:chevronright'); + // expect(contentContainer.className).toContain('slds-hide'); + // } + // } + // }); +}); diff --git a/nebula-logger/core/main/log-management/lwc/loggerPageSection/loggerPageSection.html b/nebula-logger/core/main/log-management/lwc/loggerPageSection/loggerPageSection.html new file mode 100644 index 000000000..59072c46e --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/loggerPageSection/loggerPageSection.html @@ -0,0 +1,24 @@ + + + diff --git a/nebula-logger/core/main/log-management/lwc/loggerPageSection/loggerPageSection.js b/nebula-logger/core/main/log-management/lwc/loggerPageSection/loggerPageSection.js new file mode 100644 index 000000000..db090c3c8 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/loggerPageSection/loggerPageSection.js @@ -0,0 +1,24 @@ +/************************************************************************************************* + * 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. * + ************************************************************************************************/ + +import { LightningElement } from 'lwc'; + +export default class LoggerPageSection extends LightningElement { + _showContent = true; + + get sectionToggleClass() { + const classNames = ['slds-section__content']; + classNames.push(this._showContent ? 'slds-show' : 'slds-hide'); + return classNames.join(' '); + } + + get sectionToggleIcon() { + return this._showContent ? 'utility:chevrondown' : 'utility:chevronright'; + } + + toggleSection() { + this._showContent = !this._showContent; + } +} diff --git a/nebula-logger/core/main/log-management/lwc/loggerPageSection/loggerPageSection.js-meta.xml b/nebula-logger/core/main/log-management/lwc/loggerPageSection/loggerPageSection.js-meta.xml new file mode 100644 index 000000000..a60b4c157 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/loggerPageSection/loggerPageSection.js-meta.xml @@ -0,0 +1,5 @@ + + + 60.0 + false + diff --git a/nebula-logger/core/main/log-management/lwc/loggerSettings/loggerSettings.html b/nebula-logger/core/main/log-management/lwc/loggerSettings/loggerSettings.html index 657613df9..20030aa23 100644 --- a/nebula-logger/core/main/log-management/lwc/loggerSettings/loggerSettings.html +++ b/nebula-logger/core/main/log-management/lwc/loggerSettings/loggerSettings.html @@ -54,279 +54,279 @@

Logger Settings Details