Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added plugin framework + re-added Slack integration #165

Merged
merged 85 commits into from
Jun 20, 2021
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
d8f2bac
Added basic functionality for handler post-processors in LogHandler a…
jongpie Jun 4, 2021
1de4a65
Added all trigger operations + updates in README
jongpie Jun 4, 2021
5ffe394
Added triggerOperation parameter
jongpie Jun 4, 2021
a9224c2
Fixed Flow input details
jongpie Jun 4, 2021
778af21
Added IsActive__c flag on config CMDT
jongpie Jun 4, 2021
9ff08ba
Moved post-processor interface outside of LoggerHandler, LoggerHandle…
jongpie Jun 4, 2021
b93e75f
Merge branch 'main' of https://github.com/jongpie/NebulaLogger into f…
jongpie Jun 4, 2021
e93a5cf
Swapped `super` for `this`
jongpie Jun 5, 2021
e9b7454
Added handler tests for Apex post-processors + cleaned up field names
jongpie Jun 5, 2021
30fe68c
More renames
jongpie Jun 6, 2021
e295a3a
Updated version to v4.5.0
jongpie Jun 6, 2021
5ced29b
Updated docs
jongpie Jun 6, 2021
1a4f46f
Added IsEnabled__c flag to handler config CMDT
jongpie Jun 7, 2021
6739628
Updated relationship name on CMDT
jongpie Jun 7, 2021
76c5f8e
Renamed config.LoggerSObject__c field to SObjectType__c
jongpie Jun 7, 2021
492ab3e
Updated screenshot of handler config
jongpie Jun 7, 2021
8ed788b
WIP: Fixed most of the failing tests again
jongpie Jun 7, 2021
89c7e21
Added tests to verify handlers dont run when disabled via CMDT
jongpie Jun 7, 2021
5b87d7d
Merge branch 'main' of https://github.com/jongpie/NebulaLogger into f…
jongpie Jun 7, 2021
1979b69
Added tests to make sure default configs are used when no CMDT is found
jongpie Jun 7, 2021
fc2f754
Small fix for trigger operation in Flow post-processing
jongpie Jun 8, 2021
6d59996
Changed CMDT mocking approach to be 83% less goofy based on @jamessim…
jongpie Jun 8, 2021
128f8f3
Centralized more trigger-based logic into LoggerSObjectHandler
jongpie Jun 8, 2021
dd86d6e
Removed safe navigators - there shouldnt be nulls in this situation
jongpie Jun 8, 2021
b3b6e0c
Added extra tests for Flow post-processors
jongpie Jun 8, 2021
bb7846f
Temp commenting out an assert
jongpie Jun 8, 2021
01f1fd4
Fixed incorrect SObject type
jongpie Jun 8, 2021
8df593a
Finished test for invoking Flow post-processor
jongpie Jun 8, 2021
be7744a
Added extra assert in test (just to be safe)
jongpie Jun 8, 2021
747cd5d
Generated new package versions
jongpie Jun 8, 2021
5878d89
Generated new package versions
jongpie Jun 8, 2021
608b80a
Fixed some config issues with SObjectType__c CMDT field
jongpie Jun 8, 2021
65006ef
Fixed VR rule name
jongpie Jun 8, 2021
0766406
Removed unused describe call
jongpie Jun 9, 2021
9ffcb7b
Moved post-processor config into a 2nd CMDT object
jongpie Jun 10, 2021
1ff13f3
Renamed 'post processors' to 'plugins' instead, added all 4 trigger S…
jongpie Jun 11, 2021
0e6e1e1
Fixed a few more old refs to 'post-processor' to use 'plugin' instead
jongpie Jun 11, 2021
8bf8a1a
Created Slack plugin prototype
jongpie Jun 11, 2021
dc1a179
Some code cleanup in SlackLoggerPlugin
jongpie Jun 11, 2021
58707f9
Hello there, LoggerSObjectHandlerPluginParameter__mdt
jongpie Jun 12, 2021
a3d63eb
Added description fields to plugin & param CMDT objects + new list views
jongpie Jun 12, 2021
85f4328
WIP: SlackLoggerPlugin_Tests (currently failing)
jongpie Jun 12, 2021
88af654
Renamed Slack parameter list view
jongpie Jun 12, 2021
a3bf731
Added description to Slack remote site setting
jongpie Jun 12, 2021
2df4727
Made more changes to the format of Slack notification
jongpie Jun 12, 2021
75fb074
Converted plugin framework to public for now - once stabilized, I'll …
jongpie Jun 13, 2021
cd8aada
Added more plugin details in README.md
jongpie Jun 13, 2021
37344cb
More formatting changes in README.md
jongpie Jun 13, 2021
e1a5592
Fixed Slack API token usage
jongpie Jun 13, 2021
e59df71
Added screenshot of Slack notification examples
jongpie Jun 13, 2021
c36236a
Removed Slack plugin install btn for now
jongpie Jun 13, 2021
47799f4
Added "What's Included" section in Slack's README.me
jongpie Jun 13, 2021
a057b3f
Formatting cleanup in Slack README.md
jongpie Jun 13, 2021
2d66eb3
Added detail that notifications are created via a queueable job
jongpie Jun 13, 2021
1e408ba
Changed plugin header title in README.md
jongpie Jun 13, 2021
ed31f18
More README header updates
jongpie Jun 13, 2021
45cd230
Corrected ApexDocs for LoggerSObjectHandlerPlugin
jongpie Jun 13, 2021
b090589
More ApexDocs updates
jongpie Jun 13, 2021
a78b8d8
Added plugin-framework folder
jongpie Jun 13, 2021
0d00458
Lots o' field descriptions, help text & docs updates
jongpie Jun 13, 2021
6910cfe
Renamed Slack CMDT record
jongpie Jun 13, 2021
ee587d0
Slack plugin cleanup + README updates
jongpie Jun 14, 2021
add0b27
Removed unused constants in SlackLoggerPlugin
jongpie Jun 14, 2021
cedc7d3
Cleaned up plugin details in README
jongpie Jun 14, 2021
cc86208
Moved Slack list view to correct folder
jongpie Jun 14, 2021
7a475ea
Updated Flow plugin details, moved note location about plugin framewo…
jongpie Jun 14, 2021
5c95611
Updated trigger framework variable names for consistency & clarity
jongpie Jun 14, 2021
fce4708
Fixed list view criteria, added CMDT description
jongpie Jun 14, 2021
5b709e3
Fixed deployment failure due to old variable ref
jongpie Jun 14, 2021
7d4ca50
Finished (basic) Slack tests + created plugin unlocked package + crea…
jongpie Jun 14, 2021
3910df6
Fixed plugin button image in README.me
jongpie Jun 14, 2021
4e014f7
Added extra test class LoggerSObjectHandlerPlugin_Tests
jongpie Jun 15, 2021
69a7d68
Changed some CMDT fields to DeveloperControlled
jongpie Jun 15, 2021
b72c3cb
A little unrelated code cleanup in RelatedLogEntriesController
jongpie Jun 15, 2021
cf6b17b
Added CollectionType field on plugin param CMDT
jongpie Jun 16, 2021
683dba7
Slack plugin cleanup
jongpie Jun 16, 2021
d18967a
More details + cleanup in README.md
jongpie Jun 16, 2021
5bc2804
Fixed some formatting issues in README.md
jongpie Jun 16, 2021
a24f860
New pkg versions for ALL the packages
jongpie Jun 16, 2021
e468dcd
Stupid README inconsistency
jongpie Jun 16, 2021
3727a52
Removed redundant text that was redundant
jongpie Jun 17, 2021
7c5f80a
Merge branch 'main' of https://github.com/jongpie/NebulaLogger into f…
jongpie Jun 20, 2021
7f4a84d
Updates to Slack README.md
jongpie Jun 20, 2021
4d13999
Removed commented-out code in LoggerSObjectHandlerPlugin
jongpie Jun 20, 2021
6fef1d0
New package versions
jongpie Jun 20, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,35 @@ The class `LogMessage` provides the ability to generate string messages on deman

For more details, check out the `LogMessage` class [documentation](https://jongpie.github.io/NebulaLogger/logger-engine/LogMessage).

### Adding Custom Post-Processors for Log\_\_c and LogEntry\_\_c

If you want to add your own automation to the `Log__c` or `LogEntry__c` objects, you can leverage Apex or Flow to define "post-processors" - the logger system will then automatically run the post-processors after each trigger event (BEFORE_INSERT, BEFORE_UPDATE, AFTER_INSERT, AFTER_UPDATE, and so on)

- Flow post-processors: your Flow should be built with these input parameters:
1. `triggerOperationType` - The name of the current trigger operation (such as BEFORE_INSERT, BEFORE_UPDATE, etc.)
2. `records` - The list of logger records being processed (`Log__c` or `LogEntry__c` records)
3. `oldRecords` - The list of logger records as they exist in the datatabase - this is only populated when running in the context of `Trigger.isUpdate`
- Apex post-processors: your Apex class should implement `LoggerSObjectPostProcessor`. For example:

```java
public class ExamplePostProcessor implements LoggerSObjectPostProcessor {
public void execute(Trigger.operationType triggerOperationType, List<Log__c> logs, Map<Id, SObject> oldLoggerRecordsById) {
switch on triggerOperationType {
when BEFORE_INSERT {
for (Log__c log : logs) {
log.Status__c = 'On Hold';
}
}
}
}
}

```

Once you've created your Apex or Flow post-processor(s), you will also need to configure the custom metadata type `LoggerSObjectHandlerConfiguration__mdt` to specify the name(s) of Apex class and Flow to run.

![Logger Handler Configuration](./content/logger-handler-configuration.png)

## Managing Logs

To help development and support teams better manage logs (and any underlying code or config issues), some fields on `Log__c` are provided to track the owner, priority and status of a log. These fields are optional, but are helpful in critical environments (production, QA sandboxes, UAT sandboxes, etc.) for monitoring ongoing user activities.
Expand Down
Binary file modified content/btn-view-documentation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added content/logger-handler-configuration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ Manages setting fields on `Log__c` before insert & before update

Manages mass deleting `Log__c` records that have been selected by a user on a `Log__c` list view

### [LoggerSObjectHandler](log-management/LoggerSObjectHandler)

Abstract class used by trigger handlers for shared logic

### [LoggerSObjectPostProcessor](log-management/LoggerSObjectPostProcessor)

Interface used to define custom logic to run when DML statements occur on `Log__c` or `LogEntry__c`

### [RelatedLogEntriesController](log-management/RelatedLogEntriesController)

Controller class for the component RelatedLogEntries
14 changes: 14 additions & 0 deletions docs/log-management/LogEntryEventHandler.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,18 @@ Processes `LogEntryEvent__e` platform events and normalizes the data into `Log__

Runs the trigger handler's logic for the `LogEntryEvent__e` platform event object

#### `getSObjectType()` → `SObjectType`

Returns SObject Type that the handler is responsible for processing

##### Return

**Type**

SObjectType

**Description**

The instance of `SObjectType`

---
14 changes: 14 additions & 0 deletions docs/log-management/LogEntryHandler.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,18 @@ Manages setting fields on `LogEntry__c` before insert & before update

Runs the trigger handler's logic for the `LogEntry__c` custom object

#### `getSObjectType()` → `SObjectType`

Returns SObject Type that the handler is responsible for processing

##### Return

**Type**

SObjectType

**Description**

The instance of `SObjectType`

---
14 changes: 14 additions & 0 deletions docs/log-management/LogHandler.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,18 @@ Manages setting fields on `Log__c` before insert & before update

Runs the trigger handler's logic for the `Log__c` custom object

#### `getSObjectType()` → `SObjectType`

Returns SObject Type that the handler is responsible for processing

##### Return

**Type**

SObjectType

**Description**

The instance of `SObjectType`

---
33 changes: 33 additions & 0 deletions docs/log-management/LoggerSObjectHandler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
layout: default
---

## LoggerSObjectHandler class

Abstract class used by trigger handlers for shared logic

---

### Constructors

#### `LoggerSObjectHandler()`

---

### Methods

#### `getSObjectType()` → `SObjectType`

Returns the SObject Type that the handler is responsible for processing

##### Return

**Type**

SObjectType

**Description**

The instance of `SObjectType`

---
15 changes: 15 additions & 0 deletions docs/log-management/LoggerSObjectPostProcessor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
layout: default
---

## LoggerSObjectPostProcessor interface

Interface used to define custom logic to run when DML statements occur on `Log__c` or `LogEntry__c`

---

### Methods

#### `execute(TriggerOperation triggerOperationType, List<SObject> loggerRecords, Map<Id, SObject> oldLoggerRecordsById)` → `void`

---
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @group Log Management
* @description Processes `LogEntryEvent__e` platform events and normalizes the data into `Log__c` and `LogEntry__c` records
*/
public without sharing class LogEntryEventHandler {
public without sharing class LogEntryEventHandler extends LoggerSObjectHandler {
private static final Map<String, Log__c> TRANSACTION_ID_TO_LOG = new Map<String, Log__c>();

@testVisible
Expand All @@ -33,10 +33,22 @@ public without sharing class LogEntryEventHandler {
this.topicNames = new Set<String>();
}

/**
* @description Returns SObject Type that the handler is responsible for processing
* @return The instance of `SObjectType`
*/
public override SObjectType getSObjectType() {
return Schema.LogEntryEvent__e.SObjectType;
}

/**
* @description Runs the trigger handler's logic for the `LogEntryEvent__e` platform event object
*/
public void execute() {
if (this.configuration.IsEnabled__c == false) {
return;
}

if (this.logEntryEvents.isEmpty() == false) {
this.upsertLogs();
this.insertLogEntries();
Expand Down
20 changes: 19 additions & 1 deletion nebula-logger/main/log-management/classes/LogEntryHandler.cls
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,37 @@
* @group Log Management
* @description Manages setting fields on `LogEntry__c` before insert & before update
*/
public without sharing class LogEntryHandler {
public without sharing class LogEntryHandler extends LoggerSObjectHandler {
// Trigger-based variables - tests can override these with mock objects
@testVisible
private List<LogEntry__c> logEntries;
@testVisible
private Map<Id, LogEntry__c> oldLogEntriesById;
@testVisible
private TriggerOperation triggerOperationType;

public LogEntryHandler() {
this.logEntries = (List<LogEntry__c>) Trigger.new;
this.oldLogEntriesById = (Map<Id, LogEntry__c>) Trigger.oldMap;
this.triggerOperationType = Trigger.operationType;
}

/**
* @description Returns SObject Type that the handler is responsible for processing
* @return The instance of `SObjectType`
*/
public override SObjectType getSObjectType() {
return Schema.LogEntry__c.SObjectType;
}

/**
* @description Runs the trigger handler's logic for the `LogEntry__c` custom object
*/
public void execute() {
if (this.configuration.IsEnabled__c == false) {
return;
}

switch on this.triggerOperationType {
when BEFORE_INSERT {
this.setCheckboxFields();
Expand All @@ -39,6 +54,9 @@ public without sharing class LogEntryHandler {
this.setCheckboxFields();
}
}

// Run any post-processors
this.executePostProcessors(this.logEntries, this.oldLogEntriesById);
}

private void setCheckboxFields() {
Expand Down
17 changes: 16 additions & 1 deletion nebula-logger/main/log-management/classes/LogHandler.cls
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @group Log Management
* @description Manages setting fields on `Log__c` before insert & before update
*/
public without sharing class LogHandler {
public without sharing class LogHandler extends LoggerSObjectHandler {
private static final Organization ORGANIZATION = [SELECT Id, InstanceName, IsSandbox FROM Organization];

@testVisible
Expand Down Expand Up @@ -38,10 +38,22 @@ public without sharing class LogHandler {
this.triggerOperationType = Trigger.operationType;
}

/**
* @description Returns SObject Type that the handler is responsible for processing
* @return The instance of `SObjectType`
*/
public override SObjectType getSObjectType() {
return Schema.Log__c.SObjectType;
}

/**
* @description Runs the trigger handler's logic for the `Log__c` custom object
*/
public void execute() {
if (this.configuration.IsEnabled__c == false) {
jamessimone marked this conversation as resolved.
Show resolved Hide resolved
return;
}

switch on this.triggerOperationType {
when BEFORE_INSERT {
this.setOrgReleaseCycle();
Expand All @@ -58,6 +70,9 @@ public without sharing class LogHandler {
this.shareLogsWithLoggingUsers();
}
}

// Run any post-processors
this.executePostProcessors(this.logs, this.oldLogsById);
}

private void setOrgReleaseCycle() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//------------------------------------------------------------------------------------------------//
// 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 Abstract class used by trigger handlers for shared logic
*/
public abstract class LoggerSObjectHandler {
@testVisible
private static Map<SObjectType, LoggerSObjectHandlerConfiguration__mdt> configurationsBySObjectType = loadConfigurations();

private static Map<SObjectType, LoggerSObjectHandlerConfiguration__mdt> loadConfigurations() {
DescribeSObjectResult configDescribe = Schema.LoggerSObjectHandlerConfiguration__mdt.SObjectType.getDescribe();

// When using CMDT's getAll(), it does not return relationship fields for EntityDefinition fields...
// ... so instead query the LoggerSObjectHandlerConfiguration__mdt CMDT object
List<LoggerSObjectHandlerConfiguration__mdt> configurations = [
SELECT Id, IsEnabled__c, PostProcessorApexClassName__c, PostProcessorFlowApiName__c, SObjectType__r.QualifiedApiName
FROM LoggerSObjectHandlerConfiguration__mdt
];

Map<SObjectType, LoggerSObjectHandlerConfiguration__mdt> configsBySObjectType = new Map<SObjectType, LoggerSObjectHandlerConfiguration__mdt>();
for (LoggerSObjectHandlerConfiguration__mdt config : configurations) {
// CMDT entity-definition relationship fields are weird, so skip some headaches by copying the Qualified API name
config.SObjectType__c = config.SObjectType__r.QualifiedApiName;

// Schema.getGlobalDescribe() is the worst, so don't use it
SObjectType sobjectType = ((SObject) Type.forName(config.SObjectType__c).newInstance()).getSObjectType();

configsBySObjectType.put(sobjectType, config);
}
return configsBySObjectType;
}

protected LoggerSObjectHandlerConfiguration__mdt configuration;

public LoggerSObjectHandler() {
this.configuration = getConfiguration(this.getSObjectType());
}

/**
* @description Returns the SObject Type that the handler is responsible for processing
* @return The instance of `SObjectType`
*/
public abstract SObjectType getSObjectType();

protected void executePostProcessors(List<SObject> loggerRecords, Map<Id, SObject> oldLoggerRecordsById) {
if (loggerRecords == null) {
return;
}

this.executePostProcessorApexClass(this.configuration?.PostProcessorApexClassName__c, loggerRecords, oldLoggerRecordsById);
this.executePostProcessorFlow(this.configuration?.PostProcessorFlowApiName__c, loggerRecords, oldLoggerRecordsById);
}

private LoggerSObjectHandlerConfiguration__mdt getConfiguration(SObjectType sobjectType) {
LoggerSObjectHandlerConfiguration__mdt configuration = configurationsBySObjectType.get(sobjectType);

if (configuration == null) {
configuration = new LoggerSObjectHandlerConfiguration__mdt(IsEnabled__c = true, SObjectType__c = sobjectType.getDescribe().getName());

configurationsBySObjectType.put(sobjectType, configuration);
}

return configuration;
}

private void executePostProcessorApexClass(String apexClassName, List<SObject> loggerRecords, Map<Id, SObject> oldLoggerRecordsById) {
if (String.isBlank(apexClassName) || Type.forName(apexClassName) == null) {
return;
}

LoggerSObjectPostProcessor apexPostProcessor = (LoggerSObjectPostProcessor) Type.forName(apexClassName).newInstance();
apexPostProcessor.execute(Trigger.operationType, loggerRecords, oldLoggerRecordsById);
}

private void executePostProcessorFlow(String flowApiName, List<SObject> loggerRecords, Map<Id, SObject> oldLoggerRecordsById) {
if (String.isBlank(flowApiName)) {
return;
}

Map<String, Object> flowInputs = new Map<String, Object>();
flowInputs.put('triggerOperationType', Trigger.operationType?.name());
flowInputs.put('records', loggerRecords);
flowInputs.put('oldRecords', oldLoggerRecordsById?.values());

Flow.Interview flowPostProcessor = Flow.Interview.createInterview(flowApiName, flowInputs);
flowPostProcessor.start();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//------------------------------------------------------------------------------------------------//
// 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 Interface used to define custom logic to run when DML statements occur on `Log__c` or `LogEntry__c`
*/
global interface LoggerSObjectPostProcessor {
void execute(TriggerOperation triggerOperationType, List<SObject> loggerRecords, Map<Id, SObject> oldLoggerRecordsById);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
Loading