Skip to content

Commit

Permalink
Added support for populating origin source metadata fields on LogEntr…
Browse files Browse the repository at this point in the history
…y__c for OmniStudio entries
  • Loading branch information
jongpie committed Sep 16, 2024
1 parent 1502dc0 commit 6554537
Show file tree
Hide file tree
Showing 29 changed files with 946 additions and 62 deletions.
479 changes: 433 additions & 46 deletions README.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions docs/apex/Configuration/LoggerParameter.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ Controls if Nebula Logger queries `Schema.Network` data. When set to `false`, an

Controls if Nebula Logger queries `Schema.Network` data is queried synchronously & populated on `LogEntryEvent__e` records. When set to `false`, any `Schema.Network` fields on `LogEntryEvent__e` will not be populated - the data will instead be queried asynchronously and populated on any resulting `Log__c` records. Controlled by the custom metadata record `LoggerParameter.QueryNetworkDataSynchronously`, or `true` as the default

#### `QUERY_OMNI_PROCESS_DATA``Boolean`

Controls if Nebula Logger queries `Schema.OmniProcess` data. When set to `false`, any `Schema.OmniProcess` fields on `LogEntry__c` will not be populated Controlled by the custom metadata record `LoggerParameter.QueryOmniProcessData`, or `true` as the default

#### `QUERY_ORGANIZATION_DATA``Boolean`

Controls if Nebula Logger queries `Schema.Organization` data. When set to `false`, any `Schema.Organization` fields on `LogEntryEvent__e` and `Log__c` will not be populated Controlled by the custom metadata record `LoggerParameter.QueryOrganizationData`, or `true` as the default
Expand Down
20 changes: 20 additions & 0 deletions docs/apex/Log-Management/LogManagementDataSelector.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,26 @@ List<Log\_\_c>

The list of matching `Log__c` records

#### `getOmniProcessProxies(List<Id> omniProcessIds)``Map<Id, LoggerSObjectProxy.OmniProcess>`

Returns a list of matching `Schema.OmniProcess` records based on the provided list of OmniProcess IDs

##### Parameters

| Param | Description |
| ---------------- | --------------------------------------------- |
| `omniProcessIds` | The list of `Schema.OmniProcess` IDs to query |

##### Return

**Type**

Map&lt;Id, LoggerSObjectProxy.OmniProcess&gt;

**Description**

The instance of `Map&lt;Id, SObject&gt;` containing any matching `Schema.OmniProcess` records

#### `getProfilesById(List<Id> profileIds)``List<Schema.Profile>`

Returns a `List&lt;Schema.Profile&gt;` of records with the specified profile IDs
Expand Down
34 changes: 34 additions & 0 deletions docs/apex/Logger-Engine/LoggerSObjectProxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,37 @@ Not all orgs have the SObject `Schema.Network` - it is only present in orgs that
###### `UrlPathPrefix``String`

---

#### LoggerSObjectProxy.OmniProcess class

Not all orgs have the SObject `Schema.OmniProcess` - it is only present in orgs that have enabled OmniStudio, so `Schema.OmniProcess` has to be referenced dynamically, including using hardcoded `String` values for field API names. The `LoggerSObjectProxy.OmniProcess` class acts as a substitute for a `Schema.OmniProcess` record so that the rest of the codebase can rely on strongly-typed references to fields (properties).

---

##### Constructors

###### `OmniProcess(SObject omniProcess)`

---

##### Properties

###### `CreatedBy``Schema.User`

###### `CreatedById``Id`

###### `CreatedDate``Datetime`

###### `Id``String`

###### `LastModifiedBy``Schema.User`

###### `LastModifiedById``Id`

###### `LastModifiedDate``Datetime`

###### `OmniProcessType``String`

###### `UniqueName``String`

---
4 changes: 4 additions & 0 deletions docs/apex/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ layout: default

## Logger Engine

### [CallableLogger](Logger-Engine/CallableLogger)

A class that implements the standard interface `System.Callable`. This provides 2 benefits: 1. A loosely-coupled way to optionally integrate with Nebula Logger (useful for ISVs/package developers). 2. The ability to log in OmniStudio&apos;s OmniScripts &amp; Integration Procedures.

### [ComponentLogger](Logger-Engine/ComponentLogger)

Controller class used by the lightning web component `logger`
Expand Down
15 changes: 15 additions & 0 deletions nebula-logger/core/main/configuration/classes/LoggerParameter.cls
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,21 @@ public class LoggerParameter {
private set;
}

/**
* @description Controls if Nebula Logger queries `Schema.OmniProcess` data.
* When set to `false`, any `Schema.OmniProcess` fields on `LogEntry__c` will not be populated
* Controlled by the custom metadata record `LoggerParameter.QueryOmniProcessData`, or `true` as the default
*/
public static final Boolean QUERY_OMNI_PROCESS_DATA {
get {
if (QUERY_OMNI_PROCESS_DATA == null) {
QUERY_OMNI_PROCESS_DATA = getBoolean('QueryOmniProcessData', true);
}
return QUERY_OMNI_PROCESS_DATA;
}
private set;
}

/**
* @description Controls if Nebula Logger queries `Schema.Network` data.
* When set to `false`, any `Schema.Network` fields on `LogEntryEvent__e` and `Log__c` will not be populated
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<label>Query OmniProcess Data</label>
<protected>false</protected>
<values>
<field>Comments__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>Description__c</field>
<value xsi:type="xsd:string">Note: this parameter only applies to orgs that are using OmniStudio.

When set to &apos;true&apos; (default), the OmniProcess object will be queried to track additional details about the OmniScript or OmniIntegrationProcedure that generated a log entry - the queried data is stored in fields on LogEntry__c.

When set to &apos;false&apos;, the OmniProcess object will not be queried, and the related fields will be null.</value>
</values>
<values>
<field>Value__c</field>
<value xsi:type="xsd:string">true</value>
</values>
</CustomMetadata>
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ public without sharing class LogEntryEventHandler extends LoggerSObjectHandler {
OriginLocation__c = logEntryEvent.OriginLocation__c,
OriginSourceActionName__c = logEntryEvent.OriginSourceActionName__c,
OriginSourceApiName__c = logEntryEvent.OriginSourceApiName__c,
OriginSourceId__c = logEntryEvent.OriginSourceId__c,
OriginSourceMetadataType__c = logEntryEvent.OriginSourceMetadataType__c,
OriginType__c = logEntryEvent.OriginType__c,
RecordCollectionSize__c = logEntryEvent.RecordCollectionSize__c,
Expand Down
44 changes: 44 additions & 0 deletions nebula-logger/core/main/log-management/classes/LogEntryHandler.cls
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public without sharing class LogEntryHandler extends LoggerSObjectHandler {
this.setComponentFields();
this.setFlowDefinitionFields();
this.setFlowVersionFields();
this.setOmniProcessFields();
this.setRecordNames();
this.setCheckboxFields();
}
Expand Down Expand Up @@ -219,6 +220,49 @@ public without sharing class LogEntryHandler extends LoggerSObjectHandler {
}
}

private void setOmniProcessFields() {
List<String> omniProcessIds = new List<String>();
List<LogEntry__c> omniProcessLogEntries = new List<LogEntry__c>();
for (LogEntry__c logEntry : this.logEntries) {
if (logEntry.OriginType__c == 'OmniStudio' && String.isNotBlank(logEntry.OriginSourceId__c)) {
omniProcessIds.add(logEntry.OriginSourceId__c);
omniProcessLogEntries.add(logEntry);
}
}

if (omniProcessIds.isEmpty()) {
return;
}

Map<Id, LoggerSObjectProxy.OmniProcess> omniProcessIdToProxy = LogManagementDataSelector.getInstance().getOmniProcessProxies(omniProcessIds);
for (LogEntry__c logEntry : omniProcessLogEntries) {
LoggerSObjectProxy.OmniProcess omniProcessProxy = omniProcessIdToProxy.get(logEntry.OriginSourceId__c);

if (omniProcessProxy == null) {
continue;
}

String originSourceMetadataType;
switch on omniProcessProxy.OmniProcessType {
when 'Integration Procedure' {
originSourceMetadataType = 'OmniIntegrationProcedure';
}
when 'OmniScript' {
originSourceMetadataType = 'OmniScript';
}
}

logEntry.OriginSourceApiName__c = omniProcessProxy.UniqueName;
logEntry.OriginSourceCreatedById__c = omniProcessProxy.CreatedById;
logEntry.OriginSourceCreatedByUsername__c = omniProcessProxy.CreatedBy?.Username;
logEntry.OriginSourceCreatedDate__c = omniProcessProxy.CreatedDate;
logEntry.OriginSourceLastModifiedById__c = omniProcessProxy.LastModifiedById;
logEntry.OriginSourceLastModifiedByUsername__c = omniProcessProxy.LastModifiedBy?.Username;
logEntry.OriginSourceLastModifiedDate__c = omniProcessProxy.LastModifiedDate;
logEntry.OriginSourceMetadataType__c = originSourceMetadataType;
}
}

@SuppressWarnings('PMD.OperationWithLimitsInLoop')
private void setRecordNames() {
if (LoggerParameter.QUERY_RELATED_RECORD_DATA == false) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @group Log Management
* @description Selector class used for all queries that are specific to the log management layer
*/
@SuppressWarnings('PMD.ApexCrudViolation, PMD.ExcessivePublicCount')
@SuppressWarnings('PMD.ApexCrudViolation, PMD.CyclomaticComplexity, PMD.ExcessivePublicCount')
public without sharing virtual class LogManagementDataSelector {
private static LogManagementDataSelector instance = new LogManagementDataSelector();

Expand Down Expand Up @@ -295,6 +295,29 @@ public without sharing virtual class LogManagementDataSelector {
return [SELECT Id, OwnerId, UniqueId__c FROM LoggerScenario__c WHERE Id IN :logScenarioIds];
}

/**
* @description Returns a list of matching `Schema.OmniProcess` records based on the provided list of OmniProcess IDs
* @param omniProcessIds The list of `Schema.OmniProcess` IDs to query
* @return The instance of `Map<Id, SObject>` containing any matching `Schema.OmniProcess` records
*/
public virtual Map<Id, LoggerSObjectProxy.OmniProcess> getOmniProcessProxies(List<Id> omniProcessIds) {
if (LoggerParameter.QUERY_OMNI_PROCESS_DATA == false) {
return new Map<Id, LoggerSObjectProxy.OmniProcess>();
}

// OmniStudio may not be enabled in the org, and the Schema.OmniProcess object may not exist,
// so run everything dynamically
Map<Id, LoggerSObjectProxy.OmniProcess> omniProcessIdToOmniProcessProxy = new Map<Id, LoggerSObjectProxy.OmniProcess>();
String query =
'SELECT CreatedBy.Username, CreatedById, CreatedDate, Id, IsIntegrationProcedure, LastModifiedBy.Username, LastModifiedById, LastModifiedDate, OmniProcessType, UniqueName' +
' FROM OmniProcess WHERE Id IN :omniProcessIds';
for (SObject omniProcessRecord : System.Database.query(String.escapeSingleQuotes(query))) {
LoggerSObjectProxy.OmniProcess omniProcessProxy = new LoggerSObjectProxy.OmniProcess(omniProcessRecord);
omniProcessIdToOmniProcessProxy.put(omniProcessProxy.Id, omniProcessProxy);
}
return omniProcessIdToOmniProcessProxy;
}

/**
* @description Returns a `List<Schema.Profile>` of records with the specified profile IDs
* @param profileIds The list of `ID` of the `Schema.Profile` records to query
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@
<default>false</default>
<label>LightningComponentBundle</label>
</value>
<value>
<fullName>OmniIntegrationProcedure</fullName>
<default>false</default>
<label>OmniIntegrationProcedure</label>
</value>
<value>
<fullName>OmniScript</fullName>
<default>false</default>
<label>OmniScript</label>
</value>
</valueSetDefinition>
</valueSet>
</CustomField>
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
<default>false</default>
<label>Flow</label>
</value>
<value>
<fullName>OmniStudio</fullName>
<color>#FFCC33</color>
<default>false</default>
<label>OmniStudio</label>
</value>
</valueSetDefinition>
</valueSet>
</CustomField>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<ListView xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>AllOmniStudioLogEntries</fullName>
<columns>NAME</columns>
<columns>Log__c</columns>
<columns>LoggingLevel__c</columns>
<columns>LoggedByUsernameLink__c</columns>
<columns>Message__c</columns>
<columns>OriginSourceMetadataType__c</columns>
<columns>Timestamp__c</columns>
<filterScope>Everything</filterScope>
<filters>
<field>OriginType__c</field>
<operation>equals</operation>
<value>OmniStudio</value>
</filters>
<label>All OmniStudio Entries</label>
</ListView>
7 changes: 4 additions & 3 deletions nebula-logger/core/main/logger-engine/classes/Logger.cls
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@
global with sharing class Logger {
// There's no reliable way to get the version number dynamically in Apex
@TestVisible
private static final String CURRENT_VERSION_NUMBER = 'v4.14.9';
private static final String CURRENT_VERSION_NUMBER = 'v4.14.10';
private static final System.LoggingLevel FALLBACK_LOGGING_LEVEL = System.LoggingLevel.DEBUG;
private static final List<LogEntryEventBuilder> LOG_ENTRIES_BUFFER = new List<LogEntryEventBuilder>();
private static final String MISSING_SCENARIO_ERROR_MESSAGE = 'No logger scenario specified. A scenario is required for logging in this org.';
private static final String ORGANIZATION_DOMAIN_URL = System.URL.getOrgDomainUrl()?.toExternalForm();
private static final String REQUEST_ID = System.Request.getCurrent().getRequestId();
private static final Map<String, SaveMethod> SAVE_METHOD_NAME_TO_SAVE_METHOD = new Map<String, SaveMethod>();
private static final String TRANSACTION_ID = System.UUID.randomUUID().toString();
private static final System.Quiddity TRANSACTION_QUIDDITY = loadTransactionQuiddity();

private static AsyncContext currentAsyncContext;
private static String currentEntryScenario;
Expand All @@ -35,6 +34,8 @@ global with sharing class Logger {
@TestVisible
private static Integer saveLogCallCount = 0;
private static Boolean suspendSaving = false;
@TestVisible
private static System.Quiddity transactionQuiddity = loadTransactionQuiddity();
private static String transactionScenario;

private static final List<String> CLASSES_TO_IGNORE {
Expand Down Expand Up @@ -187,7 +188,7 @@ global with sharing class Logger {
* @return System.Quiddity - The value of System.Request.getCurrent().getQuiddity()
*/
global static System.Quiddity getCurrentQuiddity() {
return TRANSACTION_QUIDDITY;
return transactionQuiddity;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,38 @@ public without sharing class LoggerSObjectProxy {
}
}
}

/**
* @description Not all orgs have the SObject `Schema.OmniProcess` - it is only present in orgs that have enabled OmniStudio,
* so `Schema.OmniProcess` has to be referenced dynamically, including using hardcoded `String` values for field API names. The
* `LoggerSObjectProxy.OmniProcess` class acts as a substitute for a `Schema.OmniProcess` record so that the rest of the codebase can rely on
* strongly-typed references to fields (properties).
*/
@SuppressWarnings('PMD.FieldNamingConventions, PMD.VariableNamingConventions')
public class OmniProcess {
public Id CreatedById;
public Schema.User CreatedBy;
public Datetime CreatedDate;
public String Id;
public Id LastModifiedById;
public Schema.User LastModifiedBy;
public Datetime LastModifiedDate;
public String OmniProcessType;
public String UniqueName;

@SuppressWarnings('PMD.ApexDoc')
public OmniProcess(SObject omniProcess) {
if (omniProcess != null) {
this.CreatedById = (String) omniProcess.get('CreatedById');
this.CreatedBy = (Schema.User) omniProcess.getSObject('CreatedBy');
this.CreatedDate = (Datetime) omniProcess.get('CreatedDate');
this.Id = (String) omniProcess.get('Id');
this.LastModifiedById = (String) omniProcess.get('LastModifiedById');
this.LastModifiedBy = (Schema.User) omniProcess.getSObject('LastModifiedBy');
this.LastModifiedDate = (Datetime) omniProcess.get('LastModifiedDate');
this.OmniProcessType = (String) omniProcess.get('OmniProcessType');
this.UniqueName = (String) omniProcess.get('UniqueName');
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public without sharing class LoggerStackTrace {
private static final System.Pattern INVALID_NAMESPACED_STACK_TRACE_PATTERN {
get {
if (INVALID_NAMESPACED_STACK_TRACE_PATTERN == null) {
INVALID_NAMESPACED_STACK_TRACE_PATTERN = System.Pattern.compile('^\\([0-9A-Za-z_]+\\)$');
INVALID_NAMESPACED_STACK_TRACE_PATTERN = System.Pattern.compile('^\\([0-9A-Za-z_ ]+\\)$');
}
return INVALID_NAMESPACED_STACK_TRACE_PATTERN;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createElement } from 'lwc';
import FORM_FACTOR from '@salesforce/client/formFactor';
import { LoggerStackTrace } from '../loggerStackTrace';
// Recommended approach
import { createLogger } from 'c/logger';
// Legacy approach
Expand All @@ -13,7 +12,7 @@ const flushPromises = async () => {
await new Promise(process.nextTick);
};

jest.mock('lightning/logger', () => ({ loadScript: jest.fn() }), {
jest.mock('lightning/logger', () => ({ log: jest.fn() }), {
virtual: true
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import FORM_FACTOR from '@salesforce/client/formFactor';
import { log as lightningLog } from 'lightning/logger';
import { LoggerStackTrace } from './loggerStackTrace';

const CURRENT_VERSION_NUMBER = 'v4.14.9';
const CURRENT_VERSION_NUMBER = 'v4.14.10';

const LOGGING_LEVEL_EMOJIS = {
ERROR: '⛔',
Expand Down
Loading

0 comments on commit 6554537

Please sign in to comment.