diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..30bc243c8 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +.github/ +.husky/ +.sfdx/ +.vscode/ +test-coverage/ \ No newline at end of file diff --git a/.github/codecov.yml b/.github/codecov.yml index f1bef807f..0d7eb5e21 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -8,3 +8,5 @@ coverage: patch: off ignore: - 'nebula-logger-recipes/**/*' +comment: + behavior: new diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4218604c3..2f9a39e82 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,24 +61,33 @@ jobs: if: steps.cache-npm.outputs.cache-hit != 'true' run: npm ci - - name: 'Verify package version number is updated' + - name: 'Authorize Dev Hub' if: ${{ github.event_name == 'pull_request' }} + shell: bash run: | - echo ${{ env.DEVHUB_SFDX_URL }} > ./DEVHUB_SFDX_URL.txt - npx sfdx auth:sfdxurl:store --sfdxurlfile ./DEVHUB_SFDX_URL.txt --setalias nebula-logger-packaging --setdefaultdevhubusername - rm ./DEVHUB_SFDX_URL.txt - npm run package:version:number:verify + echo "${{ env.DEV_HUB_JWT_SERVER_KEY }}" > ./jwt-server.key + npx sfdx force:auth:jwt:grant --instanceurl ${{ env.DEV_HUB_AUTH_URL }} --clientid ${{ env.DEV_HUB_CONSUMER_KEY }} --username ${{ env.DEV_HUB_BOT_USERNAME }} --jwtkeyfile ./jwt-server.key --setdefaultdevhubusername env: - DEVHUB_SFDX_URL: ${{ secrets.DEVHUB_SFDX_URL }} + DEV_HUB_AUTH_URL: ${{ secrets.DEV_HUB_AUTH_URL }} + DEV_HUB_BOT_USERNAME: ${{ secrets.DEV_HUB_BOT_USERNAME }} + DEV_HUB_CONSUMER_KEY: ${{ secrets.DEV_HUB_CONSUMER_KEY }} + DEV_HUB_JWT_SERVER_KEY: ${{ secrets.DEV_HUB_JWT_SERVER_KEY }} + + - name: 'Verify package version number is updated' + if: ${{ github.event_name == 'pull_request' }} + run: npm run package:version:number:verify - name: 'Verify LWC with ESLint' run: npm run lint:verify:lwc - name: 'Verify Apex with SFDX Scanner' - run: npm run lint:verify:apex + run: | + npm run sfdx:plugins:link:scanner + npm run lint:verify:apex - - name: 'Verify docs are updated' - run: npm run docs:verify + # TODO - uncomment - temporarily commented-out due to an issue with apexdocs in the pipeline + # - name: 'Verify docs are updated' + # run: npm run docs:verify - name: 'Verify formatting with Prettier' run: npm run prettier:verify @@ -140,13 +149,16 @@ jobs: - name: 'Authorize Dev Hub' shell: bash run: | - echo ${{ env.DEVHUB_SFDX_URL }} > ./DEVHUB_SFDX_URL.txt - npx sfdx auth:sfdxurl:store --sfdxurlfile ./DEVHUB_SFDX_URL.txt --setalias nebula-logger-packaging --setdefaultdevhubusername + echo "${{ env.DEV_HUB_JWT_SERVER_KEY }}" > ./jwt-server.key + npx sfdx force:auth:jwt:grant --instanceurl ${{ env.DEV_HUB_AUTH_URL }} --clientid ${{ env.DEV_HUB_CONSUMER_KEY }} --username ${{ env.DEV_HUB_BOT_USERNAME }} --jwtkeyfile ./jwt-server.key --setdefaultdevhubusername env: - DEVHUB_SFDX_URL: ${{ secrets.DEVHUB_SFDX_URL }} + DEV_HUB_AUTH_URL: ${{ secrets.DEV_HUB_AUTH_URL }} + DEV_HUB_BOT_USERNAME: ${{ secrets.DEV_HUB_BOT_USERNAME }} + DEV_HUB_CONSUMER_KEY: ${{ secrets.DEV_HUB_CONSUMER_KEY }} + DEV_HUB_JWT_SERVER_KEY: ${{ secrets.DEV_HUB_JWT_SERVER_KEY }} - name: 'Create Base Scratch Org' - run: npm run org:create:base -- -durationdays 1 -devhubs nebula-logger-packaging + run: npx sfdx force:org:create --durationdays 1 --definitionfile ./config/scratch-orgs/base-scratch-def.json --wait 20 --setdefaultusername --json - name: 'Push Source to Scratch Org' run: npm run source:push @@ -187,21 +199,19 @@ jobs: - name: 'Authorize Dev Hub' shell: bash run: | - echo ${{ env.DEVHUB_SFDX_URL }} > ./DEVHUB_SFDX_URL.txt - npx sfdx auth:sfdxurl:store --sfdxurlfile ./DEVHUB_SFDX_URL.txt --setalias nebula-logger-packaging --setdefaultdevhubusername + echo "${{ env.DEV_HUB_JWT_SERVER_KEY }}" > ./jwt-server.key + npx sfdx force:auth:jwt:grant --instanceurl ${{ env.DEV_HUB_AUTH_URL }} --clientid ${{ env.DEV_HUB_CONSUMER_KEY }} --username ${{ env.DEV_HUB_BOT_USERNAME }} --jwtkeyfile ./jwt-server.key --setdefaultdevhubusername env: - DEVHUB_SFDX_URL: ${{ secrets.DEVHUB_SFDX_URL }} - - - name: 'Create Scratch Org with Experience Sites Enabled' - run: npm run org:create:experience-cloud -- -durationdays 1 -devhubs nebula-logger-packaging + DEV_HUB_AUTH_URL: ${{ secrets.DEV_HUB_AUTH_URL }} + DEV_HUB_BOT_USERNAME: ${{ secrets.DEV_HUB_BOT_USERNAME }} + DEV_HUB_CONSUMER_KEY: ${{ secrets.DEV_HUB_CONSUMER_KEY }} + DEV_HUB_JWT_SERVER_KEY: ${{ secrets.DEV_HUB_JWT_SERVER_KEY }} - - name: 'Create Test Experience Site' - run: npm run experience:create + - name: 'Create Experience Cloud Scratch Org' + run: npx sfdx force:org:create --durationdays 1 --definitionfile ./config/scratch-orgs/experience-cloud-scratch-def.json --wait 20 --setdefaultusername --json - - name: 'Wait for Experience Site creation' - uses: maddox/actions/sleep@master - with: - args: '120' + - name: 'Deploy Test Experience Site Metadata' + run: npm run experience:deploy - name: 'Push Source to Scratch Org' run: npm run source:push @@ -251,13 +261,16 @@ jobs: if: steps.cache-npm.outputs.cache-hit != 'true' run: npm ci - - name: 'Authorize Packaging Org' + - name: 'Authorize Dev Hub' shell: bash run: | - echo ${{ env.DEVHUB_SFDX_URL }} > ./DEVHUB_SFDX_URL.txt - npx sfdx auth:sfdxurl:store --sfdxurlfile ./DEVHUB_SFDX_URL.txt --setalias nebula-logger-packaging --setdefaultdevhubusername + echo "${{ env.DEV_HUB_JWT_SERVER_KEY }}" > ./jwt-server.key + npx sfdx force:auth:jwt:grant --instanceurl ${{ env.DEV_HUB_AUTH_URL }} --clientid ${{ env.DEV_HUB_CONSUMER_KEY }} --username ${{ env.DEV_HUB_BOT_USERNAME }} --jwtkeyfile ./jwt-server.key --setdefaultdevhubusername env: - DEVHUB_SFDX_URL: ${{ secrets.DEVHUB_SFDX_URL }} + DEV_HUB_AUTH_URL: ${{ secrets.DEV_HUB_AUTH_URL }} + DEV_HUB_BOT_USERNAME: ${{ secrets.DEV_HUB_BOT_USERNAME }} + DEV_HUB_CONSUMER_KEY: ${{ secrets.DEV_HUB_CONSUMER_KEY }} + DEV_HUB_JWT_SERVER_KEY: ${{ secrets.DEV_HUB_JWT_SERVER_KEY }} - name: 'Authorize Demo Org' shell: bash @@ -268,12 +281,14 @@ jobs: PKG_DEMO_ORG_SFDX_URL: ${{ secrets.NEBULA_PKG_DEMO_SANDBOX_SFDX_URL }} - name: 'Create & Install Package Version' - run: npx pwsh ./config/scripts/build/create-and-install-package-version.ps1 -targetpackagealias '"Nebula Logger - Core"' -targetreadme ./README.md -targetusername nebula-logger-package-demo + run: npx pwsh ./scripts/build/create-and-install-package-version.ps1 -targetpackagealias '"Nebula Logger - Core"' -targetreadme ./README.md -targetusername nebula-logger-package-demo - name: 'Commit New Package Version' run: | git config --local user.email "action@github.com" git config --local user.name "GitHub Action Bot" + npm run sfdx:plugins:link:bummer + npx sfdx bummer:package:aliases:sort npx prettier --write ./sfdx-project.json git add ./sfdx-project.json git commit -m "Created new package version" @@ -305,13 +320,16 @@ jobs: if: steps.cache-npm.outputs.cache-hit != 'true' run: npm ci - - name: 'Authorize Packaging Org' + - name: 'Authorize Dev Hub' shell: bash run: | - echo ${{ env.DEVHUB_SFDX_URL }} > ./DEVHUB_SFDX_URL.txt - npx sfdx auth:sfdxurl:store --sfdxurlfile ./DEVHUB_SFDX_URL.txt --setalias nebula-logger-packaging --setdefaultdevhubusername + echo "${{ env.DEV_HUB_JWT_SERVER_KEY }}" > ./jwt-server.key + npx sfdx force:auth:jwt:grant --instanceurl ${{ env.DEV_HUB_AUTH_URL }} --clientid ${{ env.DEV_HUB_CONSUMER_KEY }} --username ${{ env.DEV_HUB_BOT_USERNAME }} --jwtkeyfile ./jwt-server.key --setdefaultdevhubusername env: - DEVHUB_SFDX_URL: ${{ secrets.DEVHUB_SFDX_URL }} + DEV_HUB_AUTH_URL: ${{ secrets.DEV_HUB_AUTH_URL }} + DEV_HUB_BOT_USERNAME: ${{ secrets.DEV_HUB_BOT_USERNAME }} + DEV_HUB_CONSUMER_KEY: ${{ secrets.DEV_HUB_CONSUMER_KEY }} + DEV_HUB_JWT_SERVER_KEY: ${{ secrets.DEV_HUB_JWT_SERVER_KEY }} - name: 'Create Beta Managed Package Version' run: npm run package:version:create:managed @@ -339,13 +357,16 @@ jobs: if: steps.cache-npm.outputs.cache-hit != 'true' run: npm ci - - name: 'Authorize Packaging Org' + - name: 'Authorize Dev Hub' shell: bash run: | - echo ${{ env.DEVHUB_SFDX_URL }} > ./DEVHUB_SFDX_URL.txt - npx sfdx auth:sfdxurl:store --sfdxurlfile ./DEVHUB_SFDX_URL.txt --setalias nebula-logger-packaging --setdefaultdevhubusername + echo "${{ env.DEV_HUB_JWT_SERVER_KEY }}" > ./jwt-server.key + npx sfdx force:auth:jwt:grant --instanceurl ${{ env.DEV_HUB_AUTH_URL }} --clientid ${{ env.DEV_HUB_CONSUMER_KEY }} --username ${{ env.DEV_HUB_BOT_USERNAME }} --jwtkeyfile ./jwt-server.key --setdefaultdevhubusername env: - DEVHUB_SFDX_URL: ${{ secrets.DEVHUB_SFDX_URL }} + DEV_HUB_AUTH_URL: ${{ secrets.DEV_HUB_AUTH_URL }} + DEV_HUB_BOT_USERNAME: ${{ secrets.DEV_HUB_BOT_USERNAME }} + DEV_HUB_CONSUMER_KEY: ${{ secrets.DEV_HUB_CONSUMER_KEY }} + DEV_HUB_JWT_SERVER_KEY: ${{ secrets.DEV_HUB_JWT_SERVER_KEY }} - name: 'Promote package versions' - run: npx pwsh ./config/scripts/build/promote-readme-packages.ps1 + run: npx pwsh ./scripts/build/promote-readme-packages.ps1 diff --git a/.gitignore b/.gitignore index 38b9b4e43..ad1b3590d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,13 @@ nebula-logger/**/main/default/ test-coverage/ temp/ +# Additional folders that are temporarily copied when creating a version of the managed package +nebula-logger/managed-package/core/main/configuration/ +nebula-logger/managed-package/core/main/log-management/ +nebula-logger/managed-package/core/main/logger-engine/ +nebula-logger/managed-package/core/main/plugin-framework/ +nebula-logger/managed-package/core/tests/ + # NPM node_modules/ yarn.lock diff --git a/README.md b/README.md index 6e3132aa2..e0ac9bb3b 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,16 @@ Designed for Salesforce admins, developers & architects. A robust logger for Apex, Lightning Components, Flow, Process Builder & Integrations. -## Unlocked Package - v4.6.16 +## Unlocked Package - v4.7.0 -[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015lLzQAI) -[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015lLzQAI) +[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015lXSQAY) +[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015lXSQAY) [![View Documentation](./images/btn-view-documentation.png)](https://jongpie.github.io/NebulaLogger/) -## Managed Package - v4.6.0 +## Managed Package - v4.7.0 -[![Install Managed Package in a Sandbox](./images/btn-install-managed-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?mgd=true&p0=04t5Y0000015keOQAQ) -[![Install Managed Package in Production](./images/btn-install-managed-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?mgd=true&p0=04t5Y0000015keOQAQ) +[![Install Managed Package in a Sandbox](./images/btn-install-managed-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?mgd=true&p0=04t5Y0000015lXNQAY) +[![Install Managed Package in Production](./images/btn-install-managed-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?mgd=true&p0=04t5Y0000015lXNQAY) [![View Milestone](./images/btn-view-managed-package-milestone.png)](https://github.com/jongpie/NebulaLogger/milestone/6) --- diff --git a/config/linters/pmd-ruleset.xml b/config/linters/pmd-ruleset.xml index b8cd220c1..8bfc2e311 100644 --- a/config/linters/pmd-ruleset.xml +++ b/config/linters/pmd-ruleset.xml @@ -10,7 +10,6 @@ - diff --git a/config/scratch-orgs/experience-cloud-scratch-def.json b/config/scratch-orgs/experience-cloud-scratch-def.json index ab81741f9..86f826db1 100644 --- a/config/scratch-orgs/experience-cloud-scratch-def.json +++ b/config/scratch-orgs/experience-cloud-scratch-def.json @@ -2,8 +2,8 @@ "orgName": "Nebula Logger - Experience Cloud Scratch Org", "edition": "Enterprise", "hasSampleData": true, - "country": "US", - "language": "en_US", + "country": "ES", + "language": "es", "features": ["Communities"], "settings": { "chatterSettings": { @@ -12,6 +12,9 @@ "communitiesSettings": { "enableNetworksEnabled": true }, + "experienceBundleSettings": { + "enableExperienceBundleMetadata": true + }, "userManagementSettings": { "enableEnhancedPermsetMgmt": true, "enableEnhancedProfileMgmt": true, diff --git a/docs/apex/index.md b/docs/apex/index.md index 4cd178744..702b14b3f 100644 --- a/docs/apex/index.md +++ b/docs/apex/index.md @@ -76,6 +76,10 @@ Manages mass deleting `Log__c` records that have been selected by a user on a `L Abstract class used by trigger handlers for shared logic +### [LoggerSObjectMetadata](log-management/LoggerSObjectMetadata) + +Provides details to LWCs about Logger's `SObjects`, using `@AuraEnabled` properties + ### [LoggerSettingsController](log-management/LoggerSettingsController) Controller class for lwc `loggerSettings`, used to manage records in `LoggerSettings__c` diff --git a/docs/apex/log-management/LoggerSObjectMetadata.md b/docs/apex/log-management/LoggerSObjectMetadata.md new file mode 100644 index 000000000..6fe5469e7 --- /dev/null +++ b/docs/apex/log-management/LoggerSObjectMetadata.md @@ -0,0 +1,85 @@ +--- +layout: default +--- + +## LoggerSObjectMetadata class + +Provides details to LWCs about Logger's `SObjects`, using `@AuraEnabled` properties + +--- + +### Methods + +#### `getLogEntryEventSchema()` → `SObjectSchema` + +Provides schema details about the the platform event object `LogEntryEvent__e` + +##### Return + +**Type** + +SObjectSchema + +**Description** + +An instance of `LoggerSObjectMetadata.SObjectSchema` for the platform event `LogEntryEvent__e` + +#### `getLoggerSettingsSchema()` → `SObjectSchema` + +Provides schema details about the the custom settings object `LoggerSettings__c` + +##### Return + +**Type** + +SObjectSchema + +**Description** + +An instance of `LoggerSObjectMetadata.SObjectSchema` for the platform event `LoggerSettings__c` + +--- + +### Inner Classes + +#### LoggerSObjectMetadata.FieldSchema class + +Inner class for `SObjectField` details to LWCs, using `@AuraEnabled` properties + +--- + +##### Properties + +###### `apiName` → `String` + +###### `inlineHelpText` → `String` + +###### `label` → `String` + +###### `localApiName` → `String` + +###### `type` → `String` + +--- + +#### LoggerSObjectMetadata.SObjectSchema class + +Inner class for `SObject` details to LWCs, using `@AuraEnabled` properties + +--- + +##### Properties + +###### `apiName` → `String` + +###### `fields` → `Map` + +###### `label` → `String` + +###### `labelPlural` → `String` + +###### `localApiName` → `String` + +###### `namespacePrefix` → `String` + +--- diff --git a/docs/apex/logger-engine/Logger.md b/docs/apex/logger-engine/Logger.md index 65f61e5fc..68889c89a 100644 --- a/docs/apex/logger-engine/Logger.md +++ b/docs/apex/logger-engine/Logger.md @@ -3244,6 +3244,20 @@ SaveMethod The enum value of Logger.SaveMethod to use for any calls to saveLog() in the current transaction +#### `getScenario()` → `String` + +Returns the scenario name for the current transaction - this is stored in `LogEntryEvent__e.Scenario__c` and `Log__c.Scenario__c`, and can be used to filter & group logs + +##### Return + +**Type** + +String + +**Description** + +The value currently set as the current transaction's scenario + #### `getTransactionId()` → `String` Returns the unique ID for a particular transaction, stored in Log**c.TransactionId**c diff --git a/nebula-logger/core/main/configuration/classes/LoggerEmailUtils.cls b/nebula-logger/core/main/configuration/classes/LoggerEmailUtils.cls index f65849d3c..7770cab70 100644 --- a/nebula-logger/core/main/configuration/classes/LoggerEmailUtils.cls +++ b/nebula-logger/core/main/configuration/classes/LoggerEmailUtils.cls @@ -20,6 +20,24 @@ public without sharing class LoggerEmailUtils { set; } + private static Boolean IS_EMAIL_DELIVERABILITY_ENABLED { + get { + if (IS_EMAIL_DELIVERABILITY_ENABLED == null) { + try { + Messaging.reserveSingleEmailCapacity(1); + Messaging.reserveMassEmailCapacity(1); + IS_EMAIL_DELIVERABILITY_ENABLED = true; + return IS_EMAIL_DELIVERABILITY_ENABLED; + } catch (System.NoAccessException e) { + IS_EMAIL_DELIVERABILITY_ENABLED = false; + return IS_EMAIL_DELIVERABILITY_ENABLED; + } + } + return IS_EMAIL_DELIVERABILITY_ENABLED; + } + set; + } + @TestVisible private static final List SENT_EMAILS { get { @@ -107,13 +125,15 @@ public without sharing class LoggerEmailUtils { @SuppressWarnings('PMD.AvoidDebugStatements') private static void sendEmail(Messaging.SingleEmailMessage message) { - List messages = new List{ message }; - List emailResults = Messaging.sendEmail(messages); - SENT_EMAILS.add(message); - if (emailResults[0].success == true) { - System.debug(LoggingLevel.INFO, 'Logger - The email was sent successfully'); - } else { - System.debug(LoggingLevel.INFO, 'Logger - The email failed to send: ' + emailResults[0].errors[0].message); + if (IS_EMAIL_DELIVERABILITY_ENABLED == true) { + List messages = new List{ message }; + List emailResults = Messaging.sendEmail(messages); + SENT_EMAILS.add(message); + if (emailResults[0].success == true) { + System.debug(LoggingLevel.INFO, 'Logger - The email was sent successfully'); + } else { + System.debug(LoggingLevel.INFO, 'Logger - The email failed to send: ' + emailResults[0].errors[0].message); + } } } diff --git a/nebula-logger/core/main/configuration/classes/LoggerEmailUtils.cls-meta.xml b/nebula-logger/core/main/configuration/classes/LoggerEmailUtils.cls-meta.xml index 871a8cfea..891916bb0 100644 --- a/nebula-logger/core/main/configuration/classes/LoggerEmailUtils.cls-meta.xml +++ b/nebula-logger/core/main/configuration/classes/LoggerEmailUtils.cls-meta.xml @@ -1,5 +1,5 @@ - 53.0 + 54.0 Active diff --git a/nebula-logger/core/main/configuration/classes/LoggerParameter.cls-meta.xml b/nebula-logger/core/main/configuration/classes/LoggerParameter.cls-meta.xml index 871a8cfea..891916bb0 100644 --- a/nebula-logger/core/main/configuration/classes/LoggerParameter.cls-meta.xml +++ b/nebula-logger/core/main/configuration/classes/LoggerParameter.cls-meta.xml @@ -1,5 +1,5 @@ - 53.0 + 54.0 Active diff --git a/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.CallStatusApi.md-meta.xml b/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.CallStatusApi.md-meta.xml index 94ccfe6ae..989fc67e9 100644 --- a/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.CallStatusApi.md-meta.xml +++ b/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.CallStatusApi.md-meta.xml @@ -5,7 +5,7 @@ xmlns:xsd="http://www.w3.org/2001/XMLSchema" > - true + false Description__c - true + false Description__c When enabled, log entries may be generated that contain additional details about the logging system. diff --git a/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.EnableTagging.md-meta.xml b/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.EnableTagging.md-meta.xml index d634c0023..b7bdf0191 100644 --- a/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.EnableTagging.md-meta.xml +++ b/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.EnableTagging.md-meta.xml @@ -5,7 +5,7 @@ xmlns:xsd="http://www.w3.org/2001/XMLSchema" > - true + false Description__c - true + false Description__c false SubscriberControlled This value is used to set the field Log__c.LogRetentionDate__c, which is then used by LogBatchPurger to delete old logs. To keep logs indefinitely, set this field to blank (null). + >This value is used to set the field Log__c.LogRetentionDate__c, which is then used by LogBatchPurger to delete old logs. To keep logs indefinitely, set this field to a blank value (null). 4 false diff --git a/nebula-logger/core/main/configuration/objects/LoggerParameter__mdt/LoggerParameter__mdt.object-meta.xml b/nebula-logger/core/main/configuration/objects/LoggerParameter__mdt/LoggerParameter__mdt.object-meta.xml index 5d143d693..b0d4c0bf1 100644 --- a/nebula-logger/core/main/configuration/objects/LoggerParameter__mdt/LoggerParameter__mdt.object-meta.xml +++ b/nebula-logger/core/main/configuration/objects/LoggerParameter__mdt/LoggerParameter__mdt.object-meta.xml @@ -3,5 +3,5 @@ Used to store additional key-value pair parameters used by Logger Logger Parameters - Protected + Public diff --git a/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/DefaultLogOwner__c.field-meta.xml b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/DefaultLogOwner__c.field-meta.xml new file mode 100644 index 000000000..06e0c4f3e --- /dev/null +++ b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/DefaultLogOwner__c.field-meta.xml @@ -0,0 +1,13 @@ + + + DefaultLogOwner__c + false + Specifies the default owner for new Log__c records. This can be a user ID, a username, a queue ID, or a queue's developer name. + + 255 + false + false + Text + false + diff --git a/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/DefaultLogScenario__c.field-meta.xml b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/DefaultLogScenario__c.field-meta.xml new file mode 100644 index 000000000..299d2a5b7 --- /dev/null +++ b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/DefaultLogScenario__c.field-meta.xml @@ -0,0 +1,13 @@ + + + DefaultLogScenario__c + Sets a default scenario for the transaction + false + Sets a default scenario for the transaction + + 255 + false + false + Text + false + diff --git a/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/DefaultLogShareAccessLevel__c.field-meta.xml b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/DefaultLogShareAccessLevel__c.field-meta.xml index 94efb19a4..1e0232424 100644 --- a/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/DefaultLogShareAccessLevel__c.field-meta.xml +++ b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/DefaultLogShareAccessLevel__c.field-meta.xml @@ -4,19 +4,10 @@ 'Read' Uses Apex managed sharing to grants users read or edit access to their log records (on insert only). When no access level is specified, no Apex sharing logic is executed. This only gives record-level access - users will still need to be granted access to the Log__c object using permission sets or profiles. - -Possible Values: -(blank) -Read -Edit + false Uses Apex managed sharing to grants users read or edit access to their log records (on insert only). When no access level is specified, no Apex sharing logic is executed. This only gives record-level access - users will still need to be granted access to the Log__c object using permission sets or profiles. - -Possible Values: -(blank) -Read -Edit + >Uses Apex managed sharing to grants users read or edit access to their log records (on insert only). When no access level is specified, no Apex sharing logic is executed. This only gives record-level access - users will still need to be granted access to the Log__c object using permission sets or profiles. 255 false diff --git a/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/DefaultSaveMethod__c.field-meta.xml b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/DefaultSaveMethod__c.field-meta.xml index b61108bf7..e6e97b71f 100644 --- a/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/DefaultSaveMethod__c.field-meta.xml +++ b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/DefaultSaveMethod__c.field-meta.xml @@ -2,21 +2,11 @@ DefaultSaveMethod__c 'EVENT_BUS' - Defaults to 'EVENT_BUS'. This controls the default save method used by Logger when calling saveLog(). - -Possible values: -EVENT_BUS -QUEUEABLE -REST -SYNCHRONOUS_DML + Defaults to EVENT_BUS. This controls the default save method used by Logger when calling saveLog(). In most situations, EVENT_BUS should be used. false - Defaults to 'EVENT_BUS'. This controls the default save method used by Logger when calling saveLog(). - -Possible values: -EVENT_BUS -QUEUEABLE -REST -SYNCHRONOUS_DML + Defaults to EVENT_BUS. This controls the default save method used by Logger when calling saveLog(). In most situations, EVENT_BUS should be used. 255 true diff --git a/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/IsAnonymousModeEnabled__c.field-meta.xml b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/IsAnonymousModeEnabled__c.field-meta.xml index cd340c178..b547ba2aa 100644 --- a/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/IsAnonymousModeEnabled__c.field-meta.xml +++ b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/IsAnonymousModeEnabled__c.field-meta.xml @@ -3,9 +3,8 @@ IsAnonymousModeEnabled__c false false - When enabled, any logs generated will not have any user-specific details set - any fields related to the User, Profile, etc. will be null. - -Note: this feature only works properly when using the save method EVENT_BUS. + When enabled, any logs generated will not have any user-specific details set - any fields related to the User, Profile, etc. will be null. Note: this feature only works properly when using the save method EVENT_BUS. false Checkbox diff --git a/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/IsPlatformEventStorageEnabled__c.field-meta.xml b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/IsPlatformEventStorageEnabled__c.field-meta.xml new file mode 100644 index 000000000..ab0b46b87 --- /dev/null +++ b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/IsPlatformEventStorageEnabled__c.field-meta.xml @@ -0,0 +1,12 @@ + + + IsPlatformEventStorageEnabled__c + true + Controls if LogEntryEvent__e platform events are transformed & stored in the custom objects Log__c and LogEntry__c (when IsSavingEnabled__c == true). + false + Controls if LogEntryEvent__e platform events are transformed & stored in the custom objects Log__c and LogEntry__c (when IsSavingEnabled__c == true). + + Checkbox + diff --git a/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/StripInaccessibleRecordFields__c.field-meta.xml b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/IsRecordFieldStrippingEnabled__c.field-meta.xml similarity index 89% rename from nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/StripInaccessibleRecordFields__c.field-meta.xml rename to nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/IsRecordFieldStrippingEnabled__c.field-meta.xml index 6edea3991..b37ab41c6 100644 --- a/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/StripInaccessibleRecordFields__c.field-meta.xml +++ b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/IsRecordFieldStrippingEnabled__c.field-meta.xml @@ -1,6 +1,6 @@ - StripInaccessibleRecordFields__c + IsRecordFieldStrippingEnabled__c false false + + IsSavingEnabled__c + true + Controls if saving is enabled - when disabled, any calls to saveLog() are ignored. + false + Controls if saving is enabled - when disabled, any calls to saveLog() are ignored. + + Checkbox + diff --git a/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/LoggingLevel__c.field-meta.xml b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/LoggingLevel__c.field-meta.xml index 89a4a83e4..ef9103a68 100644 --- a/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/LoggingLevel__c.field-meta.xml +++ b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/fields/LoggingLevel__c.field-meta.xml @@ -3,14 +3,6 @@ LoggingLevel__c 'DEBUG' false - Possible values: -ERROR -WARN -INFO -DEBUG -FINE -FINER -FINEST 255 true diff --git a/nebula-logger/core/main/configuration/objects/LoggerSettings__c/listViews/All.listView-meta.xml b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/listViews/All.listView-meta.xml index 93b410584..763457511 100644 --- a/nebula-logger/core/main/configuration/objects/LoggerSettings__c/listViews/All.listView-meta.xml +++ b/nebula-logger/core/main/configuration/objects/LoggerSettings__c/listViews/All.listView-meta.xml @@ -9,7 +9,7 @@ IsApexSystemDebugLoggingEnabled__c IsJavaScriptConsoleLoggingEnabled__c IsDataMaskingEnabled__c - StripInaccessibleRecordFields__c + IsRecordFieldStrippingEnabled__c IsAnonymousModeEnabled__c DefaultLogShareAccessLevel__c DefaultNumberOfDaysToRetainLogs__c diff --git a/nebula-logger/core/main/log-management/classes/LogBatchPurgeScheduler.cls-meta.xml b/nebula-logger/core/main/log-management/classes/LogBatchPurgeScheduler.cls-meta.xml index 871a8cfea..891916bb0 100644 --- a/nebula-logger/core/main/log-management/classes/LogBatchPurgeScheduler.cls-meta.xml +++ b/nebula-logger/core/main/log-management/classes/LogBatchPurgeScheduler.cls-meta.xml @@ -1,5 +1,5 @@ - 53.0 + 54.0 Active diff --git a/nebula-logger/core/main/log-management/classes/LogBatchPurger.cls-meta.xml b/nebula-logger/core/main/log-management/classes/LogBatchPurger.cls-meta.xml index 871a8cfea..891916bb0 100644 --- a/nebula-logger/core/main/log-management/classes/LogBatchPurger.cls-meta.xml +++ b/nebula-logger/core/main/log-management/classes/LogBatchPurger.cls-meta.xml @@ -1,5 +1,5 @@ - 53.0 + 54.0 Active diff --git a/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls b/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls index 3f76d2ef6..c15d7ab5e 100644 --- a/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls +++ b/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls @@ -42,7 +42,7 @@ public without sharing class LogEntryEventHandler extends LoggerSObjectHandler { * @param triggerNew list of new LogEntryEvent events */ public override void executeAfterInsert(List triggerNew) { - this.logEntryEvents = (List) triggerNew; + this.logEntryEvents = this.filterLogEntryEventsToSave((List) triggerNew); this.upsertLogs(); this.insertLogEntries(); @@ -50,6 +50,18 @@ public without sharing class LogEntryEventHandler extends LoggerSObjectHandler { this.insertLogEntryTags(); } + private List filterLogEntryEventsToSave(List newLogEntryEvents) { + List logEntryEventsToSave = new List(); + for (LogEntryEvent__e logEntryEvent : newLogEntryEvents) { + User loggingUser = new User(Id = logEntryEvent.LoggedById__c, ProfileId = logEntryEvent.ProfileId__c); + LoggerSettings__c loggingUserSettings = Logger.getUserSettings(loggingUser); + if (loggingUserSettings.IsPlatformEventStorageEnabled__c == true) { + logEntryEventsToSave.add(logEntryEvent); + } + } + return logEntryEventsToSave; + } + private void upsertLogs() { // To avoid making a callout for every log for details retrieved from api.status.salesforce.com, // try to query recent logs first to see if there is a recent log with the details already populated @@ -287,12 +299,11 @@ public without sharing class LogEntryEventHandler extends LoggerSObjectHandler { LoggerEmailUtils.sendErrorEmail(tagAssignmentSObjectType, saveResults); } - @SuppressWarnings('PMD.ApexSOQLInjection') private Map getTagNameToId(Schema.SObjectType tagSObjectType) { Map tagNameToId = new Map(); String tagQuery = 'SELECT Id, Name FROM ' + tagSObjectType + ' WHERE Name IN :tagNames'; - for (SObject tag : Database.query(tagQuery)) { + for (SObject tag : Database.query(String.escapeSingleQuotes(tagQuery))) { tagNameToId.put((String) tag.get('Name'), (Id) tag.get('Id')); } tagNameToId.putAll(this.insertMissingTags(tagSObjectType, tagNameToId)); diff --git a/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls-meta.xml b/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls-meta.xml index 871a8cfea..891916bb0 100644 --- a/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls-meta.xml +++ b/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls-meta.xml @@ -1,5 +1,5 @@ - 53.0 + 54.0 Active diff --git a/nebula-logger/core/main/log-management/classes/LogEntryFieldSetPicklist.cls-meta.xml b/nebula-logger/core/main/log-management/classes/LogEntryFieldSetPicklist.cls-meta.xml index 871a8cfea..891916bb0 100644 --- a/nebula-logger/core/main/log-management/classes/LogEntryFieldSetPicklist.cls-meta.xml +++ b/nebula-logger/core/main/log-management/classes/LogEntryFieldSetPicklist.cls-meta.xml @@ -1,5 +1,5 @@ - 53.0 + 54.0 Active diff --git a/nebula-logger/core/main/log-management/classes/LogEntryHandler.cls b/nebula-logger/core/main/log-management/classes/LogEntryHandler.cls index 1ca15e25f..71baae386 100644 --- a/nebula-logger/core/main/log-management/classes/LogEntryHandler.cls +++ b/nebula-logger/core/main/log-management/classes/LogEntryHandler.cls @@ -73,7 +73,8 @@ public without sharing class LogEntryHandler extends LoggerSObjectHandler { for (ApexClass apexClass : [ SELECT ApiVersion, CreatedById, CreatedDate, Id, LastModifiedById, LastModifiedDate, Name FROM ApexClass - WHERE NamespacePrefix = :getNamespacePrefix() AND Name IN :apexClassNames + WHERE Name IN :apexClassNames + ORDER BY NamespacePrefix NULLS LAST ]) { classNameToApexClass.put(apexClass.Name, apexClass); } @@ -251,7 +252,7 @@ public without sharing class LogEntryHandler extends LoggerSObjectHandler { new List{ sobjectDisplayFieldName, sobjectType, sobjectTypeRecordIds } ); - List results = Database.query(query); + List results = Database.query(String.escapeSingleQuotes(query)); recordIdToRecord.putAll(results); } diff --git a/nebula-logger/core/main/log-management/classes/LogEntryHandler.cls-meta.xml b/nebula-logger/core/main/log-management/classes/LogEntryHandler.cls-meta.xml index 871a8cfea..891916bb0 100644 --- a/nebula-logger/core/main/log-management/classes/LogEntryHandler.cls-meta.xml +++ b/nebula-logger/core/main/log-management/classes/LogEntryHandler.cls-meta.xml @@ -1,5 +1,5 @@ - 53.0 + 54.0 Active diff --git a/nebula-logger/core/main/log-management/classes/LogEntryTagHandler.cls-meta.xml b/nebula-logger/core/main/log-management/classes/LogEntryTagHandler.cls-meta.xml index 871a8cfea..891916bb0 100644 --- a/nebula-logger/core/main/log-management/classes/LogEntryTagHandler.cls-meta.xml +++ b/nebula-logger/core/main/log-management/classes/LogEntryTagHandler.cls-meta.xml @@ -1,5 +1,5 @@ - 53.0 + 54.0 Active diff --git a/nebula-logger/core/main/log-management/classes/LogHandler.cls b/nebula-logger/core/main/log-management/classes/LogHandler.cls index ba03e5606..3f0361185 100644 --- a/nebula-logger/core/main/log-management/classes/LogHandler.cls +++ b/nebula-logger/core/main/log-management/classes/LogHandler.cls @@ -7,7 +7,7 @@ * @group Log Management * @description Manages setting fields on `Log__c` before insert & before update */ -@SuppressWarnings('PMD.ApexCrudViolation') +@SuppressWarnings('PMD.ApexCrudViolation, PMD.CognitiveComplexity, PMD.CyclomaticComplexity') public without sharing class LogHandler extends LoggerSObjectHandler { @TestVisible private static final Map LOG_STATUS_NAME_TO_STATUS = loadActiveLogStatuses(); @@ -19,16 +19,6 @@ public without sharing class LogHandler extends LoggerSObjectHandler { @TestVisible private Map oldLogsById; - private static Map loadActiveLogStatuses() { - Map logStatusNameToStatus = new Map(); - for (LogStatus__mdt logStatus : LogStatus__mdt.getAll().values()) { - if (logStatus.IsActive__c == true) { - logStatusNameToStatus.put(logStatus.MasterLabel, logStatus); - } - } - return logStatusNameToStatus; - } - /** * @description Returns SObject Type that the handler is responsible for processing * @return The instance of `SObjectType` @@ -41,6 +31,8 @@ public without sharing class LogHandler extends LoggerSObjectHandler { this.logs = (List) triggerNew; this.setClosedStatusFields(); + // The log OwnerId field should support being manually changed, so only auto-set it on insert + this.setOwnerId(); // The log retention date field should support being manually changed, so only auto-set it on insert this.setLogRetentionDate(); } @@ -80,6 +72,40 @@ public without sharing class LogHandler extends LoggerSObjectHandler { } } + private void setOwnerId() { + // Loop through the logs and figure out what value has been configured as the default owner (if any) + Map ownerNamesByLoggingUserId = new Map(); + List logsToUpdate = new List(); + for (Log__c log : this.logs) { + String loggingUserDefaultLogOwner = getLoggingUserSettings(log).DefaultLogOwner__c; + + if (String.isBlank(loggingUserDefaultLogOwner) == true) { + continue; + } else if (loggingUserDefaultLogOwner instanceof Id) { + log.OwnerId = Id.valueOf(loggingUserDefaultLogOwner); + } else { + ownerNamesByLoggingUserId.put(log.LoggedBy__c, loggingUserDefaultLogOwner); + logsToUpdate.add(log); + } + } + + if (logsToUpdate.isEmpty()) { + return; + } + + // Populate OwnerId based on configured usernames + Map ownerIdByOwnerName = new Map(); + ownerIdByOwnerName.putAll(queryQueues(ownerNamesByLoggingUserId.values())); + ownerIdByOwnerName.putAll(queryUsers(ownerNamesByLoggingUserId.values())); + + for (Log__c log : logsToUpdate) { + String loggingUserDefaultLogOwner = getLoggingUserSettings(log).DefaultLogOwner__c; + if (ownerIdByOwnerName.containsKey(loggingUserDefaultLogOwner)) { + log.OwnerId = ownerIdByOwnerName.get(loggingUserDefaultLogOwner); + } + } + } + private void setLogRetentionDate() { Map scenarioToScenarioRule = queryScenarioRules(this.logs); for (Log__c log : this.logs) { @@ -89,8 +115,7 @@ public without sharing class LogHandler extends LoggerSObjectHandler { } // Load the logging user's settings - User loggingUser = new User(Id = log.LoggedBy__c, ProfileId = log.ProfileId__c); - LoggerSettings__c loggingUserSettings = Logger.getUserSettings(loggingUser); + LoggerSettings__c loggingUserSettings = getLoggingUserSettings(log); // Load the configured scenario rule (if one exists) LogScenarioRule__mdt matchingScenarioRule = scenarioToScenarioRule.get(log.Scenario__c); @@ -109,7 +134,7 @@ public without sharing class LogHandler extends LoggerSObjectHandler { } private void setPriority() { - List picklistEntries = Schema.Log__c.Priority__c.getDescribe().getPicklistValues(); + List picklistEntries = Schema.Log__c.Priority__c.getDescribe().getPicklistValues(); // 3 assumptions // 1. Assume that that there will always be 3+ picklist values for the Priority__c field (out of the box, the values are: High, Medium, Low) @@ -139,8 +164,7 @@ public without sharing class LogHandler extends LoggerSObjectHandler { } // Load the logging user's settings - User loggingUser = new User(Id = log.LoggedBy__c, ProfileId = log.ProfileId__c); - LoggerSettings__c loggingUserSettings = Logger.getUserSettings(loggingUser); + LoggerSettings__c loggingUserSettings = getLoggingUserSettings(log); // Ignore blank and unsupported values if (loggingUserSettings.DefaultLogShareAccessLevel__c != 'Read' && loggingUserSettings.DefaultLogShareAccessLevel__c != 'Edit') { @@ -158,6 +182,37 @@ public without sharing class LogHandler extends LoggerSObjectHandler { Database.insert(logShares, false); } + private static LoggerSettings__c getLoggingUserSettings(Log__c log) { + User loggingUser = new User(Id = log.LoggedBy__c, ProfileId = log.ProfileId__c); + return Logger.getUserSettings(loggingUser); + } + + private static Map loadActiveLogStatuses() { + Map logStatusNameToStatus = new Map(); + for (LogStatus__mdt logStatus : LogStatus__mdt.getAll().values()) { + if (logStatus.IsActive__c == true) { + logStatusNameToStatus.put(logStatus.MasterLabel, logStatus); + } + } + return logStatusNameToStatus; + } + + private static Map queryQueues(List possibleQueueNames) { + Map queuesByDeveloperName = new Map(); + for (Group queue : [SELECT Id, DeveloperName FROM Group WHERE Type = 'Queue' AND DeveloperName IN :possibleQueueNames]) { + queuesByDeveloperName.put(queue.DeveloperName, queue.Id); + } + return queuesByDeveloperName; + } + + private static Map queryUsers(List possibleUsernames) { + Map usersByUsername = new Map(); + for (User user : [SELECT Id, Username FROM User WHERE Username IN :possibleUsernames]) { + usersByUsername.put(user.Username, user.Id); + } + return usersByUsername; + } + private static Map queryScenarioRules(List logs) { List scenarios = new List(); for (Log__c log : logs) { diff --git a/nebula-logger/core/main/log-management/classes/LogHandler.cls-meta.xml b/nebula-logger/core/main/log-management/classes/LogHandler.cls-meta.xml index 871a8cfea..891916bb0 100644 --- a/nebula-logger/core/main/log-management/classes/LogHandler.cls-meta.xml +++ b/nebula-logger/core/main/log-management/classes/LogHandler.cls-meta.xml @@ -1,5 +1,5 @@ - 53.0 + 54.0 Active diff --git a/nebula-logger/core/main/log-management/classes/LogMassDeleteExtension.cls-meta.xml b/nebula-logger/core/main/log-management/classes/LogMassDeleteExtension.cls-meta.xml index 871a8cfea..891916bb0 100644 --- a/nebula-logger/core/main/log-management/classes/LogMassDeleteExtension.cls-meta.xml +++ b/nebula-logger/core/main/log-management/classes/LogMassDeleteExtension.cls-meta.xml @@ -1,5 +1,5 @@ - 53.0 + 54.0 Active diff --git a/nebula-logger/core/main/log-management/classes/LoggerSObjectHandler.cls-meta.xml b/nebula-logger/core/main/log-management/classes/LoggerSObjectHandler.cls-meta.xml index 871a8cfea..891916bb0 100644 --- a/nebula-logger/core/main/log-management/classes/LoggerSObjectHandler.cls-meta.xml +++ b/nebula-logger/core/main/log-management/classes/LoggerSObjectHandler.cls-meta.xml @@ -1,5 +1,5 @@ - 53.0 + 54.0 Active diff --git a/nebula-logger/core/main/log-management/classes/LoggerSObjectMetadata.cls b/nebula-logger/core/main/log-management/classes/LoggerSObjectMetadata.cls new file mode 100644 index 000000000..26dc92c89 --- /dev/null +++ b/nebula-logger/core/main/log-management/classes/LoggerSObjectMetadata.cls @@ -0,0 +1,98 @@ +//------------------------------------------------------------------------------------------------// +// 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 Provides details to LWCs about Logger's `SObjects`, using `@AuraEnabled` properties + */ +public without sharing class LoggerSObjectMetadata { + /** + * @description Provides schema details about the the platform event object `LogEntryEvent__e` + * @return An instance of `LoggerSObjectMetadata.SObjectSchema` for the platform event `LogEntryEvent__e` + */ + @AuraEnabled(cacheable=true) + public static SObjectSchema getLogEntryEventSchema() { + return buildSObjectSchema(Schema.LogEntryEvent__e.SObjectType.getDescribe()); + } + + /** + * @description Provides schema details about the the custom settings object `LoggerSettings__c` + * @return An instance of `LoggerSObjectMetadata.SObjectSchema` for the platform event `LoggerSettings__c` + */ + @AuraEnabled(cacheable=true) + public static SObjectSchema getLoggerSettingsSchema() { + return buildSObjectSchema(Schema.LoggerSettings__c.SObjectType.getDescribe()); + } + + private static SObjectSchema buildSObjectSchema(Schema.DescribeSObjectResult describe) { + SObjectSchema schema = new SObjectSchema(); + schema.apiName = describe.getName(); + schema.localApiName = describe.getLocalName(); + schema.label = describe.getLabel(); + schema.labelPlural = describe.getLabelPlural(); + schema.namespacePrefix = describe.getLocalName() == describe.getName() ? '' : describe.getName().removeEnd(describe.getLocalName()); + + schema.fields = new Map(); + for (Schema.SObjectField field : describe.fields.getMap().values()) { + Schema.DescribeFieldResult fieldDescribe = field.getDescribe(); + schema.fields.put(fieldDescribe.getLocalName(), buildFieldSchema(fieldDescribe)); + } + return schema; + } + + private static FieldSchema buildFieldSchema(Schema.DescribeFieldResult fieldDescribe) { + FieldSchema schema = new FieldSchema(); + schema.apiName = fieldDescribe.getName(); + schema.localApiName = fieldDescribe.getLocalName(); + schema.inlineHelpText = fieldDescribe.getInlineHelpText(); + schema.label = fieldDescribe.getLabel(); + schema.type = fieldDescribe.getType().name().toLowerCase(); + + return schema; + } + + /** + * @description Inner class for `SObject` details to LWCs, using `@AuraEnabled` properties + */ + public class SObjectSchema { + @AuraEnabled + public String apiName; + + @AuraEnabled + public String localApiName; + + @AuraEnabled + public String namespacePrefix; + + @AuraEnabled + public String label; + + @AuraEnabled + public String labelPlural; + + @AuraEnabled + public Map fields; + } + + /** + * @description Inner class for `SObjectField` details to LWCs, using `@AuraEnabled` properties + */ + public class FieldSchema { + @AuraEnabled + public String apiName; + + @AuraEnabled + public String localApiName; + + @AuraEnabled + public String inlineHelpText; + + @AuraEnabled + public String label; + + @AuraEnabled + public String type; + } +} diff --git a/nebula-logger/extra-tests/tests/LogEntryEventBuilder_IntegrationTests.cls-meta.xml b/nebula-logger/core/main/log-management/classes/LoggerSObjectMetadata.cls-meta.xml similarity index 80% rename from nebula-logger/extra-tests/tests/LogEntryEventBuilder_IntegrationTests.cls-meta.xml rename to nebula-logger/core/main/log-management/classes/LoggerSObjectMetadata.cls-meta.xml index 871a8cfea..891916bb0 100644 --- a/nebula-logger/extra-tests/tests/LogEntryEventBuilder_IntegrationTests.cls-meta.xml +++ b/nebula-logger/core/main/log-management/classes/LoggerSObjectMetadata.cls-meta.xml @@ -1,5 +1,5 @@ - 53.0 + 54.0 Active diff --git a/nebula-logger/core/main/log-management/classes/LoggerSettingsController.cls b/nebula-logger/core/main/log-management/classes/LoggerSettingsController.cls index 2e55b9229..24ada1915 100644 --- a/nebula-logger/core/main/log-management/classes/LoggerSettingsController.cls +++ b/nebula-logger/core/main/log-management/classes/LoggerSettingsController.cls @@ -70,7 +70,7 @@ public without sharing class LoggerSettingsController { * @description Creates a new, unsaved `LoggerSettings__c` record * @return A new `LoggerSettings__c` record, with all fields populated with default values */ - @AuraEnabled + @AuraEnabled(cacheable=true) public static LoggerSettings__c createRecord() { return (LoggerSettings__c) Schema.LoggerSettings__c.SObjectType.newSObject(null, true); } @@ -110,7 +110,7 @@ public without sharing class LoggerSettingsController { * @description Returns the `Organization` record for the current environment * @return The current environment's `Organization` record */ - @AuraEnabled + @AuraEnabled(cacheable=true) public static Organization getOrganization() { return [SELECT Id, Name FROM Organization]; } @@ -256,30 +256,13 @@ public without sharing class LoggerSettingsController { } private static List queryLoggerSettings() { - return [ - SELECT - CreatedBy.Username, - CreatedById, - CreatedDate, - DefaultLogShareAccessLevel__c, - DefaultNumberOfDaysToRetainLogs__c, - DefaultSaveMethod__c, - Id, - IsAnonymousModeEnabled__c, - IsApexSystemDebugLoggingEnabled__c, - IsJavaScriptConsoleLoggingEnabled__c, - IsDataMaskingEnabled__c, - IsEnabled__c, - LastModifiedBy.Username, - LastModifiedById, - LastModifiedDate, - LoggingLevel__c, - SetupOwner.Name, - SetupOwner.Type, - SetupOwnerId, - StripInaccessibleRecordFields__c - FROM LoggerSettings__c - ]; + List fieldNames = new List(Schema.LoggerSettings__c.SObjectType.getDescribe().fields.getMap().keySet()); + fieldNames.add('CreatedBy.Username'); + fieldNames.add('LastModifiedBy.Username'); + fieldNames.add('SetupOwner.Name'); + fieldNames.add('SetupOwner.Type'); + String query = 'SELECT ' + String.join(fieldNames, ', ') + ' FROM ' + Schema.LoggerSettings__c.SObjectType; + return (List) Database.query(String.escapeSingleQuotes(query)); } private static Map querySetupOwnerNames(List setupOwnerIds) { diff --git a/nebula-logger/core/main/log-management/classes/LoggerSettingsController.cls-meta.xml b/nebula-logger/core/main/log-management/classes/LoggerSettingsController.cls-meta.xml index 871a8cfea..891916bb0 100644 --- a/nebula-logger/core/main/log-management/classes/LoggerSettingsController.cls-meta.xml +++ b/nebula-logger/core/main/log-management/classes/LoggerSettingsController.cls-meta.xml @@ -1,5 +1,5 @@ - 53.0 + 54.0 Active diff --git a/nebula-logger/core/main/log-management/classes/LoggerTagHandler.cls-meta.xml b/nebula-logger/core/main/log-management/classes/LoggerTagHandler.cls-meta.xml index 871a8cfea..891916bb0 100644 --- a/nebula-logger/core/main/log-management/classes/LoggerTagHandler.cls-meta.xml +++ b/nebula-logger/core/main/log-management/classes/LoggerTagHandler.cls-meta.xml @@ -1,5 +1,5 @@ - 53.0 + 54.0 Active diff --git a/nebula-logger/core/main/log-management/classes/RelatedLogEntriesController.cls b/nebula-logger/core/main/log-management/classes/RelatedLogEntriesController.cls index c6f0bd886..e48f92a66 100644 --- a/nebula-logger/core/main/log-management/classes/RelatedLogEntriesController.cls +++ b/nebula-logger/core/main/log-management/classes/RelatedLogEntriesController.cls @@ -78,7 +78,7 @@ public with sharing class RelatedLogEntriesController { return (List) Search.query(logEntrySearch).get(0); } - @SuppressWarnings('PMD.ExcessiveParameterList, PMD.AvoidDebugStatements') + @SuppressWarnings('PMD.ExcessiveParameterList') private static List query(Id recordId, String fieldsClause, String orderByClause, Integer rowLimit) { List queryTextReplacements = new List{ fieldsClause, @@ -90,7 +90,7 @@ public with sharing class RelatedLogEntriesController { String logEntryQuery = 'SELECT {0} FROM {1} WHERE {2} = :recordId ORDER BY {3} LIMIT {4}'; logEntryQuery = String.format(logEntryQuery, queryTextReplacements); - return (List) Database.query(logEntryQuery); + return (List) Database.query(String.escapeSingleQuotes(logEntryQuery)); } private static String getFieldsClause(List fields) { diff --git a/nebula-logger/core/main/log-management/classes/RelatedLogEntriesController.cls-meta.xml b/nebula-logger/core/main/log-management/classes/RelatedLogEntriesController.cls-meta.xml index 871a8cfea..891916bb0 100644 --- a/nebula-logger/core/main/log-management/classes/RelatedLogEntriesController.cls-meta.xml +++ b/nebula-logger/core/main/log-management/classes/RelatedLogEntriesController.cls-meta.xml @@ -1,5 +1,5 @@ - 53.0 + 54.0 Active 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 5ce445284..9b65875f7 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 @@ -523,6 +523,12 @@ flexipage:fieldSection flexipage_fieldSection8 + 1 OR 2 + + {!Record.HasRecordId__c} + EQUAL + true + {!Record.HasRecordJson__c} EQUAL diff --git a/nebula-logger/core/main/log-management/flexipages/LogRecordPage.flexipage-meta.xml b/nebula-logger/core/main/log-management/flexipages/LogRecordPage.flexipage-meta.xml index a74ebf30b..974483a16 100644 --- a/nebula-logger/core/main/log-management/flexipages/LogRecordPage.flexipage-meta.xml +++ b/nebula-logger/core/main/log-management/flexipages/LogRecordPage.flexipage-meta.xml @@ -7,7 +7,7 @@ actionNames - Log__c.ViewLogJSON + Log__c.OpenViewer ChangeOwnerOne diff --git a/nebula-logger/core/main/log-management/layouts/Log__c-Log Layout.layout-meta.xml b/nebula-logger/core/main/log-management/layouts/Log__c-Log Layout.layout-meta.xml index 1ef166d1d..161423505 100644 --- a/nebula-logger/core/main/log-management/layouts/Log__c-Log Layout.layout-meta.xml +++ b/nebula-logger/core/main/log-management/layouts/Log__c-Log Layout.layout-meta.xml @@ -321,7 +321,7 @@ Record - Log__c.ViewLogJSON + Log__c.OpenViewer QuickAction 0 diff --git a/nebula-logger/core/main/log-management/lwc/logEntryEventStream/__tests__/data/getLogEntryEventSchema.json b/nebula-logger/core/main/log-management/lwc/logEntryEventStream/__tests__/data/getLogEntryEventSchema.json new file mode 100644 index 000000000..972304089 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/logEntryEventStream/__tests__/data/getLogEntryEventSchema.json @@ -0,0 +1,807 @@ +{ + "namespacePrefix": null, + "localApiName": "LogEntryEvent__e", + "labelPlural": "Log Entry Events", + "label": "Log Entry Event", + "fields": { + "UserType__c": { + "type": "string", + "localApiName": "UserType__c", + "label": "User Type", + "inlineHelpText": null, + "apiName": "UserType__c" + }, + "UserRoleName__c": { + "type": "string", + "localApiName": "UserRoleName__c", + "label": "User Role Name", + "inlineHelpText": null, + "apiName": "UserRoleName__c" + }, + "UserRoleId__c": { + "type": "string", + "localApiName": "UserRoleId__c", + "label": "User Role ID", + "inlineHelpText": null, + "apiName": "UserRoleId__c" + }, + "UserLoggingLevel__c": { + "type": "string", + "localApiName": "UserLoggingLevel__c", + "label": "User Logging Level", + "inlineHelpText": null, + "apiName": "UserLoggingLevel__c" + }, + "UserLoggingLevelOrdinal__c": { + "type": "double", + "localApiName": "UserLoggingLevelOrdinal__c", + "label": "User Logging Level Ordinal", + "inlineHelpText": null, + "apiName": "UserLoggingLevelOrdinal__c" + }, + "UserLicenseName__c": { + "type": "string", + "localApiName": "UserLicenseName__c", + "label": "User License Name", + "inlineHelpText": null, + "apiName": "UserLicenseName__c" + }, + "UserLicenseId__c": { + "type": "string", + "localApiName": "UserLicenseId__c", + "label": "User License ID", + "inlineHelpText": null, + "apiName": "UserLicenseId__c" + }, + "UserLicenseDefinitionKey__c": { + "type": "string", + "localApiName": "UserLicenseDefinitionKey__c", + "label": "User License Definition Key", + "inlineHelpText": null, + "apiName": "UserLicenseDefinitionKey__c" + }, + "TriggerSObjectType__c": { + "type": "string", + "localApiName": "TriggerSObjectType__c", + "label": "Trigger SObject Type", + "inlineHelpText": null, + "apiName": "TriggerSObjectType__c" + }, + "TriggerOperationType__c": { + "type": "string", + "localApiName": "TriggerOperationType__c", + "label": "Trigger Operation Type", + "inlineHelpText": null, + "apiName": "TriggerOperationType__c" + }, + "TriggerIsExecuting__c": { + "type": "boolean", + "localApiName": "TriggerIsExecuting__c", + "label": "Trigger Is Executing", + "inlineHelpText": null, + "apiName": "TriggerIsExecuting__c" + }, + "TransactionId__c": { + "type": "string", + "localApiName": "TransactionId__c", + "label": "Transaction ID", + "inlineHelpText": null, + "apiName": "TransactionId__c" + }, + "TransactionEntryNumber__c": { + "type": "double", + "localApiName": "TransactionEntryNumber__c", + "label": "Transaction Entry Number", + "inlineHelpText": null, + "apiName": "TransactionEntryNumber__c" + }, + "Topics__c": { + "type": "textarea", + "localApiName": "Topics__c", + "label": "DEPRECATED: Topics", + "inlineHelpText": null, + "apiName": "Topics__c" + }, + "Timestamp__c": { + "type": "datetime", + "localApiName": "Timestamp__c", + "label": "Timestamp", + "inlineHelpText": null, + "apiName": "Timestamp__c" + }, + "TimestampString__c": { + "type": "string", + "localApiName": "TimestampString__c", + "label": "Timestamp String", + "inlineHelpText": null, + "apiName": "TimestampString__c" + }, + "TimeZoneName__c": { + "type": "string", + "localApiName": "TimeZoneName__c", + "label": "Time Zone Name", + "inlineHelpText": null, + "apiName": "TimeZoneName__c" + }, + "TimeZoneId__c": { + "type": "string", + "localApiName": "TimeZoneId__c", + "label": "Time Zone ID", + "inlineHelpText": null, + "apiName": "TimeZoneId__c" + }, + "ThemeDisplayed__c": { + "type": "string", + "localApiName": "ThemeDisplayed__c", + "label": "Theme Displayed", + "inlineHelpText": null, + "apiName": "ThemeDisplayed__c" + }, + "Tags__c": { + "type": "textarea", + "localApiName": "Tags__c", + "label": "Tags", + "inlineHelpText": null, + "apiName": "Tags__c" + }, + "SystemMode__c": { + "type": "string", + "localApiName": "SystemMode__c", + "label": "System Mode", + "inlineHelpText": null, + "apiName": "SystemMode__c" + }, + "StackTrace__c": { + "type": "textarea", + "localApiName": "StackTrace__c", + "label": "Stack Trace", + "inlineHelpText": null, + "apiName": "StackTrace__c" + }, + "SourceIp__c": { + "type": "string", + "localApiName": "SourceIp__c", + "label": "Source IP", + "inlineHelpText": null, + "apiName": "SourceIp__c" + }, + "SessionType__c": { + "type": "string", + "localApiName": "SessionType__c", + "label": "Session Type", + "inlineHelpText": null, + "apiName": "SessionType__c" + }, + "SessionSecurityLevel__c": { + "type": "string", + "localApiName": "SessionSecurityLevel__c", + "label": "Session Security Level", + "inlineHelpText": null, + "apiName": "SessionSecurityLevel__c" + }, + "SessionId__c": { + "type": "string", + "localApiName": "SessionId__c", + "label": "SessionId", + "inlineHelpText": null, + "apiName": "SessionId__c" + }, + "Scenario__c": { + "type": "string", + "localApiName": "Scenario__c", + "label": "Scenario", + "inlineHelpText": null, + "apiName": "Scenario__c" + }, + "RecordSObjectType__c": { + "type": "string", + "localApiName": "RecordSObjectType__c", + "label": "Related Record SObject Type", + "inlineHelpText": null, + "apiName": "RecordSObjectType__c" + }, + "RecordSObjectTypeNamespace__c": { + "type": "string", + "localApiName": "RecordSObjectTypeNamespace__c", + "label": "Related Record Object Namespace", + "inlineHelpText": null, + "apiName": "RecordSObjectTypeNamespace__c" + }, + "RecordSObjectClassification__c": { + "type": "string", + "localApiName": "RecordSObjectClassification__c", + "label": "Related Record SObject Classification", + "inlineHelpText": null, + "apiName": "RecordSObjectClassification__c" + }, + "RecordJson__c": { + "type": "textarea", + "localApiName": "RecordJson__c", + "label": "Related Record JSON", + "inlineHelpText": null, + "apiName": "RecordJson__c" + }, + "RecordJsonMasked__c": { + "type": "boolean", + "localApiName": "RecordJsonMasked__c", + "label": "Record JSON Masked", + "inlineHelpText": null, + "apiName": "RecordJsonMasked__c" + }, + "RecordId__c": { + "type": "string", + "localApiName": "RecordId__c", + "label": "Related Record ID", + "inlineHelpText": null, + "apiName": "RecordId__c" + }, + "RecordCollectionType__c": { + "type": "string", + "localApiName": "RecordCollectionType__c", + "label": "Related Record Collection Type", + "inlineHelpText": null, + "apiName": "RecordCollectionType__c" + }, + "ProfileName__c": { + "type": "string", + "localApiName": "ProfileName__c", + "label": "Profile Name", + "inlineHelpText": null, + "apiName": "ProfileName__c" + }, + "ProfileId__c": { + "type": "string", + "localApiName": "ProfileId__c", + "label": "Profile ID", + "inlineHelpText": null, + "apiName": "ProfileId__c" + }, + "ParentLogTransactionId__c": { + "type": "string", + "localApiName": "ParentLogTransactionId__c", + "label": "Parent Log Transaction ID", + "inlineHelpText": null, + "apiName": "ParentLogTransactionId__c" + }, + "OriginType__c": { + "type": "string", + "localApiName": "OriginType__c", + "label": "Origin Type", + "inlineHelpText": null, + "apiName": "OriginType__c" + }, + "OriginLocation__c": { + "type": "string", + "localApiName": "OriginLocation__c", + "label": "Origin Location", + "inlineHelpText": null, + "apiName": "OriginLocation__c" + }, + "OrganizationType__c": { + "type": "string", + "localApiName": "OrganizationType__c", + "label": "Organization Type", + "inlineHelpText": null, + "apiName": "OrganizationType__c" + }, + "OrganizationNamespacePrefix__c": { + "type": "string", + "localApiName": "OrganizationNamespacePrefix__c", + "label": "Organization Namespace Prefix", + "inlineHelpText": null, + "apiName": "OrganizationNamespacePrefix__c" + }, + "OrganizationName__c": { + "type": "string", + "localApiName": "OrganizationName__c", + "label": "Organization Name", + "inlineHelpText": null, + "apiName": "OrganizationName__c" + }, + "OrganizationInstanceName__c": { + "type": "string", + "localApiName": "OrganizationInstanceName__c", + "label": "Organization Instance Name", + "inlineHelpText": null, + "apiName": "OrganizationInstanceName__c" + }, + "OrganizationId__c": { + "type": "string", + "localApiName": "OrganizationId__c", + "label": "Organization ID", + "inlineHelpText": null, + "apiName": "OrganizationId__c" + }, + "OrganizationEnvironmentType__c": { + "type": "string", + "localApiName": "OrganizationEnvironmentType__c", + "label": "Organization Environment Type", + "inlineHelpText": null, + "apiName": "OrganizationEnvironmentType__c" + }, + "OrganizationDomainUrl__c": { + "type": "string", + "localApiName": "OrganizationDomainUrl__c", + "label": "Organization Domain URL", + "inlineHelpText": null, + "apiName": "OrganizationDomainUrl__c" + }, + "NetworkUrlPathPrefix__c": { + "type": "string", + "localApiName": "NetworkUrlPathPrefix__c", + "label": "Site URL Path Prefix", + "inlineHelpText": null, + "apiName": "NetworkUrlPathPrefix__c" + }, + "NetworkSelfRegistrationUrl__c": { + "type": "string", + "localApiName": "NetworkSelfRegistrationUrl__c", + "label": "Site Self Registration URL", + "inlineHelpText": null, + "apiName": "NetworkSelfRegistrationUrl__c" + }, + "NetworkName__c": { + "type": "string", + "localApiName": "NetworkName__c", + "label": "Site Name", + "inlineHelpText": null, + "apiName": "NetworkName__c" + }, + "NetworkLogoutUrl__c": { + "type": "string", + "localApiName": "NetworkLogoutUrl__c", + "label": "Site Logout URL", + "inlineHelpText": null, + "apiName": "NetworkLogoutUrl__c" + }, + "NetworkLoginUrl__c": { + "type": "string", + "localApiName": "NetworkLoginUrl__c", + "label": "Site Login URL", + "inlineHelpText": null, + "apiName": "NetworkLoginUrl__c" + }, + "NetworkId__c": { + "type": "string", + "localApiName": "NetworkId__c", + "label": "Network ID", + "inlineHelpText": null, + "apiName": "NetworkId__c" + }, + "Message__c": { + "type": "textarea", + "localApiName": "Message__c", + "label": "Message", + "inlineHelpText": null, + "apiName": "Message__c" + }, + "MessageTruncated__c": { + "type": "boolean", + "localApiName": "MessageTruncated__c", + "label": "Message Truncated", + "inlineHelpText": null, + "apiName": "MessageTruncated__c" + }, + "MessageMasked__c": { + "type": "boolean", + "localApiName": "MessageMasked__c", + "label": "Message Masked", + "inlineHelpText": null, + "apiName": "MessageMasked__c" + }, + "LogoutUrl__c": { + "type": "string", + "localApiName": "LogoutUrl__c", + "label": "Logout URL", + "inlineHelpText": null, + "apiName": "LogoutUrl__c" + }, + "LoginType__c": { + "type": "string", + "localApiName": "LoginType__c", + "label": "Login Type", + "inlineHelpText": null, + "apiName": "LoginType__c" + }, + "LoginPlatform__c": { + "type": "string", + "localApiName": "LoginPlatform__c", + "label": "Login Platform", + "inlineHelpText": null, + "apiName": "LoginPlatform__c" + }, + "LoginHistoryId__c": { + "type": "string", + "localApiName": "LoginHistoryId__c", + "label": "Login History ID", + "inlineHelpText": null, + "apiName": "LoginHistoryId__c" + }, + "LoginDomain__c": { + "type": "string", + "localApiName": "LoginDomain__c", + "label": "DEPRECATED: Login Domain", + "inlineHelpText": null, + "apiName": "LoginDomain__c" + }, + "LoginBrowser__c": { + "type": "string", + "localApiName": "LoginBrowser__c", + "label": "Login Browser", + "inlineHelpText": null, + "apiName": "LoginBrowser__c" + }, + "LoginApplication__c": { + "type": "string", + "localApiName": "LoginApplication__c", + "label": "Login Application", + "inlineHelpText": null, + "apiName": "LoginApplication__c" + }, + "LoggingLevel__c": { + "type": "string", + "localApiName": "LoggingLevel__c", + "label": "Logging Level", + "inlineHelpText": null, + "apiName": "LoggingLevel__c" + }, + "LoggingLevelOrdinal__c": { + "type": "double", + "localApiName": "LoggingLevelOrdinal__c", + "label": "Logging Level Ordinal", + "inlineHelpText": null, + "apiName": "LoggingLevelOrdinal__c" + }, + "LoggerVersionNumber__c": { + "type": "string", + "localApiName": "LoggerVersionNumber__c", + "label": "Logger Version Number", + "inlineHelpText": null, + "apiName": "LoggerVersionNumber__c" + }, + "LoggedByUsername__c": { + "type": "string", + "localApiName": "LoggedByUsername__c", + "label": "Username", + "inlineHelpText": null, + "apiName": "LoggedByUsername__c" + }, + "LoggedById__c": { + "type": "string", + "localApiName": "LoggedById__c", + "label": "Logged By ID", + "inlineHelpText": null, + "apiName": "LoggedById__c" + }, + "Locale__c": { + "type": "string", + "localApiName": "Locale__c", + "label": "Locale", + "inlineHelpText": null, + "apiName": "Locale__c" + }, + "LimitsSoslSearchesUsed__c": { + "type": "double", + "localApiName": "LimitsSoslSearchesUsed__c", + "label": "SOSL Searches Used", + "inlineHelpText": null, + "apiName": "LimitsSoslSearchesUsed__c" + }, + "LimitsSoslSearchesMax__c": { + "type": "double", + "localApiName": "LimitsSoslSearchesMax__c", + "label": "SOSL Searches Max", + "inlineHelpText": null, + "apiName": "LimitsSoslSearchesMax__c" + }, + "LimitsSoqlQueryRowsUsed__c": { + "type": "double", + "localApiName": "LimitsSoqlQueryRowsUsed__c", + "label": "SOQL Query Rows Used", + "inlineHelpText": null, + "apiName": "LimitsSoqlQueryRowsUsed__c" + }, + "LimitsSoqlQueryRowsMax__c": { + "type": "double", + "localApiName": "LimitsSoqlQueryRowsMax__c", + "label": "SOQL Query Rows Max", + "inlineHelpText": null, + "apiName": "LimitsSoqlQueryRowsMax__c" + }, + "LimitsSoqlQueryLocatorRowsUsed__c": { + "type": "double", + "localApiName": "LimitsSoqlQueryLocatorRowsUsed__c", + "label": "SOQL Query Locator Rows Used", + "inlineHelpText": null, + "apiName": "LimitsSoqlQueryLocatorRowsUsed__c" + }, + "LimitsSoqlQueryLocatorRowsMax__c": { + "type": "double", + "localApiName": "LimitsSoqlQueryLocatorRowsMax__c", + "label": "SOQL Query Locator Rows Max", + "inlineHelpText": null, + "apiName": "LimitsSoqlQueryLocatorRowsMax__c" + }, + "LimitsSoqlQueriesUsed__c": { + "type": "double", + "localApiName": "LimitsSoqlQueriesUsed__c", + "label": "SOQL Queries Used", + "inlineHelpText": null, + "apiName": "LimitsSoqlQueriesUsed__c" + }, + "LimitsSoqlQueriesMax__c": { + "type": "double", + "localApiName": "LimitsSoqlQueriesMax__c", + "label": "SOQL Queries Max", + "inlineHelpText": null, + "apiName": "LimitsSoqlQueriesMax__c" + }, + "LimitsQueueableJobsUsed__c": { + "type": "double", + "localApiName": "LimitsQueueableJobsUsed__c", + "label": "Queueable Jobs Used", + "inlineHelpText": null, + "apiName": "LimitsQueueableJobsUsed__c" + }, + "LimitsQueueableJobsMax__c": { + "type": "double", + "localApiName": "LimitsQueueableJobsMax__c", + "label": "Queueable Jobs Max", + "inlineHelpText": null, + "apiName": "LimitsQueueableJobsMax__c" + }, + "LimitsPublishImmediateDmlStatementsUsed__c": { + "type": "double", + "localApiName": "LimitsPublishImmediateDmlStatementsUsed__c", + "label": "Publish Immediate Statements DML Used", + "inlineHelpText": null, + "apiName": "LimitsPublishImmediateDmlStatementsUsed__c" + }, + "LimitsPublishImmediateDmlStatementsMax__c": { + "type": "double", + "localApiName": "LimitsPublishImmediateDmlStatementsMax__c", + "label": "Publish Immediate Statements DML Max", + "inlineHelpText": null, + "apiName": "LimitsPublishImmediateDmlStatementsMax__c" + }, + "LimitsMobilePushApexCallsUsed__c": { + "type": "double", + "localApiName": "LimitsMobilePushApexCallsUsed__c", + "label": "Mobile Push Apex Calls Used", + "inlineHelpText": null, + "apiName": "LimitsMobilePushApexCallsUsed__c" + }, + "LimitsMobilePushApexCallsMax__c": { + "type": "double", + "localApiName": "LimitsMobilePushApexCallsMax__c", + "label": "Mobile Push Apex Calls Max", + "inlineHelpText": null, + "apiName": "LimitsMobilePushApexCallsMax__c" + }, + "LimitsHeapSizeUsed__c": { + "type": "double", + "localApiName": "LimitsHeapSizeUsed__c", + "label": "Heap Size Used", + "inlineHelpText": null, + "apiName": "LimitsHeapSizeUsed__c" + }, + "LimitsHeapSizeMax__c": { + "type": "double", + "localApiName": "LimitsHeapSizeMax__c", + "label": "Heap Size Max", + "inlineHelpText": null, + "apiName": "LimitsHeapSizeMax__c" + }, + "LimitsFutureCallsUsed__c": { + "type": "double", + "localApiName": "LimitsFutureCallsUsed__c", + "label": "Future Calls Used", + "inlineHelpText": null, + "apiName": "LimitsFutureCallsUsed__c" + }, + "LimitsFutureCallsMax__c": { + "type": "double", + "localApiName": "LimitsFutureCallsMax__c", + "label": "Future Calls Max", + "inlineHelpText": null, + "apiName": "LimitsFutureCallsMax__c" + }, + "LimitsEmailInvocationsUsed__c": { + "type": "double", + "localApiName": "LimitsEmailInvocationsUsed__c", + "label": "Email Invocations Used", + "inlineHelpText": null, + "apiName": "LimitsEmailInvocationsUsed__c" + }, + "LimitsEmailInvocationsMax__c": { + "type": "double", + "localApiName": "LimitsEmailInvocationsMax__c", + "label": "Email Invocations Max", + "inlineHelpText": null, + "apiName": "LimitsEmailInvocationsMax__c" + }, + "LimitsDmlStatementsUsed__c": { + "type": "double", + "localApiName": "LimitsDmlStatementsUsed__c", + "label": "DML Statements Used", + "inlineHelpText": null, + "apiName": "LimitsDmlStatementsUsed__c" + }, + "LimitsDmlStatementsMax__c": { + "type": "double", + "localApiName": "LimitsDmlStatementsMax__c", + "label": "DML Statements Max", + "inlineHelpText": null, + "apiName": "LimitsDmlStatementsMax__c" + }, + "LimitsDmlRowsUsed__c": { + "type": "double", + "localApiName": "LimitsDmlRowsUsed__c", + "label": "DML Rows Used", + "inlineHelpText": null, + "apiName": "LimitsDmlRowsUsed__c" + }, + "LimitsDmlRowsMax__c": { + "type": "double", + "localApiName": "LimitsDmlRowsMax__c", + "label": "DML Rows Max", + "inlineHelpText": null, + "apiName": "LimitsDmlRowsMax__c" + }, + "LimitsCpuTimeUsed__c": { + "type": "double", + "localApiName": "LimitsCpuTimeUsed__c", + "label": "CPU Time Used", + "inlineHelpText": null, + "apiName": "LimitsCpuTimeUsed__c" + }, + "LimitsCpuTimeMax__c": { + "type": "double", + "localApiName": "LimitsCpuTimeMax__c", + "label": "CPU Time Max", + "inlineHelpText": null, + "apiName": "LimitsCpuTimeMax__c" + }, + "LimitsCalloutsUsed__c": { + "type": "double", + "localApiName": "LimitsCalloutsUsed__c", + "label": "Callouts Used", + "inlineHelpText": null, + "apiName": "LimitsCalloutsUsed__c" + }, + "LimitsCalloutsMax__c": { + "type": "double", + "localApiName": "LimitsCalloutsMax__c", + "label": "Callouts Max", + "inlineHelpText": null, + "apiName": "LimitsCalloutsMax__c" + }, + "LimitsAsyncCallsUsed__c": { + "type": "double", + "localApiName": "LimitsAsyncCallsUsed__c", + "label": "Async Calls Used", + "inlineHelpText": null, + "apiName": "LimitsAsyncCallsUsed__c" + }, + "LimitsAsyncCallsMax__c": { + "type": "double", + "localApiName": "LimitsAsyncCallsMax__c", + "label": "Async Calls Max", + "inlineHelpText": null, + "apiName": "LimitsAsyncCallsMax__c" + }, + "LimitsAggregateQueryMax__c": { + "type": "double", + "localApiName": "LimitsAggregateQueryMax__c", + "label": "Limits AggregateQueryMax_", + "inlineHelpText": null, + "apiName": "LimitsAggregateQueryMax__c" + }, + "LimitsAggregateQueriesUsed__c": { + "type": "double", + "localApiName": "LimitsAggregateQueriesUsed__c", + "label": "Aggregate Queries Used", + "inlineHelpText": null, + "apiName": "LimitsAggregateQueriesUsed__c" + }, + "LimitsAggregateQueriesMax__c": { + "type": "double", + "localApiName": "LimitsAggregateQueriesMax__c", + "label": "Aggregate Queries Max", + "inlineHelpText": null, + "apiName": "LimitsAggregateQueriesMax__c" + }, + "ExceptionType__c": { + "type": "string", + "localApiName": "ExceptionType__c", + "label": "Exception Type", + "inlineHelpText": null, + "apiName": "ExceptionType__c" + }, + "ExceptionStackTrace__c": { + "type": "textarea", + "localApiName": "ExceptionStackTrace__c", + "label": "Exception Stack Trace", + "inlineHelpText": null, + "apiName": "ExceptionStackTrace__c" + }, + "ExceptionMessage__c": { + "type": "textarea", + "localApiName": "ExceptionMessage__c", + "label": "Exception Message", + "inlineHelpText": null, + "apiName": "ExceptionMessage__c" + }, + "EpochTimestamp__c": { + "type": "double", + "localApiName": "EpochTimestamp__c", + "label": "Epoch Timestamp", + "inlineHelpText": null, + "apiName": "EpochTimestamp__c" + }, + "DatabaseResultType__c": { + "type": "string", + "localApiName": "DatabaseResultType__c", + "label": "Database Result Type", + "inlineHelpText": null, + "apiName": "DatabaseResultType__c" + }, + "DatabaseResultJson__c": { + "type": "textarea", + "localApiName": "DatabaseResultJson__c", + "label": "Database Result JSON", + "inlineHelpText": null, + "apiName": "DatabaseResultJson__c" + }, + "DatabaseResultCollectionType__c": { + "type": "string", + "localApiName": "DatabaseResultCollectionType__c", + "label": "Database Result Collection Type", + "inlineHelpText": null, + "apiName": "DatabaseResultCollectionType__c" + }, + "ComponentType__c": { + "type": "string", + "localApiName": "ComponentType__c", + "label": "Component Type", + "inlineHelpText": null, + "apiName": "ComponentType__c" + }, + "ApiVersion__c": { + "type": "string", + "localApiName": "ApiVersion__c", + "label": "API Version", + "inlineHelpText": null, + "apiName": "ApiVersion__c" + }, + "EventUuid": { + "type": "string", + "localApiName": "EventUuid", + "label": "Event UUID", + "inlineHelpText": null, + "apiName": "EventUuid" + }, + "CreatedById": { + "type": "reference", + "localApiName": "CreatedById", + "label": "Created By ID", + "inlineHelpText": null, + "apiName": "CreatedById" + }, + "CreatedDate": { + "type": "datetime", + "localApiName": "CreatedDate", + "label": "Created Date", + "inlineHelpText": null, + "apiName": "CreatedDate" + }, + "ReplayId": { + "type": "string", + "localApiName": "ReplayId", + "label": "Replay ID", + "inlineHelpText": null, + "apiName": "ReplayId" + } + }, + "apiName": "LogEntryEvent__e" +} diff --git a/nebula-logger/core/main/log-management/lwc/logEntryEventStream/__tests__/logEntryEventStream.test.js b/nebula-logger/core/main/log-management/lwc/logEntryEventStream/__tests__/logEntryEventStream.test.js index c91513aae..1dff298ab 100644 --- a/nebula-logger/core/main/log-management/lwc/logEntryEventStream/__tests__/logEntryEventStream.test.js +++ b/nebula-logger/core/main/log-management/lwc/logEntryEventStream/__tests__/logEntryEventStream.test.js @@ -1,6 +1,7 @@ import { createElement } from 'lwc'; import LogEntryEventStream from 'c/logEntryEventStream'; import { jestMockPublish } from 'lightning/empApi'; +import getLogEntryEventSchema from '@salesforce/apex/LoggerSObjectMetadata.getLogEntryEventSchema'; const loggingLevels = { FINEST: 2, @@ -24,136 +25,193 @@ const mockLogEntryEventTemplate = { TransactionEntryNumber__c: 1 }; -function getPlatformEventText(mockLogEntryEvent) { +const mockLogEntryEventSchemaTemplate = require('./data/getLogEntryEventSchema.json'); + +jest.mock( + '@salesforce/apex/LoggerSObjectMetadata.getLogEntryEventSchema', + () => { + return { + default: jest.fn() + }; + }, + { virtual: true } +); + +async function createStreamElement(namespace) { + const mockLogEntryEventSchema = generateLogEntryEventSchema(namespace); + getLogEntryEventSchema.mockResolvedValue(mockLogEntryEventSchema); + const element = createElement('log-entry-event-stream', { + is: LogEntryEventStream + }); + document.body.appendChild(element); + await Promise.resolve(); + return element; +} + +function generateLogEntryEventSchema(namespace) { + namespace = !!namespace ? namespace + '__' : ''; + const schemaTemplate = { ...mockLogEntryEventSchemaTemplate }; + const schema = { ...schemaTemplate }; + schema.apiName += namespace; + schema.namespacePrefix = namespace; + schema.fields = {}; + Object.keys(schemaTemplate.fields).forEach(templateKey => { + schema.fields[templateKey] = schemaTemplate.fields[templateKey]; + schema.fields[templateKey].apiName = namespace + templateKey; + }); + return schema; +} + +function generatePlatformEvent(namespace) { + namespace = !!namespace ? namespace + '__' : ''; + return { + [namespace + 'LoggedByUsername__c']: 'some.person@test.com', + [namespace + 'LoggingLevel__c']: 'INFO', + [namespace + 'LoggingLevelOrdinal__c']: 6, + [namespace + 'Message__c']: 'My important log entry message', + [namespace + 'OriginType__c']: 'Apex', + [namespace + 'OriginLocation__c']: 'SomeClass.someMethod', + [namespace + 'Timestamp__c']: new Date().toISOString(), + [namespace + 'TransactionId__c']: 'ABC-1234', + [namespace + 'TransactionEntryNumber__c']: 1 + }; +} + +function getPlatformEventText(mockLogEntryEvent, namespace) { + namespace = !!namespace ? namespace + '__' : ''; return ( - mockLogEntryEvent.Timestamp__c + - mockLogEntryEvent.LoggedByUsername__c + + mockLogEntryEvent[namespace + 'Timestamp__c'] + + mockLogEntryEvent[namespace + 'LoggedByUsername__c'] + ' - ' + - mockLogEntryEvent.TransactionId__c + + mockLogEntryEvent[namespace + 'TransactionId__c'] + '__' + - mockLogEntryEvent.TransactionEntryNumber__c + - mockLogEntryEvent.OriginType__c + + mockLogEntryEvent[namespace + 'TransactionEntryNumber__c'] + + mockLogEntryEvent[namespace + 'OriginType__c'] + '.' + - mockLogEntryEvent.OriginLocation__c + + mockLogEntryEvent[namespace + 'OriginLocation__c'] + ' ' + - mockLogEntryEvent.LoggingLevel__c + - mockLogEntryEvent.Message__c + mockLogEntryEvent[namespace + 'LoggingLevel__c'] + + mockLogEntryEvent[namespace + 'Message__c'] ); } +async function publishPlatformEvent(namespace, mockLogEntryEvent) { + const mockLogEntryEventSchema = generateLogEntryEventSchema(namespace); + await jestMockPublish('/event/' + mockLogEntryEventSchema.apiName, { + data: { + payload: mockLogEntryEvent + } + }); +} + describe('LogEntryEventStream tests', () => { afterEach(() => { while (document.body.firstChild) { document.body.removeChild(document.body.firstChild); } }); + + const namespaces = ['', 'SomeNamespace']; it('streams a single log entry event', async () => { - const element = createElement('log-entry-event-stream', { - is: LogEntryEventStream - }); - document.body.appendChild(element); - await Promise.resolve(); + await Promise.all( + namespaces.map(async namespace => { + const element = await createStreamElement(namespace); + const mockLogEntryEvent = await generatePlatformEvent(namespace); - const mockLogEntryEvent = { ...mockLogEntryEventTemplate }; - await jestMockPublish('/event/LogEntryEvent__e', { - data: { - payload: mockLogEntryEvent - } - }); + await publishPlatformEvent(namespace, mockLogEntryEvent); - const expectedStreamText = getPlatformEventText(mockLogEntryEvent); - const eventStreamDiv = element.shadowRoot.querySelector('.event-stream'); - expect(eventStreamDiv.textContent).toBe(expectedStreamText); + const expectedStreamText = getPlatformEventText(mockLogEntryEvent, namespace); + const eventStreamDiv = element.shadowRoot.querySelector('.event-stream'); + expect(eventStreamDiv.textContent).toBe(expectedStreamText); + }) + ); }); it('toggles streaming when button clicked', async () => { - const element = createElement('log-entry-event-stream', { - is: LogEntryEventStream - }); - document.body.appendChild(element); - await Promise.resolve(); - const toggleButton = element.shadowRoot.querySelector('lightning-button-stateful[data-id="toggle-stream"]'); - - return Promise.resolve() - .then(() => { - expect(toggleButton.variant).toBe('success'); - toggleButton.click(); + await Promise.all( + namespaces.map(async namespace => { + const element = await createStreamElement(namespace); + const toggleButton = element.shadowRoot.querySelector('lightning-button-stateful[data-id="toggle-stream"]'); + + return Promise.resolve() + .then(() => { + expect(toggleButton.variant).toBe('success'); + toggleButton.click(); + }) + .then(() => { + expect(toggleButton.variant).toBe('brand'); + }); }) - .then(() => { - expect(toggleButton.variant).toBe('brand'); - }); + ); }); it('clears stream when clear button clicked', async () => { - const element = createElement('log-entry-event-stream', { - is: LogEntryEventStream - }); - document.body.appendChild(element); - await Promise.resolve(); - const mockLogEntryEvent = { ...mockLogEntryEventTemplate }; - await jestMockPublish('/event/LogEntryEvent__e', { - data: { - payload: mockLogEntryEvent - } - }); - const expectedStreamText = getPlatformEventText(mockLogEntryEvent); - let eventStreamDiv = element.shadowRoot.querySelector('.event-stream'); - expect(eventStreamDiv.textContent).toBe(expectedStreamText); - - const clearButton = element.shadowRoot.querySelector('lightning-button[data-id="clear"]'); - clearButton.click(); - - return Promise.resolve().then(() => { - eventStreamDiv = element.shadowRoot.querySelector('.event-stream'); - expect(eventStreamDiv.textContent).toBeFalsy(); - }); + await Promise.all( + namespaces.map(async namespace => { + const element = await createStreamElement(namespace); + const mockLogEntryEvent = await generatePlatformEvent(namespace); + await publishPlatformEvent(namespace, mockLogEntryEvent); + const expectedStreamText = getPlatformEventText(mockLogEntryEvent, namespace); + + let eventStreamDiv = element.shadowRoot.querySelector('.event-stream'); + expect(eventStreamDiv.textContent).toBe(expectedStreamText); + + const clearButton = element.shadowRoot.querySelector('lightning-button[data-id="clear"]'); + clearButton.click(); + + return Promise.resolve().then(() => { + eventStreamDiv = element.shadowRoot.querySelector('.event-stream'); + expect(eventStreamDiv.textContent).toBeFalsy(); + }); + }) + ); }); it('includes matching log entry event for logging level filter', async () => { - const element = createElement('log-entry-event-stream', { - is: LogEntryEventStream - }); - document.body.appendChild(element); - await Promise.resolve(); - const loggingLevelFilterDropdown = element.shadowRoot.querySelector('lightning-combobox[data-id="loggingLevelFilter"]'); - loggingLevelFilterDropdown.value = loggingLevels.DEBUG; - loggingLevelFilterDropdown.dispatchEvent(new CustomEvent('change')); + await Promise.all( + namespaces.map(async namespace => { + const element = await createStreamElement(namespace); - const matchingLogEntryEvent = { ...mockLogEntryEventTemplate }; - matchingLogEntryEvent.LoggingLevel__c = 'INFO'; - matchingLogEntryEvent.LoggingLevelOrdinal__c = loggingLevels.INFO; - expect(matchingLogEntryEvent.LoggingLevelOrdinal__c).toBeGreaterThan(Number(loggingLevelFilterDropdown.value)); - await jestMockPublish('/event/LogEntryEvent__e', { - data: { - payload: matchingLogEntryEvent - } - }); + const loggingLevelFilterDropdown = element.shadowRoot.querySelector('lightning-combobox[data-id="loggingLevelFilter"]'); + loggingLevelFilterDropdown.value = loggingLevels.DEBUG; + loggingLevelFilterDropdown.dispatchEvent(new CustomEvent('change')); - const expectedStreamText = getPlatformEventText(matchingLogEntryEvent); - const eventStreamDiv = element.shadowRoot.querySelector('.event-stream'); - expect(eventStreamDiv.textContent).toBe(expectedStreamText); + const matchingLogEntryEvent = generatePlatformEvent(namespace); + const namespacePrefix = !!namespace ? namespace + '__' : ''; + matchingLogEntryEvent[namespacePrefix + 'LoggingLevel__c'] = 'INFO'; + matchingLogEntryEvent[namespacePrefix + 'LoggingLevelOrdinal__c'] = loggingLevels.INFO; + expect(matchingLogEntryEvent[namespacePrefix + 'LoggingLevelOrdinal__c']).toBeGreaterThan(Number(loggingLevelFilterDropdown.value)); + + await publishPlatformEvent(namespace, matchingLogEntryEvent); + + const expectedStreamText = getPlatformEventText(matchingLogEntryEvent, namespace); + const eventStreamDiv = element.shadowRoot.querySelector('.event-stream'); + expect(eventStreamDiv.textContent).toBe(expectedStreamText); + }) + ); }); it('excludes non-matching log entry event for logging level filter', async () => { - const element = createElement('log-entry-event-stream', { - is: LogEntryEventStream - }); - document.body.appendChild(element); - await Promise.resolve(); - const loggingLevelFilterDropdown = element.shadowRoot.querySelector('lightning-combobox[data-id="loggingLevelFilter"]'); - loggingLevelFilterDropdown.value = loggingLevels.DEBUG; - loggingLevelFilterDropdown.dispatchEvent(new CustomEvent('change')); + await Promise.all( + namespaces.map(async namespace => { + const element = await createStreamElement(namespace); - const nonMatchingLogEntryEvent = { ...mockLogEntryEventTemplate }; - nonMatchingLogEntryEvent.LoggingLevel__c = 'FINEST'; - nonMatchingLogEntryEvent.LoggingLevelOrdinal__c = loggingLevels.FINEST; - expect(nonMatchingLogEntryEvent.LoggingLevelOrdinal__c).toBeLessThan(Number(loggingLevelFilterDropdown.value)); - await jestMockPublish('/event/LogEntryEvent__e', { - data: { - payload: nonMatchingLogEntryEvent - } - }); + const loggingLevelFilterDropdown = element.shadowRoot.querySelector('lightning-combobox[data-id="loggingLevelFilter"]'); + loggingLevelFilterDropdown.value = loggingLevels.DEBUG; + loggingLevelFilterDropdown.dispatchEvent(new CustomEvent('change')); - const eventStreamDiv = element.shadowRoot.querySelector('.event-stream'); - expect(eventStreamDiv.textContent).toBeFalsy(); + const matchingLogEntryEvent = generatePlatformEvent(namespace); + const namespacePrefix = !!namespace ? namespace + '__' : ''; + matchingLogEntryEvent[namespacePrefix + 'LoggingLevel__c'] = 'FINEST'; + matchingLogEntryEvent[namespacePrefix + 'LoggingLevelOrdinal__c'] = loggingLevels.FINEST; + expect(matchingLogEntryEvent[namespacePrefix + 'LoggingLevelOrdinal__c']).toBeLessThan(Number(loggingLevelFilterDropdown.value)); + + await publishPlatformEvent(namespace, matchingLogEntryEvent); + + const eventStreamDiv = element.shadowRoot.querySelector('.event-stream'); + expect(eventStreamDiv.textContent).toBeFalsy(); + }) + ); }); it('includes matching log entry event for origin type filter', async () => { + getLogEntryEventSchema.mockResolvedValue(mockLogEntryEventSchemaTemplate); + const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); @@ -177,6 +235,8 @@ describe('LogEntryEventStream tests', () => { expect(eventStreamDiv.textContent).toBe(expectedStreamText); }); it('excludes non-matching log entry event for origin type filter', async () => { + getLogEntryEventSchema.mockResolvedValue(mockLogEntryEventSchemaTemplate); + const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); @@ -199,6 +259,8 @@ describe('LogEntryEventStream tests', () => { expect(eventStreamDiv.textContent).toBeFalsy(); }); it('includes matching log entry event for origin location filter', async () => { + getLogEntryEventSchema.mockResolvedValue(mockLogEntryEventSchemaTemplate); + const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); @@ -222,6 +284,8 @@ describe('LogEntryEventStream tests', () => { expect(eventStreamDiv.textContent).toBe(expectedStreamText); }); it('excludes non-matching log entry event for origin location filter', async () => { + getLogEntryEventSchema.mockResolvedValue(mockLogEntryEventSchemaTemplate); + const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); @@ -244,6 +308,8 @@ describe('LogEntryEventStream tests', () => { expect(eventStreamDiv.textContent).toBeFalsy(); }); it('includes matching log entry event for logged by filter', async () => { + getLogEntryEventSchema.mockResolvedValue(mockLogEntryEventSchemaTemplate); + const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); @@ -267,6 +333,8 @@ describe('LogEntryEventStream tests', () => { expect(eventStreamDiv.textContent).toBe(expectedStreamText); }); it('excludes non-matching log entry event for logged by filter', async () => { + getLogEntryEventSchema.mockResolvedValue(mockLogEntryEventSchemaTemplate); + const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); @@ -289,6 +357,8 @@ describe('LogEntryEventStream tests', () => { expect(eventStreamDiv.textContent).toBeFalsy(); }); it('includes matching log entry event using string for message filter', async () => { + getLogEntryEventSchema.mockResolvedValue(mockLogEntryEventSchemaTemplate); + const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); @@ -312,6 +382,8 @@ describe('LogEntryEventStream tests', () => { expect(eventStreamDiv.textContent).toBe(expectedStreamText); }); it('excludes non-matching log entry event for message filter', async () => { + getLogEntryEventSchema.mockResolvedValue(mockLogEntryEventSchemaTemplate); + const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); @@ -335,6 +407,8 @@ describe('LogEntryEventStream tests', () => { }); it('includes matching log entry event using regex for message filter', async () => { + getLogEntryEventSchema.mockResolvedValue(mockLogEntryEventSchemaTemplate); + const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); diff --git a/nebula-logger/core/main/log-management/lwc/logEntryEventStream/logEntryEventStream.js b/nebula-logger/core/main/log-management/lwc/logEntryEventStream/logEntryEventStream.js index 0a1d582e4..5ee6adb6e 100644 --- a/nebula-logger/core/main/log-management/lwc/logEntryEventStream/logEntryEventStream.js +++ b/nebula-logger/core/main/log-management/lwc/logEntryEventStream/logEntryEventStream.js @@ -4,7 +4,8 @@ ************************************************************************************************/ import { LightningElement } from 'lwc'; -import { isEmpEnabled, subscribe, unsubscribe } from 'lightning/empApi'; +import { subscribe, unsubscribe } from 'lightning/empApi'; +import getLogEntryEventSchema from '@salesforce/apex/LoggerSObjectMetadata.getLogEntryEventSchema'; export default class LogEntryEventStream extends LightningElement { unfilteredEvents = []; @@ -21,9 +22,25 @@ export default class LogEntryEventStream extends LightningElement { scenarioFilter; maxEvents = 50; - _channel = '/event/LogEntryEvent__e'; // TODO need to support namespace in managed package + _logEntryEventSchema; + _channel; _subscription = {}; + async connectedCallback() { + document.title = 'Log Entry Event Stream'; + + getLogEntryEventSchema().then(result => { + this._logEntryEventSchema = result; + this._channel = '/event/' + this._logEntryEventSchema.apiName; + + this.createSubscription(); + }); + } + + disconnectedCallback() { + this.cancelSubscription(); + } + get title() { let logEntryString = ' Log Entry Events'; let startingTitle = this.logEntryEvents.length + logEntryString; @@ -59,24 +76,29 @@ export default class LogEntryEventStream extends LightningElement { ]; } - async connectedCallback() { - document.title = 'Log Entry Event Stream'; - if (isEmpEnabled()) { - this.createSubscription(); - } - } - - disconnectedCallback() { - this.cancelSubscription(); - } - async createSubscription() { this._subscription = await subscribe(this._channel, -2, event => { - const logEntryEvent = event.data.payload; + const logEntryEvent = JSON.parse(JSON.stringify(event.data.payload)); + + let cleanedLogEntryEvent; + if (!this._logEntryEventSchema.namespacePrefix) { + cleanedLogEntryEvent = logEntryEvent; + } else { + // To handle the namespaced managed package, convert all of the field API names from the fully-qualified name (that includes the namespace) + // to instead use just the local field name + // Example: `Nebula__LoggingLevel__c` becomes `LoggingLevel__c` + // This makes it easier for the rest of the code in this lwc to just reference the field without worrying about if there is a namespace + cleanedLogEntryEvent = {}; + Object.keys(logEntryEvent).forEach(eventFieldApiName => { + const localFieldApiName = eventFieldApiName.replace(this._logEntryEventSchema.namespacePrefix, ''); + cleanedLogEntryEvent[localFieldApiName] = logEntryEvent[eventFieldApiName]; + }); + } + // As of API v52.0 (Summer '21), platform events have a unique field, EventUUID // but it doesn't seem to be populated via empApi, so use a synthetic key instead - logEntryEvent.key = logEntryEvent.TransactionId__c + '__' + logEntryEvent.TransactionEntryNumber__c; - this.unfilteredEvents.unshift(logEntryEvent); + cleanedLogEntryEvent.key = cleanedLogEntryEvent.TransactionId__c + '__' + cleanedLogEntryEvent.TransactionEntryNumber__c; + this.unfilteredEvents.unshift(cleanedLogEntryEvent); this._filterEvents(); }); } diff --git a/nebula-logger/core/main/log-management/lwc/logEntryEventStream/logEntryEventStream.js-meta.xml b/nebula-logger/core/main/log-management/lwc/logEntryEventStream/logEntryEventStream.js-meta.xml index cec768d25..e7dbaf178 100644 --- a/nebula-logger/core/main/log-management/lwc/logEntryEventStream/logEntryEventStream.js-meta.xml +++ b/nebula-logger/core/main/log-management/lwc/logEntryEventStream/logEntryEventStream.js-meta.xml @@ -1,6 +1,6 @@ - 53.0 + 54.0 false Log Entry Event Stream diff --git a/nebula-logger/core/main/log-management/lwc/logJSON/__tests__/data/getLog.json b/nebula-logger/core/main/log-management/lwc/logJSON/__tests__/data/getLog.json deleted file mode 100644 index f1ac7fc0f..000000000 --- a/nebula-logger/core/main/log-management/lwc/logJSON/__tests__/data/getLog.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "Name": "Log-1234567890" -} diff --git a/nebula-logger/core/main/log-management/lwc/logJSON/__tests__/logJSON.test.js b/nebula-logger/core/main/log-management/lwc/logJSON/__tests__/logJSON.test.js deleted file mode 100644 index d0063f41f..000000000 --- a/nebula-logger/core/main/log-management/lwc/logJSON/__tests__/logJSON.test.js +++ /dev/null @@ -1,71 +0,0 @@ -import { createElement } from 'lwc'; -import LogJSON from 'c/logJSON'; -import getLog from '@salesforce/apex/Logger.getLog'; -import { registerApexTestWireAdapter } from '@salesforce/sfdx-lwc-jest'; - -// Mock data -const mockGetLog = require('./data/getLog.json'); - -// Register a test wire adapter -const getLogAdapter = registerApexTestWireAdapter(getLog); - -document.execCommand = jest.fn(); - -jest.mock( - '@salesforce/apex/Logger.getLog', - () => { - return { - default: () => mockGetLog - }; - }, - { virtual: true } -); - -describe('Logger JSON Viewer lwc tests', () => { - afterEach(() => { - while (document.body.firstChild) { - document.body.removeChild(document.body.firstChild); - } - jest.clearAllMocks(); - }); - - it('sets document title', async () => { - const logViewerElement = createElement('c-log-json', { is: LogJSON }); - document.body.appendChild(logViewerElement); - getLogAdapter.emit(mockGetLog); - - await Promise.resolve(); - expect(logViewerElement.title).toEqual('JSON for ' + mockGetLog.Name); - }); - - it('defaults to brand button variant', async () => { - const logViewer = createElement('c-log-json', { is: LogJSON }); - document.body.appendChild(logViewer); - - getLogAdapter.emit(mockGetLog); - - await Promise.resolve(); - const inputButton = logViewer.shadowRoot.querySelector('lightning-button-stateful'); - expect(logViewer.title).toEqual('JSON for ' + mockGetLog.Name); - expect(inputButton.variant).toEqual('brand'); - }); - - it('copies the JSON to the clipboard', async () => { - const logViewer = createElement('c-log-json', { is: LogJSON }); - document.body.appendChild(logViewer); - - getLogAdapter.emit(mockGetLog); - - // Resolve a promise to wait for a rerender of the new content - return Promise.resolve() - .then(() => { - let copyJsonBtn = logViewer.shadowRoot.querySelector('lightning-button-stateful[data-id="copy-json-btn"]'); - copyJsonBtn.click(); - }) - .then(() => { - const clipboardContent = JSON.parse(logViewer.shadowRoot.querySelector('pre').textContent); - expect(clipboardContent).toEqual(mockGetLog); - expect(document.execCommand).toHaveBeenCalledWith('copy'); - }); - }); -}); diff --git a/nebula-logger/core/main/log-management/lwc/logJSON/logJSON.css b/nebula-logger/core/main/log-management/lwc/logJSON/logJSON.css deleted file mode 100644 index ed52f8b6a..000000000 --- a/nebula-logger/core/main/log-management/lwc/logJSON/logJSON.css +++ /dev/null @@ -1,11 +0,0 @@ -pre { - max-height: 500px; - margin: auto 10px; - overflow-y: scroll; -} - -.slds-modal__content { - overflow-y: hidden !important; - height: unset !important; - max-height: unset !important; -} diff --git a/nebula-logger/core/main/log-management/lwc/logJSON/logJSON.html b/nebula-logger/core/main/log-management/lwc/logJSON/logJSON.html deleted file mode 100644 index 063f43544..000000000 --- a/nebula-logger/core/main/log-management/lwc/logJSON/logJSON.html +++ /dev/null @@ -1,24 +0,0 @@ - - - diff --git a/nebula-logger/core/main/log-management/lwc/logJSON/logJSON.js b/nebula-logger/core/main/log-management/lwc/logJSON/logJSON.js deleted file mode 100644 index e55298e16..000000000 --- a/nebula-logger/core/main/log-management/lwc/logJSON/logJSON.js +++ /dev/null @@ -1,68 +0,0 @@ -/************************************************************************************************* - * 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 { api, LightningElement, wire } from 'lwc'; -import getLog from '@salesforce/apex/Logger.getLog'; - -export default class LogJSON extends LightningElement { - @api - recordId; - - log; - logJSON; - jsonCopied = false; - - @wire(getLog, { logId: '$recordId' }) - wiredGetLog(result) { - this.log = result; - - let formattedLog; - // Sort the keys (fields) in the log object - if (result.data) { - formattedLog = Object.keys(result.data) - .sort() - .reduce((obj, key) => { - obj[key] = result.data[key]; - return obj; - }, {}); - } - this.logJSON = JSON.stringify(formattedLog, null, '\t'); - } - - @api - get title() { - return this.log.data ? 'JSON for ' + this.log.data.Name : ''; - } - - get variant() { - return this.jsonCopied ? 'success' : 'brand'; - } - - async copyToClipboard() { - const value = this.template.querySelector('pre').textContent; - - const textArea = document.createElement('textarea'); - textArea.value = value; - // Avoid scrolling to bottom - textArea.style.top = '0'; - textArea.style.left = '0'; - textArea.style.position = 'fixed'; - - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - document.execCommand('copy'); - document.body.removeChild(textArea); - - /* eslint-disable-next-line no-console */ - console.log('Log data successfully copied to clipboard', JSON.parse(value)); - this.jsonCopied = true; - - /* eslint-disable-next-line @lwc/lwc/no-async-operation */ - setTimeout(() => { - this.jsonCopied = false; - }, 5000); - } -} diff --git a/nebula-logger/core/main/log-management/lwc/logViewer/__tests__/data/getLog.json b/nebula-logger/core/main/log-management/lwc/logViewer/__tests__/data/getLog.json new file mode 100644 index 000000000..051b2cfec --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/logViewer/__tests__/data/getLog.json @@ -0,0 +1,849 @@ +{ + "ApiReleaseNumber__c": "236.12", + "ApiReleaseVersion__c": "Spring '22 Patch 12", + "ApiVersion__c": "v54.0", + "CreatedById": "0051h000008NoMwAAK", + "CreatedDate": "2022-03-09T05:22:53.000Z", + "EndTime__c": "2022-03-09T05:22:50.000Z", + "Id": "a021h000004AXYMAA4", + "IsClosed__c": false, + "IsDeleted": false, + "IsResolved__c": false, + "LastModifiedById": "0051h000008NoMwAAK", + "LastModifiedDate": "2022-03-09T05:22:57.000Z", + "LastReferencedDate": "2022-03-09T05:47:29.000Z", + "LastViewedDate": "2022-03-09T05:47:29.000Z", + "Locale__c": "en_US", + "LogEntriesSummary__c": "9 total entries, 1 error(s), 1 warning(s)", + "LogEntries__r": [ + { + "LastModifiedDate": "2022-03-09T05:22:53.000Z", + "HasException__c": false, + "LimitsMobilePushApexCalls__c": "0 / 10", + "LoggingLevelWithImage__c": "\" FINEST", + "HasDatabaseResult__c": false, + "LimitsAggregateQueries__c": "0 / 300", + "MessageMasked__c": false, + "LimitsSoqlQueryRowsMax__c": 50000, + "HasRecordJson__c": false, + "LimitsCalloutsMax__c": 100, + "LimitsEmailInvocationsUsed__c": 0, + "IsDeleted": false, + "LimitsCalloutsUsed__c": 0, + "Log__c": "a021h000004AXYMAA4", + "LimitsDmlRowsMax__c": 10000, + "LimitsCpuTimeUsed__c": 132, + "LimitsQueueableJobsMax__c": 50, + "LimitsPublishImmediateDmlStatementsUsed__c": 0, + "LimitsSoslSearchesUsed__c": 0, + "LimitsDmlStatementsMax__c": 150, + "Message__c": "Logger - Saving 8 log entries via Anonymous Apex, save method is EVENT_BUS", + "Id": "a011h000005P8h7AAC", + "LoggingLevelOrdinal__c": 2, + "MessageTruncated__c": false, + "LimitsQueueableJobsUsed__c": 0, + "LimitsPublishImmediateDmlStatements__c": "0 / 150", + "TransactionEntryNumber__c": 9, + "LimitsSoqlQueryRows__c": "4 / 50000", + "HasRecordId__c": false, + "LimitsSoqlQueries__c": "4 / 100", + "LimitsSoqlQueryRowsUsed__c": 4, + "Timestamp__c": "2022-03-09T05:22:50.000Z", + "LimitsDmlRowsUsed__c": 0, + "LimitsMobilePushApexCallsUsed__c": 0, + "RecordJsonMasked__c": false, + "LimitsAggregateQueriesMax__c": 300, + "SystemModstamp": "2022-03-09T05:22:53.000Z", + "LimitsCallouts__c": "0 / 100", + "LimitsSoqlQueriesMax__c": 100, + "LimitsDmlStatements__c": "0 / 150", + "LimitsSoslSearches__c": "0 / 20", + "LogTransactionId__c": "4heTw6JlPFkvtu-qbxkog-", + "LimitsPublishImmediateDmlStatementsMax__c": 150, + "LimitsFutureCallsUsed__c": 0, + "LimitsCpuTime__c": "132 / 10000", + "OriginLocation__c": "AnonymousBlock", + "LimitsDmlStatementsUsed__c": 0, + "Name": "a011h000005P8h7", + "StackTrace__c": "AnonymousBlock: line 21, column 1\nAnonymousBlock: line 21, column 1", + "HasStackTrace__c": true, + "LimitsSoslSearchesMax__c": 20, + "CreatedById": "0051h000008NoMwAAK", + "HasExceptionStackTrace__c": false, + "LimitsDmlRows__c": "0 / 10000", + "LimitsFutureCallsMax__c": 50, + "LimitsHeapSizeMax__c": 6000000, + "LimitsHeapSize__c": "41745 / 6000000", + "CreatedDate": "2022-03-09T05:22:53.000Z", + "LimitsSoqlQueryLocatorRowsUsed__c": 0, + "LimitsAsyncCalls__c": "0 / 200", + "LimitsCpuTimeMax__c": 10000, + "EventUuid__c": "9dadd636-3d40-492a-913a-3add99824912", + "LoggedByUsernameLink__c": "test-sgpopgvvyplr@example.com", + "LimitsQueueableJobs__c": "0 / 50", + "LimitsSoqlQueryLocatorRows__c": "0 / 10000", + "LimitsHeapSizeUsed__c": 41745, + "TriggerIsExecuting__c": false, + "LimitsMobilePushApexCallsMax__c": 10, + "Origin__c": "Apex.AnonymousBlock", + "LimitsAsyncCallsMax__c": 200, + "LimitsAsyncCallsUsed__c": 0, + "LoggingLevel__c": "FINEST", + "LimitsAggregateQueriesUsed__c": 0, + "LimitsEmailInvocations__c": "0 / 10", + "OriginType__c": "Apex", + "LimitsFutureCalls__c": "0 / 50", + "EpochTimestamp__c": 1646803370189, + "LimitsSoqlQueryLocatorRowsMax__c": 10000, + "LimitsEmailInvocationsMax__c": 10, + "LastModifiedById": "0051h000008NoMwAAK", + "LimitsSoqlQueriesUsed__c": 4 + }, + { + "LastModifiedDate": "2022-03-09T05:22:53.000Z", + "HasException__c": false, + "LimitsMobilePushApexCalls__c": "0 / 10", + "LoggingLevelWithImage__c": "\" FINEST", + "HasDatabaseResult__c": false, + "LimitsAggregateQueries__c": "0 / 300", + "MessageMasked__c": false, + "LimitsSoqlQueryRowsMax__c": 50000, + "HasRecordJson__c": false, + "LimitsCalloutsMax__c": 100, + "LimitsEmailInvocationsUsed__c": 0, + "IsDeleted": false, + "LimitsCalloutsUsed__c": 0, + "Log__c": "a021h000004AXYMAA4", + "LimitsDmlRowsMax__c": 10000, + "LimitsCpuTimeUsed__c": 122, + "LimitsQueueableJobsMax__c": 50, + "LimitsPublishImmediateDmlStatementsUsed__c": 0, + "LimitsSoslSearchesUsed__c": 0, + "LimitsDmlStatementsMax__c": 150, + "Message__c": "Example FINEST entry", + "Id": "a011h000005P8h6AAC", + "LoggingLevelOrdinal__c": 2, + "MessageTruncated__c": false, + "LimitsQueueableJobsUsed__c": 0, + "LimitsPublishImmediateDmlStatements__c": "0 / 150", + "TransactionEntryNumber__c": 8, + "LimitsSoqlQueryRows__c": "4 / 50000", + "HasRecordId__c": false, + "LimitsSoqlQueries__c": "4 / 100", + "LimitsSoqlQueryRowsUsed__c": 4, + "Timestamp__c": "2022-03-09T05:22:50.000Z", + "LimitsDmlRowsUsed__c": 0, + "LimitsMobilePushApexCallsUsed__c": 0, + "RecordJsonMasked__c": false, + "LimitsAggregateQueriesMax__c": 300, + "SystemModstamp": "2022-03-09T05:22:53.000Z", + "LimitsCallouts__c": "0 / 100", + "LimitsSoqlQueriesMax__c": 100, + "LimitsDmlStatements__c": "0 / 150", + "LimitsSoslSearches__c": "0 / 20", + "LogTransactionId__c": "4heTw6JlPFkvtu-qbxkog-", + "LimitsPublishImmediateDmlStatementsMax__c": 150, + "LimitsFutureCallsUsed__c": 0, + "LimitsCpuTime__c": "122 / 10000", + "OriginLocation__c": "AnonymousBlock", + "LimitsDmlStatementsUsed__c": 0, + "Name": "a011h000005P8h6", + "StackTrace__c": "AnonymousBlock: line 19, column 1\nAnonymousBlock: line 19, column 1", + "HasStackTrace__c": true, + "LimitsSoslSearchesMax__c": 20, + "CreatedById": "0051h000008NoMwAAK", + "HasExceptionStackTrace__c": false, + "LimitsDmlRows__c": "0 / 10000", + "LimitsFutureCallsMax__c": 50, + "LimitsHeapSizeMax__c": 6000000, + "LimitsHeapSize__c": "37516 / 6000000", + "CreatedDate": "2022-03-09T05:22:53.000Z", + "LimitsSoqlQueryLocatorRowsUsed__c": 0, + "LimitsAsyncCalls__c": "0 / 200", + "LimitsCpuTimeMax__c": 10000, + "EventUuid__c": "58747fda-b991-4d44-86f1-892eb288f94e", + "LoggedByUsernameLink__c": "test-sgpopgvvyplr@example.com", + "LimitsQueueableJobs__c": "0 / 50", + "LimitsSoqlQueryLocatorRows__c": "0 / 10000", + "LimitsHeapSizeUsed__c": 37516, + "TriggerIsExecuting__c": false, + "LimitsMobilePushApexCallsMax__c": 10, + "Origin__c": "Apex.AnonymousBlock", + "LimitsAsyncCallsMax__c": 200, + "LimitsAsyncCallsUsed__c": 0, + "LoggingLevel__c": "FINEST", + "LimitsAggregateQueriesUsed__c": 0, + "LimitsEmailInvocations__c": "0 / 10", + "OriginType__c": "Apex", + "LimitsFutureCalls__c": "0 / 50", + "EpochTimestamp__c": 1646803370178, + "LimitsSoqlQueryLocatorRowsMax__c": 10000, + "LimitsEmailInvocationsMax__c": 10, + "LastModifiedById": "0051h000008NoMwAAK", + "LimitsSoqlQueriesUsed__c": 4 + }, + { + "LastModifiedDate": "2022-03-09T05:22:53.000Z", + "HasException__c": false, + "LimitsMobilePushApexCalls__c": "0 / 10", + "LoggingLevelWithImage__c": "\" FINER", + "HasDatabaseResult__c": false, + "LimitsAggregateQueries__c": "0 / 300", + "MessageMasked__c": false, + "LimitsSoqlQueryRowsMax__c": 50000, + "HasRecordJson__c": false, + "LimitsCalloutsMax__c": 100, + "LimitsEmailInvocationsUsed__c": 0, + "IsDeleted": false, + "LimitsCalloutsUsed__c": 0, + "Log__c": "a021h000004AXYMAA4", + "LimitsDmlRowsMax__c": 10000, + "LimitsCpuTimeUsed__c": 113, + "LimitsQueueableJobsMax__c": 50, + "LimitsPublishImmediateDmlStatementsUsed__c": 0, + "LimitsSoslSearchesUsed__c": 0, + "LimitsDmlStatementsMax__c": 150, + "Message__c": "Example FINER entry", + "Id": "a011h000005P8h5AAC", + "LoggingLevelOrdinal__c": 3, + "MessageTruncated__c": false, + "LimitsQueueableJobsUsed__c": 0, + "LimitsPublishImmediateDmlStatements__c": "0 / 150", + "TransactionEntryNumber__c": 7, + "LimitsSoqlQueryRows__c": "4 / 50000", + "HasRecordId__c": false, + "LimitsSoqlQueries__c": "4 / 100", + "LimitsSoqlQueryRowsUsed__c": 4, + "Timestamp__c": "2022-03-09T05:22:50.000Z", + "LimitsDmlRowsUsed__c": 0, + "LimitsMobilePushApexCallsUsed__c": 0, + "RecordJsonMasked__c": false, + "LimitsAggregateQueriesMax__c": 300, + "SystemModstamp": "2022-03-09T05:22:53.000Z", + "LimitsCallouts__c": "0 / 100", + "LimitsSoqlQueriesMax__c": 100, + "LimitsDmlStatements__c": "0 / 150", + "LimitsSoslSearches__c": "0 / 20", + "LogTransactionId__c": "4heTw6JlPFkvtu-qbxkog-", + "LimitsPublishImmediateDmlStatementsMax__c": 150, + "LimitsFutureCallsUsed__c": 0, + "LimitsCpuTime__c": "113 / 10000", + "OriginLocation__c": "AnonymousBlock", + "LimitsDmlStatementsUsed__c": 0, + "Name": "a011h000005P8h5", + "StackTrace__c": "AnonymousBlock: line 18, column 1\nAnonymousBlock: line 18, column 1", + "HasStackTrace__c": true, + "LimitsSoslSearchesMax__c": 20, + "CreatedById": "0051h000008NoMwAAK", + "HasExceptionStackTrace__c": false, + "LimitsDmlRows__c": "0 / 10000", + "LimitsFutureCallsMax__c": 50, + "LimitsHeapSizeMax__c": 6000000, + "LimitsHeapSize__c": "33429 / 6000000", + "CreatedDate": "2022-03-09T05:22:53.000Z", + "LimitsSoqlQueryLocatorRowsUsed__c": 0, + "LimitsAsyncCalls__c": "0 / 200", + "LimitsCpuTimeMax__c": 10000, + "EventUuid__c": "f5c18382-72ba-4808-8319-56cdfe27916f", + "LoggedByUsernameLink__c": "test-sgpopgvvyplr@example.com", + "LimitsQueueableJobs__c": "0 / 50", + "LimitsSoqlQueryLocatorRows__c": "0 / 10000", + "LimitsHeapSizeUsed__c": 33429, + "TriggerIsExecuting__c": false, + "LimitsMobilePushApexCallsMax__c": 10, + "Origin__c": "Apex.AnonymousBlock", + "LimitsAsyncCallsMax__c": 200, + "LimitsAsyncCallsUsed__c": 0, + "LoggingLevel__c": "FINER", + "LimitsAggregateQueriesUsed__c": 0, + "LimitsEmailInvocations__c": "0 / 10", + "OriginType__c": "Apex", + "LimitsFutureCalls__c": "0 / 50", + "EpochTimestamp__c": 1646803370169, + "LimitsSoqlQueryLocatorRowsMax__c": 10000, + "LimitsEmailInvocationsMax__c": 10, + "LastModifiedById": "0051h000008NoMwAAK", + "LimitsSoqlQueriesUsed__c": 4 + }, + { + "LastModifiedDate": "2022-03-09T05:22:53.000Z", + "HasException__c": false, + "LimitsMobilePushApexCalls__c": "0 / 10", + "LoggingLevelWithImage__c": "\" FINE", + "HasDatabaseResult__c": false, + "LimitsAggregateQueries__c": "0 / 300", + "MessageMasked__c": false, + "LimitsSoqlQueryRowsMax__c": 50000, + "HasRecordJson__c": false, + "LimitsCalloutsMax__c": 100, + "LimitsEmailInvocationsUsed__c": 0, + "IsDeleted": false, + "LimitsCalloutsUsed__c": 0, + "Log__c": "a021h000004AXYMAA4", + "LimitsDmlRowsMax__c": 10000, + "LimitsCpuTimeUsed__c": 104, + "LimitsQueueableJobsMax__c": 50, + "LimitsPublishImmediateDmlStatementsUsed__c": 0, + "LimitsSoslSearchesUsed__c": 0, + "LimitsDmlStatementsMax__c": 150, + "Message__c": "Example FINE entry", + "Id": "a011h000005P8h4AAC", + "LoggingLevelOrdinal__c": 4, + "MessageTruncated__c": false, + "LimitsQueueableJobsUsed__c": 0, + "LimitsPublishImmediateDmlStatements__c": "0 / 150", + "TransactionEntryNumber__c": 6, + "LimitsSoqlQueryRows__c": "4 / 50000", + "HasRecordId__c": false, + "LimitsSoqlQueries__c": "4 / 100", + "LimitsSoqlQueryRowsUsed__c": 4, + "Timestamp__c": "2022-03-09T05:22:50.000Z", + "LimitsDmlRowsUsed__c": 0, + "LimitsMobilePushApexCallsUsed__c": 0, + "RecordJsonMasked__c": false, + "LimitsAggregateQueriesMax__c": 300, + "SystemModstamp": "2022-03-09T05:22:53.000Z", + "LimitsCallouts__c": "0 / 100", + "LimitsSoqlQueriesMax__c": 100, + "LimitsDmlStatements__c": "0 / 150", + "LimitsSoslSearches__c": "0 / 20", + "LogTransactionId__c": "4heTw6JlPFkvtu-qbxkog-", + "LimitsPublishImmediateDmlStatementsMax__c": 150, + "LimitsFutureCallsUsed__c": 0, + "LimitsCpuTime__c": "104 / 10000", + "OriginLocation__c": "AnonymousBlock", + "LimitsDmlStatementsUsed__c": 0, + "Name": "a011h000005P8h4", + "StackTrace__c": "AnonymousBlock: line 17, column 1\nAnonymousBlock: line 17, column 1", + "HasStackTrace__c": true, + "LimitsSoslSearchesMax__c": 20, + "CreatedById": "0051h000008NoMwAAK", + "HasExceptionStackTrace__c": false, + "LimitsDmlRows__c": "0 / 10000", + "LimitsFutureCallsMax__c": 50, + "LimitsHeapSizeMax__c": 6000000, + "LimitsHeapSize__c": "29340 / 6000000", + "CreatedDate": "2022-03-09T05:22:53.000Z", + "LimitsSoqlQueryLocatorRowsUsed__c": 0, + "LimitsAsyncCalls__c": "0 / 200", + "LimitsCpuTimeMax__c": 10000, + "EventUuid__c": "ea49c0ac-234a-4793-bff5-cfcf0bb0685b", + "LoggedByUsernameLink__c": "test-sgpopgvvyplr@example.com", + "LimitsQueueableJobs__c": "0 / 50", + "LimitsSoqlQueryLocatorRows__c": "0 / 10000", + "LimitsHeapSizeUsed__c": 29340, + "TriggerIsExecuting__c": false, + "LimitsMobilePushApexCallsMax__c": 10, + "Origin__c": "Apex.AnonymousBlock", + "LimitsAsyncCallsMax__c": 200, + "LimitsAsyncCallsUsed__c": 0, + "LoggingLevel__c": "FINE", + "LimitsAggregateQueriesUsed__c": 0, + "LimitsEmailInvocations__c": "0 / 10", + "OriginType__c": "Apex", + "LimitsFutureCalls__c": "0 / 50", + "EpochTimestamp__c": 1646803370159, + "LimitsSoqlQueryLocatorRowsMax__c": 10000, + "LimitsEmailInvocationsMax__c": 10, + "LastModifiedById": "0051h000008NoMwAAK", + "LimitsSoqlQueriesUsed__c": 4 + }, + { + "LastModifiedDate": "2022-03-09T05:22:53.000Z", + "HasException__c": false, + "LimitsMobilePushApexCalls__c": "0 / 10", + "RecordSObjectClassification__c": "Standard Object", + "LoggingLevelWithImage__c": "\" DEBUG", + "HasDatabaseResult__c": false, + "LimitsAggregateQueries__c": "0 / 300", + "MessageMasked__c": false, + "RecordLink__c": "test-sgpopgvvyplr@example.com", + "LimitsSoqlQueryRowsMax__c": 50000, + "HasRecordJson__c": false, + "LimitsCalloutsMax__c": 100, + "LimitsEmailInvocationsUsed__c": 0, + "IsDeleted": false, + "LimitsCalloutsUsed__c": 0, + "Log__c": "a021h000004AXYMAA4", + "RecordId__c": "0051h000008NoMxAAK", + "LimitsDmlRowsMax__c": 10000, + "LimitsCpuTimeUsed__c": 94, + "LimitsQueueableJobsMax__c": 50, + "LimitsPublishImmediateDmlStatementsUsed__c": 0, + "LimitsSoslSearchesUsed__c": 0, + "LimitsDmlStatementsMax__c": 150, + "Message__c": "Example DEBUG entry for just a record ID", + "Id": "a011h000005P8h3AAC", + "LoggingLevelOrdinal__c": 5, + "MessageTruncated__c": false, + "RecordSObjectType__c": "User", + "LimitsQueueableJobsUsed__c": 0, + "LimitsPublishImmediateDmlStatements__c": "0 / 150", + "RecordDetailedLink__c": "User: test-sgpopgvvyplr@example.com", + "TransactionEntryNumber__c": 5, + "LimitsSoqlQueryRows__c": "4 / 50000", + "HasRecordId__c": true, + "LimitsSoqlQueries__c": "4 / 100", + "LimitsSoqlQueryRowsUsed__c": 4, + "Timestamp__c": "2022-03-09T05:22:50.000Z", + "LimitsDmlRowsUsed__c": 0, + "LimitsMobilePushApexCallsUsed__c": 0, + "RecordJsonMasked__c": false, + "LimitsAggregateQueriesMax__c": 300, + "SystemModstamp": "2022-03-09T05:22:53.000Z", + "LimitsCallouts__c": "0 / 100", + "LimitsSoqlQueriesMax__c": 100, + "LimitsDmlStatements__c": "0 / 150", + "LimitsSoslSearches__c": "0 / 20", + "LogTransactionId__c": "4heTw6JlPFkvtu-qbxkog-", + "LimitsPublishImmediateDmlStatementsMax__c": 150, + "RecordCollectionType__c": "Single", + "LimitsFutureCallsUsed__c": 0, + "LimitsCpuTime__c": "94 / 10000", + "OriginLocation__c": "AnonymousBlock", + "LimitsDmlStatementsUsed__c": 0, + "Name": "a011h000005P8h3", + "StackTrace__c": "AnonymousBlock: line 16, column 1\nAnonymousBlock: line 16, column 1", + "HasStackTrace__c": true, + "LimitsSoslSearchesMax__c": 20, + "CreatedById": "0051h000008NoMwAAK", + "HasExceptionStackTrace__c": false, + "LimitsDmlRows__c": "0 / 10000", + "LimitsFutureCallsMax__c": 50, + "LimitsHeapSizeMax__c": 6000000, + "LimitsHeapSize__c": "25114 / 6000000", + "CreatedDate": "2022-03-09T05:22:53.000Z", + "LimitsSoqlQueryLocatorRowsUsed__c": 0, + "LimitsAsyncCalls__c": "0 / 200", + "LimitsCpuTimeMax__c": 10000, + "RecordName__c": "test-sgpopgvvyplr@example.com", + "EventUuid__c": "4b1cbe02-9778-4472-a5c1-8de2a25cc9b7", + "LoggedByUsernameLink__c": "test-sgpopgvvyplr@example.com", + "LimitsQueueableJobs__c": "0 / 50", + "LimitsSoqlQueryLocatorRows__c": "0 / 10000", + "LimitsHeapSizeUsed__c": 25114, + "TriggerIsExecuting__c": false, + "LimitsMobilePushApexCallsMax__c": 10, + "Origin__c": "Apex.AnonymousBlock", + "LimitsAsyncCallsMax__c": 200, + "LimitsAsyncCallsUsed__c": 0, + "LoggingLevel__c": "DEBUG", + "LimitsAggregateQueriesUsed__c": 0, + "LimitsEmailInvocations__c": "0 / 10", + "OriginType__c": "Apex", + "LimitsFutureCalls__c": "0 / 50", + "EpochTimestamp__c": 1646803370149, + "LimitsSoqlQueryLocatorRowsMax__c": 10000, + "LimitsEmailInvocationsMax__c": 10, + "LastModifiedById": "0051h000008NoMwAAK", + "LimitsSoqlQueriesUsed__c": 4 + }, + { + "LastModifiedDate": "2022-03-09T05:22:53.000Z", + "HasException__c": false, + "LimitsMobilePushApexCalls__c": "0 / 10", + "RecordJson__c": "{\n \"attributes\" : {\n \"type\" : \"User\",\n \"url\" : \"/services/data/v54.0/sobjects/User/0051h000008NoMxAAK\"\n },\n \"Id\" : \"0051h000008NoMxAAK\",\n \"Name\" : \"User User\",\n \"Username\" : \"test-sgpopgvvyplr@example.com\",\n \"ProfileId\" : \"00e1h000000xTOgAAM\",\n \"Profile\" : {\n \"attributes\" : {\n \"type\" : \"Profile\",\n \"url\" : \"/services/data/v54.0/sobjects/Profile/00e1h000000xTOgAAM\"\n },\n \"Id\" : \"00e1h000000xTOgAAM\",\n \"Name\" : \"System Administrator\"\n },\n \"AboutMe\" : \"I hope you dont leak my social, which is XXX-XX-9999, btw.\"\n}", + "RecordSObjectClassification__c": "Standard Object", + "LoggingLevelWithImage__c": "\" DEBUG", + "HasDatabaseResult__c": false, + "LimitsAggregateQueries__c": "0 / 300", + "MessageMasked__c": false, + "RecordLink__c": "test-sgpopgvvyplr@example.com", + "LimitsSoqlQueryRowsMax__c": 50000, + "HasRecordJson__c": true, + "LimitsCalloutsMax__c": 100, + "LimitsEmailInvocationsUsed__c": 0, + "IsDeleted": false, + "LimitsCalloutsUsed__c": 0, + "Log__c": "a021h000004AXYMAA4", + "RecordId__c": "0051h000008NoMxAAK", + "LimitsDmlRowsMax__c": 10000, + "LimitsCpuTimeUsed__c": 85, + "LimitsQueueableJobsMax__c": 50, + "LimitsPublishImmediateDmlStatementsUsed__c": 0, + "LimitsSoslSearchesUsed__c": 0, + "LimitsDmlStatementsMax__c": 150, + "Message__c": "Example DEBUG entry", + "Id": "a011h000005P8h2AAC", + "LoggingLevelOrdinal__c": 5, + "MessageTruncated__c": false, + "RecordSObjectType__c": "User", + "LimitsQueueableJobsUsed__c": 0, + "LimitsPublishImmediateDmlStatements__c": "0 / 150", + "RecordDetailedLink__c": "User: test-sgpopgvvyplr@example.com", + "TransactionEntryNumber__c": 4, + "LimitsSoqlQueryRows__c": "4 / 50000", + "HasRecordId__c": true, + "LimitsSoqlQueries__c": "4 / 100", + "LimitsSoqlQueryRowsUsed__c": 4, + "Timestamp__c": "2022-03-09T05:22:50.000Z", + "LimitsDmlRowsUsed__c": 0, + "LimitsMobilePushApexCallsUsed__c": 0, + "RecordJsonMasked__c": true, + "LimitsAggregateQueriesMax__c": 300, + "SystemModstamp": "2022-03-09T05:22:53.000Z", + "LimitsCallouts__c": "0 / 100", + "LimitsSoqlQueriesMax__c": 100, + "LimitsDmlStatements__c": "0 / 150", + "LimitsSoslSearches__c": "0 / 20", + "LogTransactionId__c": "4heTw6JlPFkvtu-qbxkog-", + "LimitsPublishImmediateDmlStatementsMax__c": 150, + "RecordCollectionType__c": "Single", + "LimitsFutureCallsUsed__c": 0, + "LimitsCpuTime__c": "85 / 10000", + "OriginLocation__c": "AnonymousBlock", + "LimitsDmlStatementsUsed__c": 0, + "Name": "a011h000005P8h2", + "StackTrace__c": "AnonymousBlock: line 15, column 1\nAnonymousBlock: line 15, column 1", + "HasStackTrace__c": true, + "LimitsSoslSearchesMax__c": 20, + "CreatedById": "0051h000008NoMwAAK", + "HasExceptionStackTrace__c": false, + "LimitsDmlRows__c": "0 / 10000", + "LimitsFutureCallsMax__c": 50, + "LimitsHeapSizeMax__c": 6000000, + "LimitsHeapSize__c": "20253 / 6000000", + "CreatedDate": "2022-03-09T05:22:53.000Z", + "LimitsSoqlQueryLocatorRowsUsed__c": 0, + "LimitsAsyncCalls__c": "0 / 200", + "LimitsCpuTimeMax__c": 10000, + "RecordName__c": "test-sgpopgvvyplr@example.com", + "EventUuid__c": "5c2ffa21-1c7b-421b-813e-cbd7971cb446", + "LoggedByUsernameLink__c": "test-sgpopgvvyplr@example.com", + "LimitsQueueableJobs__c": "0 / 50", + "LimitsSoqlQueryLocatorRows__c": "0 / 10000", + "LimitsHeapSizeUsed__c": 20253, + "TriggerIsExecuting__c": false, + "LimitsMobilePushApexCallsMax__c": 10, + "Origin__c": "Apex.AnonymousBlock", + "LimitsAsyncCallsMax__c": 200, + "LimitsAsyncCallsUsed__c": 0, + "LoggingLevel__c": "DEBUG", + "LimitsAggregateQueriesUsed__c": 0, + "LimitsEmailInvocations__c": "0 / 10", + "OriginType__c": "Apex", + "LimitsFutureCalls__c": "0 / 50", + "EpochTimestamp__c": 1646803370138, + "LimitsSoqlQueryLocatorRowsMax__c": 10000, + "LimitsEmailInvocationsMax__c": 10, + "LastModifiedById": "0051h000008NoMwAAK", + "LimitsSoqlQueriesUsed__c": 4 + }, + { + "LastModifiedDate": "2022-03-09T05:22:53.000Z", + "HasException__c": false, + "LimitsMobilePushApexCalls__c": "0 / 10", + "RecordJson__c": "{\n \"attributes\" : {\n \"type\" : \"User\",\n \"url\" : \"/services/data/v54.0/sobjects/User/0051h000008NoMxAAK\"\n },\n \"Id\" : \"0051h000008NoMxAAK\",\n \"Name\" : \"User User\",\n \"Username\" : \"test-sgpopgvvyplr@example.com\",\n \"ProfileId\" : \"00e1h000000xTOgAAM\",\n \"Profile\" : {\n \"attributes\" : {\n \"type\" : \"Profile\",\n \"url\" : \"/services/data/v54.0/sobjects/Profile/00e1h000000xTOgAAM\"\n },\n \"Id\" : \"00e1h000000xTOgAAM\",\n \"Name\" : \"System Administrator\"\n },\n \"AboutMe\" : \"I hope you dont leak my social, which is XXX-XX-9999, btw.\"\n}", + "RecordSObjectClassification__c": "Standard Object", + "LoggingLevelWithImage__c": "\" INFO", + "HasDatabaseResult__c": false, + "LimitsAggregateQueries__c": "0 / 300", + "MessageMasked__c": true, + "RecordLink__c": "test-sgpopgvvyplr@example.com", + "LimitsSoqlQueryRowsMax__c": 50000, + "HasRecordJson__c": true, + "LimitsCalloutsMax__c": 100, + "LimitsEmailInvocationsUsed__c": 0, + "IsDeleted": false, + "LimitsCalloutsUsed__c": 0, + "Log__c": "a021h000004AXYMAA4", + "RecordId__c": "0051h000008NoMxAAK", + "LimitsDmlRowsMax__c": 10000, + "LimitsCpuTimeUsed__c": 74, + "LimitsQueueableJobsMax__c": 50, + "LimitsPublishImmediateDmlStatementsUsed__c": 0, + "LimitsSoslSearchesUsed__c": 0, + "LimitsDmlStatementsMax__c": 150, + "Message__c": "In case you want to steal my identity, my fake social is XXX-XX-9999, thanks", + "Id": "a011h000005P8h1AAC", + "LoggingLevelOrdinal__c": 6, + "MessageTruncated__c": false, + "RecordSObjectType__c": "User", + "LimitsQueueableJobsUsed__c": 0, + "LimitsPublishImmediateDmlStatements__c": "0 / 150", + "RecordDetailedLink__c": "User: test-sgpopgvvyplr@example.com", + "TransactionEntryNumber__c": 3, + "LimitsSoqlQueryRows__c": "4 / 50000", + "HasRecordId__c": true, + "LimitsSoqlQueries__c": "4 / 100", + "LimitsSoqlQueryRowsUsed__c": 4, + "Timestamp__c": "2022-03-09T05:22:50.000Z", + "LimitsDmlRowsUsed__c": 0, + "LimitsMobilePushApexCallsUsed__c": 0, + "RecordJsonMasked__c": true, + "LimitsAggregateQueriesMax__c": 300, + "SystemModstamp": "2022-03-09T05:22:53.000Z", + "LimitsCallouts__c": "0 / 100", + "LimitsSoqlQueriesMax__c": 100, + "LimitsDmlStatements__c": "0 / 150", + "LimitsSoslSearches__c": "0 / 20", + "LogTransactionId__c": "4heTw6JlPFkvtu-qbxkog-", + "LimitsPublishImmediateDmlStatementsMax__c": 150, + "RecordCollectionType__c": "Single", + "LimitsFutureCallsUsed__c": 0, + "LimitsCpuTime__c": "74 / 10000", + "OriginLocation__c": "AnonymousBlock", + "LimitsDmlStatementsUsed__c": 0, + "Name": "a011h000005P8h1", + "StackTrace__c": "AnonymousBlock: line 14, column 1\nAnonymousBlock: line 14, column 1", + "HasStackTrace__c": true, + "LimitsSoslSearchesMax__c": 20, + "CreatedById": "0051h000008NoMwAAK", + "HasExceptionStackTrace__c": false, + "LimitsDmlRows__c": "0 / 10000", + "LimitsFutureCallsMax__c": 50, + "LimitsHeapSizeMax__c": 6000000, + "LimitsHeapSize__c": "15333 / 6000000", + "CreatedDate": "2022-03-09T05:22:53.000Z", + "LimitsSoqlQueryLocatorRowsUsed__c": 0, + "LimitsAsyncCalls__c": "0 / 200", + "LimitsCpuTimeMax__c": 10000, + "RecordName__c": "test-sgpopgvvyplr@example.com", + "EventUuid__c": "c780f79c-d24c-4a2c-84bb-be06249debb0", + "LoggedByUsernameLink__c": "test-sgpopgvvyplr@example.com", + "LimitsQueueableJobs__c": "0 / 50", + "LimitsSoqlQueryLocatorRows__c": "0 / 10000", + "LimitsHeapSizeUsed__c": 15333, + "TriggerIsExecuting__c": false, + "LimitsMobilePushApexCallsMax__c": 10, + "Origin__c": "Apex.AnonymousBlock", + "LimitsAsyncCallsMax__c": 200, + "LimitsAsyncCallsUsed__c": 0, + "LoggingLevel__c": "INFO", + "LimitsAggregateQueriesUsed__c": 0, + "LimitsEmailInvocations__c": "0 / 10", + "OriginType__c": "Apex", + "LimitsFutureCalls__c": "0 / 50", + "EpochTimestamp__c": 1646803370126, + "LimitsSoqlQueryLocatorRowsMax__c": 10000, + "LimitsEmailInvocationsMax__c": 10, + "LastModifiedById": "0051h000008NoMwAAK", + "LimitsSoqlQueriesUsed__c": 4 + }, + { + "LastModifiedDate": "2022-03-09T05:22:53.000Z", + "HasException__c": false, + "LimitsMobilePushApexCalls__c": "0 / 10", + "LoggingLevelWithImage__c": "\" WARN", + "HasDatabaseResult__c": false, + "LimitsAggregateQueries__c": "0 / 300", + "MessageMasked__c": true, + "LimitsSoqlQueryRowsMax__c": 50000, + "HasRecordJson__c": false, + "LimitsCalloutsMax__c": 100, + "LimitsEmailInvocationsUsed__c": 0, + "IsDeleted": false, + "LimitsCalloutsUsed__c": 0, + "Log__c": "a021h000004AXYMAA4", + "LimitsDmlRowsMax__c": 10000, + "LimitsCpuTimeUsed__c": 68, + "LimitsQueueableJobsMax__c": 50, + "LimitsPublishImmediateDmlStatementsUsed__c": 0, + "LimitsSoslSearchesUsed__c": 0, + "LimitsDmlStatementsMax__c": 150, + "Message__c": "Here is my fake Mastercard credit card ****-****-****-0005, please don't steal it", + "Id": "a011h000005P8h0AAC", + "LoggingLevelOrdinal__c": 7, + "MessageTruncated__c": false, + "LimitsQueueableJobsUsed__c": 0, + "LimitsPublishImmediateDmlStatements__c": "0 / 150", + "TransactionEntryNumber__c": 2, + "LimitsSoqlQueryRows__c": "4 / 50000", + "HasRecordId__c": false, + "LimitsSoqlQueries__c": "4 / 100", + "LimitsSoqlQueryRowsUsed__c": 4, + "Timestamp__c": "2022-03-09T05:22:50.000Z", + "LimitsDmlRowsUsed__c": 0, + "LimitsMobilePushApexCallsUsed__c": 0, + "RecordJsonMasked__c": false, + "LimitsAggregateQueriesMax__c": 300, + "SystemModstamp": "2022-03-09T05:22:53.000Z", + "LimitsCallouts__c": "0 / 100", + "LimitsSoqlQueriesMax__c": 100, + "LimitsDmlStatements__c": "0 / 150", + "LimitsSoslSearches__c": "0 / 20", + "LogTransactionId__c": "4heTw6JlPFkvtu-qbxkog-", + "LimitsPublishImmediateDmlStatementsMax__c": 150, + "LimitsFutureCallsUsed__c": 0, + "LimitsCpuTime__c": "68 / 10000", + "OriginLocation__c": "AnonymousBlock", + "LimitsDmlStatementsUsed__c": 0, + "Name": "a011h000005P8h0", + "StackTrace__c": "AnonymousBlock: line 13, column 1\nAnonymousBlock: line 13, column 1", + "HasStackTrace__c": true, + "LimitsSoslSearchesMax__c": 20, + "CreatedById": "0051h000008NoMwAAK", + "HasExceptionStackTrace__c": false, + "LimitsDmlRows__c": "0 / 10000", + "LimitsFutureCallsMax__c": 50, + "LimitsHeapSizeMax__c": 6000000, + "LimitsHeapSize__c": "11116 / 6000000", + "CreatedDate": "2022-03-09T05:22:53.000Z", + "LimitsSoqlQueryLocatorRowsUsed__c": 0, + "LimitsAsyncCalls__c": "0 / 200", + "LimitsCpuTimeMax__c": 10000, + "EventUuid__c": "9b68c3c3-e2f2-41e8-b4a2-b5470dfcde84", + "LoggedByUsernameLink__c": "test-sgpopgvvyplr@example.com", + "LimitsQueueableJobs__c": "0 / 50", + "LimitsSoqlQueryLocatorRows__c": "0 / 10000", + "LimitsHeapSizeUsed__c": 11116, + "TriggerIsExecuting__c": false, + "LimitsMobilePushApexCallsMax__c": 10, + "Origin__c": "Apex.AnonymousBlock", + "LimitsAsyncCallsMax__c": 200, + "LimitsAsyncCallsUsed__c": 0, + "LoggingLevel__c": "WARN", + "LimitsAggregateQueriesUsed__c": 0, + "LimitsEmailInvocations__c": "0 / 10", + "OriginType__c": "Apex", + "LimitsFutureCalls__c": "0 / 50", + "EpochTimestamp__c": 1646803370120, + "LimitsSoqlQueryLocatorRowsMax__c": 10000, + "LimitsEmailInvocationsMax__c": 10, + "LastModifiedById": "0051h000008NoMwAAK", + "LimitsSoqlQueriesUsed__c": 4 + }, + { + "LastModifiedDate": "2022-03-09T05:22:53.000Z", + "HasException__c": false, + "LimitsMobilePushApexCalls__c": "0 / 10", + "LoggingLevelWithImage__c": "\" ERROR", + "HasDatabaseResult__c": false, + "LimitsAggregateQueries__c": "0 / 300", + "MessageMasked__c": true, + "LimitsSoqlQueryRowsMax__c": 50000, + "HasRecordJson__c": false, + "LimitsCalloutsMax__c": 100, + "LimitsEmailInvocationsUsed__c": 0, + "IsDeleted": false, + "LimitsCalloutsUsed__c": 0, + "Log__c": "a021h000004AXYMAA4", + "LimitsDmlRowsMax__c": 10000, + "LimitsCpuTimeUsed__c": 30, + "LimitsQueueableJobsMax__c": 50, + "LimitsPublishImmediateDmlStatementsUsed__c": 0, + "LimitsSoslSearchesUsed__c": 0, + "LimitsDmlStatementsMax__c": 150, + "Message__c": "Here is my fake Visa credit card\n\n ****-****-****-0004,\n\n\n please don't steal it", + "Id": "a011h000005P8gzAAC", + "LoggingLevelOrdinal__c": 8, + "MessageTruncated__c": false, + "LimitsQueueableJobsUsed__c": 0, + "LimitsPublishImmediateDmlStatements__c": "0 / 150", + "TransactionEntryNumber__c": 1, + "LimitsSoqlQueryRows__c": "1 / 50000", + "HasRecordId__c": false, + "LimitsSoqlQueries__c": "1 / 100", + "LimitsSoqlQueryRowsUsed__c": 1, + "Timestamp__c": "2022-03-09T05:22:50.000Z", + "LimitsDmlRowsUsed__c": 0, + "LimitsMobilePushApexCallsUsed__c": 0, + "RecordJsonMasked__c": false, + "LimitsAggregateQueriesMax__c": 300, + "SystemModstamp": "2022-03-09T05:22:53.000Z", + "LimitsCallouts__c": "0 / 100", + "LimitsSoqlQueriesMax__c": 100, + "LimitsDmlStatements__c": "0 / 150", + "LimitsSoslSearches__c": "0 / 20", + "LogTransactionId__c": "4heTw6JlPFkvtu-qbxkog-", + "LimitsPublishImmediateDmlStatementsMax__c": 150, + "LimitsFutureCallsUsed__c": 0, + "LimitsCpuTime__c": "30 / 10000", + "OriginLocation__c": "AnonymousBlock", + "LimitsDmlStatementsUsed__c": 0, + "Name": "a011h000005P8gz", + "StackTrace__c": "AnonymousBlock: line 12, column 1\nAnonymousBlock: line 12, column 1", + "HasStackTrace__c": true, + "LimitsSoslSearchesMax__c": 20, + "CreatedById": "0051h000008NoMwAAK", + "HasExceptionStackTrace__c": false, + "LimitsDmlRows__c": "0 / 10000", + "LimitsFutureCallsMax__c": 50, + "LimitsHeapSizeMax__c": 6000000, + "LimitsHeapSize__c": "5472 / 6000000", + "CreatedDate": "2022-03-09T05:22:53.000Z", + "LimitsSoqlQueryLocatorRowsUsed__c": 0, + "LimitsAsyncCalls__c": "0 / 200", + "LimitsCpuTimeMax__c": 10000, + "EventUuid__c": "5a1678b2-82b6-4228-8907-a0df605bda03", + "LoggedByUsernameLink__c": "test-sgpopgvvyplr@example.com", + "LimitsQueueableJobs__c": "0 / 50", + "LimitsSoqlQueryLocatorRows__c": "0 / 10000", + "LimitsHeapSizeUsed__c": 5472, + "TriggerIsExecuting__c": false, + "LimitsMobilePushApexCallsMax__c": 10, + "Origin__c": "Apex.AnonymousBlock", + "LimitsAsyncCallsMax__c": 200, + "LimitsAsyncCallsUsed__c": 0, + "LoggingLevel__c": "ERROR", + "LimitsAggregateQueriesUsed__c": 0, + "LimitsEmailInvocations__c": "0 / 10", + "OriginType__c": "Apex", + "LimitsFutureCalls__c": "0 / 50", + "EpochTimestamp__c": 1646803370023, + "LimitsSoqlQueryLocatorRowsMax__c": 10000, + "LimitsEmailInvocationsMax__c": 10, + "LastModifiedById": "0051h000008NoMwAAK", + "LimitsSoqlQueriesUsed__c": 1 + } + ], + "LogRetentionDate__c": "2022-03-22", + "LoggedByUsernameLink__c": "test-sgpopgvvyplr@example.com", + "LoggedByUsername__c": "test-sgpopgvvyplr@example.com", + "LoggedBy__c": "0051h000008NoMxAAK", + "LoggerVersionNumber__c": "v4.7.0", + "LoginApplication__c": "Salesforce CLI", + "LoginBrowser__c": "Unknown", + "LoginHistoryId__c": "0Ya1h00000yEUrXCAW", + "LoginPlatform__c": "Unknown", + "LoginType__c": "Remote Access 2.0", + "MaxLogEntryLoggingLevelOrdinal__c": 8, + "Name": "Log-000004", + "OrganizationDomainUrl__c": "https://velocity-nosoftware-48362.cs79.my.salesforce.com", + "OrganizationEnvironmentType__c": "Scratch Org", + "OrganizationId__c": "00D1h000000MNKZEA4", + "OrganizationInstanceName__c": "CS79", + "OrganizationName__c": "Nebula Logger - Base Scratch Org", + "OrganizationType__c": "Enterprise Edition", + "Owner": { + "Type": "User", + "Id": "0051h000008NoMxAAK", + "Name": "User User" + }, + "OwnerId": "0051h000008NoMxAAK", + "Priority__c": "High", + "ProfileId__c": "00e1h000000xTOgAAM", + "ProfileLink__c": "System Administrator", + "ProfileName__c": "System Administrator", + "Scenario__c": "an example transaction scenario name", + "SendSlackNotification__c": true, + "SessionId__c": "0Ak1h00001MhxLcCAJ", + "SessionSecurityLevel__c": "STANDARD", + "SessionType__c": "Oauth2", + "SourceIp__c": "Salesforce.com IP", + "StartTime__c": "2022-03-09T05:22:50.000Z", + "Status__c": "New", + "SystemModeSummary__c": "API.v54.0.ANONYMOUS", + "SystemMode__c": "ANONYMOUS", + "SystemModstamp": "2022-03-09T05:22:57.000Z", + "TimeZoneId__c": "America/Los_Angeles", + "TimeZoneName__c": "(GMT-08:00) Pacific Standard Time (America/Los_Angeles)", + "TotalDEBUGLogEntries__c": 2, + "TotalERRORLogEntries__c": 1, + "TotalFINELogEntries__c": 1, + "TotalFINERLogEntries__c": 1, + "TotalFINESTLogEntries__c": 2, + "TotalINFOLogEntries__c": 1, + "TotalLimitsCpuTimeUsed__c": 132, + "TotalLogEntries__c": 9, + "TotalWARNLogEntries__c": 1, + "TransactionId__c": "4heTw6JlPFkvtu-qbxkog-", + "UserLicenseDefinitionKey__c": "SFDC", + "UserLicenseId__c": "1001h000000abKhAAI", + "UserLicenseName__c": "Salesforce", + "UserLoggingLevelOrdinal__c": 2, + "UserLoggingLevel__c": "FINEST", + "UserRoleLink__c": " ", + "UserType__c": "Standard", + "WasLoggedByCurrentUser__c": true +} 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 new file mode 100644 index 000000000..88c3060af --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/logViewer/__tests__/logViewer.test.js @@ -0,0 +1,111 @@ +import { createElement } from 'lwc'; +import LogViewer from 'c/logViewer'; +import getLog from '@salesforce/apex/Logger.getLog'; +import { registerApexTestWireAdapter } from '@salesforce/sfdx-lwc-jest'; + +// Mock data +const mockGetLog = require('./data/getLog.json'); + +// Register a test wire adapter +const getLogAdapter = registerApexTestWireAdapter(getLog); + +document.execCommand = jest.fn(); + +jest.mock( + '@salesforce/apex/Logger.getLog', + () => { + return { + default: () => mockGetLog + }; + }, + { virtual: true } +); + +test.todo('test for downloading JSON file'); +test.todo('test for downloading log file'); + +describe('Logger JSON Viewer lwc tests', () => { + afterEach(() => { + while (document.body.firstChild) { + document.body.removeChild(document.body.firstChild); + } + jest.clearAllMocks(); + }); + + it('sets document title', async () => { + const logViewerElement = createElement('c-log-viewer', { is: LogViewer }); + document.body.appendChild(logViewerElement); + getLogAdapter.emit(mockGetLog); + + await Promise.resolve(); + expect(logViewerElement.title).toEqual(mockGetLog.Name); + }); + + it('defaults to brand button variant', async () => { + const logViewer = createElement('c-log-viewer', { is: LogViewer }); + document.body.appendChild(logViewer); + + getLogAdapter.emit(mockGetLog); + + await Promise.resolve(); + const inputButton = logViewer.shadowRoot.querySelector('lightning-button-stateful'); + expect(logViewer.title).toEqual(mockGetLog.Name); + expect(inputButton.variant).toEqual('brand'); + }); + + it('copies the JSON to the clipboard', async () => { + const logViewer = createElement('c-log-viewer', { is: LogViewer }); + document.body.appendChild(logViewer); + + getLogAdapter.emit(mockGetLog); + + return Promise.resolve() + .then(() => { + let copyBtn = logViewer.shadowRoot.querySelector('lightning-button-stateful[data-id="copy-btn"]'); + copyBtn.click(); + }) + .then(() => { + const tab = logViewer.shadowRoot.querySelector('lightning-tab[data-id="json-content"]'); + expect(tab.value).toEqual('json'); + tab.dispatchEvent(new CustomEvent('active')); + }) + .then(() => { + const clipboardContent = JSON.parse(logViewer.shadowRoot.querySelector('pre').textContent); + expect(clipboardContent).toEqual(mockGetLog); + expect(document.execCommand).toHaveBeenCalledWith('copy'); + }); + }); + + it('copies the log file to the clipboard', async () => { + const logViewer = createElement('c-log-viewer', { is: LogViewer }); + document.body.appendChild(logViewer); + + getLogAdapter.emit(mockGetLog); + + return Promise.resolve() + .then(() => { + let copyBtn = logViewer.shadowRoot.querySelector('lightning-button-stateful[data-id="copy-btn"]'); + copyBtn.click(); + }) + .then(() => { + const tab = logViewer.shadowRoot.querySelector('lightning-tab[data-id="file-content"]'); + expect(tab.value).toEqual('file'); + tab.dispatchEvent(new CustomEvent('active')); + }) + .then(() => { + let expectedContentLines = []; + mockGetLog.LogEntries__r.forEach(logEntry => { + const columns = []; + columns.push('[' + new Date(logEntry.EpochTimestamp__c).toISOString() + ' - ' + logEntry.LoggingLevel__c + ']'); + columns.push('[Message]\n' + logEntry.Message__c); + columns.push('\n[Stack Trace]\n' + logEntry.StackTrace__c); + + expectedContentLines.push(columns.join('\n')); + }); + + const clipboardContent = logViewer.shadowRoot.querySelector('pre').textContent; + 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 new file mode 100644 index 000000000..d35f572f0 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/logViewer/logViewer.css @@ -0,0 +1,9 @@ +.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 new file mode 100644 index 000000000..dfda55ed1 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/logViewer/logViewer.html @@ -0,0 +1,36 @@ + + + diff --git a/nebula-logger/core/main/log-management/lwc/logViewer/logViewer.js b/nebula-logger/core/main/log-management/lwc/logViewer/logViewer.js new file mode 100644 index 000000000..9b32918e0 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/logViewer/logViewer.js @@ -0,0 +1,152 @@ +/************************************************************************************************* + * 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 getLog from '@salesforce/apex/Logger.getLog'; +import LOG_ENTRY_OBJECT from '@salesforce/schema/LogEntry__c'; +import LOG_ENTRY_EPOCH_TIMESTAMP_FIELD from '@salesforce/schema/LogEntry__c.EpochTimestamp__c'; +import LOG_ENTRY_LOGGING_LEVEL_FIELD from '@salesforce/schema/LogEntry__c.LoggingLevel__c'; +import LOG_ENTRY_MESSAGE_FIELD from '@salesforce/schema/LogEntry__c.Message__c'; +import LOG_ENTRY_STACK_TRACE_FIELD from '@salesforce/schema/LogEntry__c.StackTrace__c'; +import LOG_ORGANIZATION_ID_FIELD from '@salesforce/schema/Log__c.OrganizationId__c'; + +export default class LogViewer extends LightningElement { + // logId is deprecated - it was used before quickActions supported LWC. + // recordId is now used instead, but logId has to be kept for the managed package + @api + logId; // Deprecated + + @api + recordId; + + isLoaded = false; + log = {}; + currentMode = {}; + dataCopied = false; + + _logFileContent; + _logJSONContent; + + @wire(getLog, { logId: '$recordId' }) + wiredGetLog(result) { + if (result.data) { + this.log = result.data; + this._loadLogFileContent(); + this._loadLogJSONContent(); + this.isLoaded = true; + } + } + + @api + get title() { + return this.log?.Name; + } + + get variant() { + return this.dataCopied ? 'success' : 'brand'; + } + + get downloadButtonLabel() { + return `Download ${this.currentMode?.label}`; + } + + handleTabActivated(event) { + this.currentMode = { + label: event.target.label, + value: event.target.value + }; + + /* eslint-disable-next-line default-case */ + switch (this.currentMode.value) { + case 'file': + this.currentMode.data = this._logFileContent; + this.currentMode.extension = 'log'; + break; + case 'json': + this.currentMode.data = this._logJSONContent; + this.currentMode.extension = 'json'; + break; + } + } + + async copyToClipboard() { + const value = this.currentMode.data; + + const textArea = document.createElement('textarea'); + textArea.value = value; + // Avoid scrolling to bottom + textArea.style.top = '0'; + textArea.style.left = '0'; + textArea.style.position = 'fixed'; + + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + document.execCommand('copy'); + document.body.removeChild(textArea); + + /* eslint-disable-next-line no-console */ + console.log('Log data successfully copied to clipboard', value); + this.dataCopied = true; + + /* eslint-disable-next-line @lwc/lwc/no-async-operation */ + setTimeout(() => { + this.dataCopied = false; + }, 5000); + } + + async downloadFile() { + const exportedFilename = this.log.Name + '_' + this.log[LOG_ORGANIZATION_ID_FIELD.fieldApiName] + '.' + this.currentMode.extension; + const encodedValue = encodeURIComponent(this.currentMode.data); + + const link = window.document.createElement('a'); + link.href = 'data:text;charset=utf-8,' + encodedValue; + link.target = '_blank'; + link.download = exportedFilename; + link.click(); + } + + _loadLogFileContent() { + const fieldDelimiter = '\n'; + const lineDelimiter = '\n\n' + '-'.repeat(36) + '\n\n'; + const logFileLines = []; + + // There's probably a better way to do this long-term, but this handles determining if Logger is running with a namespace + // This can be determined by splitting the LogEntry__c object's name into an array, splitting on '__' since SF does not let + // admins/devs include '__' in an object name - the platform automatically includes it: + // - One occurrence of '__' when there is no namespace: LogEntry__c (splits into an array with 2 items) + // - Two occurrences of '__' when there is namespace: Nebula__LogEntry__c (splits into an array with 3 items) + const sobjectNamePieces = LOG_ENTRY_OBJECT.objectApiName.split('__'); + const namespacePrefix = sobjectNamePieces.length === 3 ? sobjectNamePieces[0] + '__' : ''; + const logEntriesRelationshipName = namespacePrefix + 'LogEntries__r'; + this.log[logEntriesRelationshipName].forEach(logEntry => { + const columns = []; + columns.push( + '[' + + new Date(logEntry[LOG_ENTRY_EPOCH_TIMESTAMP_FIELD.fieldApiName]).toISOString() + + ' - ' + + logEntry[LOG_ENTRY_LOGGING_LEVEL_FIELD.fieldApiName] + + ']' + ); + columns.push('[Message]\n' + logEntry[LOG_ENTRY_MESSAGE_FIELD.fieldApiName]); + columns.push('\n[Stack Trace]\n' + logEntry[LOG_ENTRY_STACK_TRACE_FIELD.fieldApiName]); + + logFileLines.push(columns.join(fieldDelimiter)); + }); + this._logFileContent = logFileLines.join(lineDelimiter); + } + + _loadLogJSONContent() { + // Sort the keys (fields) in the log object + let formattedLog; + formattedLog = Object.keys(this.log) + .sort() + .reduce((obj, key) => { + obj[key] = this.log[key]; + return obj; + }, {}); + this._logJSONContent = JSON.stringify(formattedLog, null, '\t'); + } +} diff --git a/nebula-logger/core/main/log-management/lwc/logJSON/logJSON.js-meta.xml b/nebula-logger/core/main/log-management/lwc/logViewer/logViewer.js-meta.xml similarity index 92% rename from nebula-logger/core/main/log-management/lwc/logJSON/logJSON.js-meta.xml rename to nebula-logger/core/main/log-management/lwc/logViewer/logViewer.js-meta.xml index 11bf4bd15..28d152906 100644 --- a/nebula-logger/core/main/log-management/lwc/logJSON/logJSON.js-meta.xml +++ b/nebula-logger/core/main/log-management/lwc/logViewer/logViewer.js-meta.xml @@ -1,6 +1,6 @@ - 53.0 + 54.0 true lightning__RecordAction diff --git a/nebula-logger/core/main/log-management/lwc/loggerSettings/__tests__/data/createRecord.json b/nebula-logger/core/main/log-management/lwc/loggerSettings/__tests__/data/createRecord.json index e9f8d3121..64aa27d59 100644 --- a/nebula-logger/core/main/log-management/lwc/loggerSettings/__tests__/data/createRecord.json +++ b/nebula-logger/core/main/log-management/lwc/loggerSettings/__tests__/data/createRecord.json @@ -9,6 +9,6 @@ "IsDataMaskingEnabled__c": true, "IsDeleted": false, "IsEnabled__c": true, - "LoggingLevel__c": "DEBUG", - "StripInaccessibleRecordFields__c": false + "IsRecordFieldStrippingEnabled__c": false, + "LoggingLevel__c": "DEBUG" } diff --git a/nebula-logger/core/main/log-management/lwc/loggerSettings/__tests__/data/getLoggerSettingsSchema.json b/nebula-logger/core/main/log-management/lwc/loggerSettings/__tests__/data/getLoggerSettingsSchema.json new file mode 100644 index 000000000..b6512d296 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/loggerSettings/__tests__/data/getLoggerSettingsSchema.json @@ -0,0 +1,159 @@ +{ + "apiName": "LoggerSettings__c", + "fields": { + "Id": { + "apiName": "Id", + "label": "Record ID", + "localApiName": "Id", + "type": "Id" + }, + "IsDeleted": { + "apiName": "IsDeleted", + "label": "Deleted", + "localApiName": "IsDeleted", + "type": "Boolean" + }, + "Name": { + "apiName": "Name", + "label": "Name", + "localApiName": "Name", + "type": "String" + }, + "SetupOwnerId": { + "apiName": "SetupOwnerId", + "label": "Location", + "localApiName": "SetupOwnerId", + "type": "Reference" + }, + "CreatedDate": { + "apiName": "CreatedDate", + "label": "Created Date", + "localApiName": "CreatedDate", + "type": "Datetime" + }, + "CreatedById": { + "apiName": "CreatedById", + "label": "Created By ID", + "localApiName": "CreatedById", + "type": "Reference" + }, + "LastModifiedDate": { + "apiName": "LastModifiedDate", + "label": "Last Modified Date", + "localApiName": "LastModifiedDate", + "type": "Datetime" + }, + "LastModifiedById": { + "apiName": "LastModifiedById", + "label": "Last Modified By ID", + "localApiName": "LastModifiedById", + "type": "Reference" + }, + "SystemModstamp": { + "apiName": "SystemModstamp", + "label": "System Modstamp", + "localApiName": "SystemModstamp", + "type": "Datetime" + }, + "DefaultLogOwner__c": { + "apiName": "DefaultLogOwner__c", + "inlineHelpText": "Specifies the default owner for new Log__c records. This can be a user ID, a username, a queue ID, or a queue's developer name.", + "label": "Log Owner", + "localApiName": "DefaultLogOwner__c", + "type": "String" + }, + "DefaultLogShareAccessLevel__c": { + "apiName": "DefaultLogShareAccessLevel__c", + "inlineHelpText": "Uses Apex managed sharing to grants users read or edit access to their log records (on insert only). When no access level is specified, no Apex sharing logic is executed. This only gives record-level access - users will still need to be granted access to the Log__c object using permission sets or profiles.", + "label": "Log Access Level", + "localApiName": "DefaultLogShareAccessLevel__c", + "type": "String" + }, + "DefaultNumberOfDaysToRetainLogs__c": { + "apiName": "DefaultNumberOfDaysToRetainLogs__c", + "inlineHelpText": "This value is used to set the field Log__c.LogRetentionDate__c, which is then used by LogBatchPurger to delete old logs. To keep logs indefinitely, set this field to blank (null).", + "label": "Days to Retain Logs", + "localApiName": "DefaultNumberOfDaysToRetainLogs__c", + "type": "Double" + }, + "DefaultSaveMethod__c": { + "apiName": "DefaultSaveMethod__c", + "inlineHelpText": "Defaults to EVENT_BUS. This controls the default save method used by Logger when calling saveLog(). In most situations, EVENT_BUS should be used.", + "label": "Save Method", + "localApiName": "DefaultSaveMethod__c", + "type": "String" + }, + "IsAnonymousModeEnabled__c": { + "apiName": "IsAnonymousModeEnabled__c", + "inlineHelpText": "When enabled, any logs generated will not have any user-specific details set - any fields related to the User, Profile, etc. will be null. Note: this feature only works properly when using the save method EVENT_BUS.", + "label": "Enable Anonymous Mode", + "localApiName": "IsAnonymousModeEnabled__c", + "type": "Boolean" + }, + "IsApexSystemDebugLoggingEnabled__c": { + "apiName": "IsApexSystemDebugLoggingEnabled__c", + "inlineHelpText": "When enabled, Logger will automatically call Apex's System.debug(). To help with performance, this option should be disabled in production unless you are actively troubleshooting an issue.", + "label": "Enable Apex System.debug()", + "localApiName": "IsApexSystemDebugLoggingEnabled__c", + "type": "Boolean" + }, + "IsDataMaskingEnabled__c": { + "apiName": "IsDataMaskingEnabled__c", + "inlineHelpText": "When enabled, any data-mask rules (configured in LogEntryDataMaskRule__mdt) will be automatically applied to log entry messages.", + "label": "Enable Data Masking", + "localApiName": "IsDataMaskingEnabled__c", + "type": "Boolean" + }, + "IsEnabled__c": { + "apiName": "IsEnabled__c", + "inlineHelpText": "Controls if Logger is enabled for the specified level (organization, profile, or user)", + "label": "Enabled", + "localApiName": "IsEnabled__c", + "type": "Boolean" + }, + "IsJavaScriptConsoleLoggingEnabled__c": { + "apiName": "IsJavaScriptConsoleLoggingEnabled__c", + "inlineHelpText": "When enabled, Logger will automatically call the browser's console.log() function when logging via lightning components. To help with performance, this option should be disabled in production unless you are actively troubleshooting an issue.", + "label": "Enable JavaScript console.log()", + "localApiName": "IsJavaScriptConsoleLoggingEnabled__c", + "type": "Boolean" + }, + "IsPlatformEventStorageEnabled__c": { + "apiName": "IsPlatformEventStorageEnabled__c", + "inlineHelpText": "Controls if LogEntryEvent__e platform events are transformed & stored in the custom objects Log__c and LogEntry__c (when IsSavingEnabled__c == true).", + "label": "Store Platform Events", + "localApiName": "IsPlatformEventStorageEnabled__c", + "type": "Boolean" + }, + "IsRecordFieldStrippingEnabled__c": { + "apiName": "IsRecordFieldStrippingEnabled__c", + "inlineHelpText": "When enabled, any time an SObject record is logged, only fields that the current user can access will be included in the record's JSON.", + "label": "Strip Inaccessible Record Fields", + "localApiName": "IsRecordFieldStrippingEnabled__c", + "type": "Boolean" + }, + "IsSavingEnabled__c": { + "apiName": "IsSavingEnabled__c", + "inlineHelpText": "Controls if saving is enabled - when disabled, any calls to saveLog() are ignored.", + "label": "Enable Saving", + "localApiName": "IsSavingEnabled__c", + "type": "Boolean" + }, + "LoggingLevel__c": { + "apiName": "LoggingLevel__c", + "label": "Logging Level", + "localApiName": "LoggingLevel__c", + "type": "String" + }, + "DefaultLogScenario__c": { + "apiName": "DefaultLogScenario__c", + "inlineHelpText": "Sets a default scenario for the transaction", + "label": "Log Scenario", + "localApiName": "DefaultLogScenario__c", + "type": "String" + } + }, + "label": "Logger Settings", + "labelPlural": "Logger Settings", + "localApiName": "LoggerSettings__c" +} diff --git a/nebula-logger/core/main/log-management/lwc/loggerSettings/__tests__/data/getRecords.json b/nebula-logger/core/main/log-management/lwc/loggerSettings/__tests__/data/getRecords.json index e9d473a9f..3531736e7 100644 --- a/nebula-logger/core/main/log-management/lwc/loggerSettings/__tests__/data/getRecords.json +++ b/nebula-logger/core/main/log-management/lwc/loggerSettings/__tests__/data/getRecords.json @@ -15,11 +15,11 @@ "IsJavaScriptConsoleLoggingEnabled__c": true, "IsDataMaskingEnabled__c": true, "IsEnabled__c": true, + "IsRecordFieldStrippingEnabled__c": false, "LastModifiedById": "0051g000007o2bNAAQ", "LastModifiedDate": "2021-12-02T05:35:37.000+0000", "LoggingLevel__c": "INFO", "SetupOwnerId": "00D1g000000HgecEAC", - "StripInaccessibleRecordFields__c": false, "CreatedBy": { "attributes": { "type": "User", "url": "/services/data/v53.0/sobjects/User/0051g000007o2bNAAQ" }, "Id": "0051g000007o2bNAAQ", @@ -56,11 +56,11 @@ "IsJavaScriptConsoleLoggingEnabled__c": true, "IsDataMaskingEnabled__c": true, "IsEnabled__c": true, + "IsRecordFieldStrippingEnabled__c": false, "LastModifiedById": "0051g000007o2bNAAQ", "LastModifiedDate": "2021-12-02T05:00:18.000+0000", "LoggingLevel__c": "DEBUG", "SetupOwnerId": "00e1g000000ySdaAAE", - "StripInaccessibleRecordFields__c": false, "CreatedBy": { "attributes": { "type": "User", "url": "/services/data/v53.0/sobjects/User/0051g000007o2bNAAQ" }, "Id": "0051g000007o2bNAAQ", @@ -97,11 +97,11 @@ "IsJavaScriptConsoleLoggingEnabled__c": true, "IsDataMaskingEnabled__c": false, "IsEnabled__c": true, + "IsRecordFieldStrippingEnabled__c": false, "LastModifiedById": "0051g000007o2bNAAQ", "LastModifiedDate": "2021-12-02T05:35:02.000+0000", "LoggingLevel__c": "INFO", "SetupOwnerId": "00e1g000000ySdXAAU", - "StripInaccessibleRecordFields__c": false, "CreatedBy": { "attributes": { "type": "User", "url": "/services/data/v53.0/sobjects/User/0051g000007o2bNAAQ" }, "Id": "0051g000007o2bNAAQ", @@ -138,11 +138,11 @@ "IsJavaScriptConsoleLoggingEnabled__c": true, "IsDataMaskingEnabled__c": false, "IsEnabled__c": true, + "IsRecordFieldStrippingEnabled__c": false, "LastModifiedById": "0051g000007o2bNAAQ", "LastModifiedDate": "2021-11-30T05:22:42.000+0000", "LoggingLevel__c": "FINER", "SetupOwnerId": "0051g000007o2bTAAQ", - "StripInaccessibleRecordFields__c": false, "CreatedBy": { "attributes": { "type": "User", "url": "/services/data/v53.0/sobjects/User/0051g000007o2bNAAQ" }, "Id": "0051g000007o2bNAAQ", @@ -179,11 +179,11 @@ "IsJavaScriptConsoleLoggingEnabled__c": true, "IsDataMaskingEnabled__c": false, "IsEnabled__c": true, + "IsRecordFieldStrippingEnabled__c": false, "LastModifiedById": "0051g000007o2bNAAQ", "LastModifiedDate": "2021-11-30T04:59:57.000+0000", "LoggingLevel__c": "DEBUG", "SetupOwnerId": "0051g000007o2baAAA", - "StripInaccessibleRecordFields__c": false, "CreatedBy": { "attributes": { "type": "User", "url": "/services/data/v53.0/sobjects/User/0051g000007o2bNAAQ" }, "Id": "0051g000007o2bNAAQ", @@ -220,11 +220,11 @@ "IsJavaScriptConsoleLoggingEnabled__c": true, "IsDataMaskingEnabled__c": false, "IsEnabled__c": true, + "IsRecordFieldStrippingEnabled__c": false, "LastModifiedById": "0051g000007o2bNAAQ", "LastModifiedDate": "2021-11-30T22:36:40.000+0000", "LoggingLevel__c": "DEBUG", "SetupOwnerId": "0051g000007o2bcAAA", - "StripInaccessibleRecordFields__c": false, "CreatedBy": { "attributes": { "type": "User", "url": "/services/data/v53.0/sobjects/User/0051g000007o2bNAAQ" }, "Id": "0051g000007o2bNAAQ", @@ -261,11 +261,11 @@ "IsJavaScriptConsoleLoggingEnabled__c": true, "IsDataMaskingEnabled__c": false, "IsEnabled__c": true, + "IsRecordFieldStrippingEnabled__c": false, "LastModifiedById": "0051g000007o2bNAAQ", "LastModifiedDate": "2021-11-30T06:16:01.000+0000", "LoggingLevel__c": "DEBUG", "SetupOwnerId": "0051g000007o2bZAAQ", - "StripInaccessibleRecordFields__c": false, "CreatedBy": { "attributes": { "type": "User", "url": "/services/data/v53.0/sobjects/User/0051g000007o2bNAAQ" }, "Id": "0051g000007o2bNAAQ", @@ -302,11 +302,11 @@ "IsJavaScriptConsoleLoggingEnabled__c": true, "IsDataMaskingEnabled__c": true, "IsEnabled__c": true, + "IsRecordFieldStrippingEnabled__c": false, "LastModifiedById": "0051g000007o2bNAAQ", "LastModifiedDate": "2021-12-02T05:09:48.000+0000", "LoggingLevel__c": "DEBUG", "SetupOwnerId": "0051g000007oJ8cAAE", - "StripInaccessibleRecordFields__c": false, "CreatedBy": { "attributes": { "type": "User", "url": "/services/data/v53.0/sobjects/User/0051g000007o2bNAAQ" }, "Id": "0051g000007o2bNAAQ", @@ -343,11 +343,11 @@ "IsJavaScriptConsoleLoggingEnabled__c": true, "IsDataMaskingEnabled__c": false, "IsEnabled__c": true, + "IsRecordFieldStrippingEnabled__c": false, "LastModifiedById": "0051g000007o2bNAAQ", "LastModifiedDate": "2021-11-30T05:22:31.000+0000", "LoggingLevel__c": "WARN", "SetupOwnerId": "0051g000007o2bNAAQ", - "StripInaccessibleRecordFields__c": false, "CreatedBy": { "attributes": { "type": "User", "url": "/services/data/v53.0/sobjects/User/0051g000007o2bNAAQ" }, "Id": "0051g000007o2bNAAQ", diff --git a/nebula-logger/core/main/log-management/lwc/loggerSettings/__tests__/loggerSettings.test.js b/nebula-logger/core/main/log-management/lwc/loggerSettings/__tests__/loggerSettings.test.js index 3839276e3..ddd4453db 100644 --- a/nebula-logger/core/main/log-management/lwc/loggerSettings/__tests__/loggerSettings.test.js +++ b/nebula-logger/core/main/log-management/lwc/loggerSettings/__tests__/loggerSettings.test.js @@ -3,8 +3,8 @@ import { createElement } from 'lwc'; import LoggerSettings from 'c/loggerSettings'; // LoggerSettings__c metadata -import { getObjectInfo } from 'lightning/uiObjectInfoApi'; import canUserModifyLoggerSettings from '@salesforce/apex/LoggerSettingsController.canUserModifyLoggerSettings'; +import getLoggerSettingsSchema from '@salesforce/apex/LoggerSObjectMetadata.getLoggerSettingsSchema'; import getPicklistOptions from '@salesforce/apex/LoggerSettingsController.getPicklistOptions'; import getOrganization from '@salesforce/apex/LoggerSettingsController.getOrganization'; import searchForSetupOwner from '@salesforce/apex/LoggerSettingsController.searchForSetupOwner'; @@ -16,7 +16,7 @@ import saveRecord from '@salesforce/apex/LoggerSettingsController.saveRecord'; import deleteRecord from '@salesforce/apex/LoggerSettingsController.deleteRecord'; // Mock metadata -const mockObjectInfo = require('./data/getObjectInfo.json'); +const mockLoggerSettingsSchema = require('./data/getLoggerSettingsSchema.json'); const mockOrganization = require('./data/getOrganization.json'); const mockPicklistOptions = require('./data/getPicklistOptions.json'); @@ -105,9 +105,20 @@ jest.mock( { virtual: true } ); +jest.mock( + '@salesforce/apex/LoggerSObjectMetadata.getLoggerSettingsSchema', + () => { + return { + default: jest.fn() + }; + }, + { virtual: true } +); + async function initializeElement(enableModifyAccess) { // Assign mock values for resolved Apex promises canUserModifyLoggerSettings.mockResolvedValue(enableModifyAccess); + getLoggerSettingsSchema.mockResolvedValue(mockLoggerSettingsSchema); getPicklistOptions.mockResolvedValue(mockPicklistOptions); getRecords.mockResolvedValue(mockRecords); @@ -115,9 +126,7 @@ async function initializeElement(enableModifyAccess) { const loggerSettingsElement = createElement('c-logger-settings', { is: LoggerSettings }); document.body.appendChild(loggerSettingsElement); - // Emit data from @wire - await getObjectInfo.emit(mockObjectInfo); - await Promise.resolve(); + await new Promise(resolve => setTimeout(resolve, 0)); return loggerSettingsElement; } @@ -156,6 +165,7 @@ describe('Logger Settings lwc tests', () => { // Verify the expected Apex/framework calls expect(canUserModifyLoggerSettings).toHaveBeenCalledTimes(1); + expect(getLoggerSettingsSchema).toHaveBeenCalledTimes(1); expect(getPicklistOptions).toHaveBeenCalledTimes(1); expect(getRecords).toHaveBeenCalledTimes(1); expect(createRecord).toHaveBeenCalledTimes(0); @@ -211,8 +221,8 @@ describe('Logger Settings lwc tests', () => { 'IsJavaScriptConsoleLoggingEnabled__c', 'IsDataMaskingEnabled__c', 'IsEnabled__c', - 'LoggingLevel__c', - 'StripInaccessibleRecordFields__c' + 'IsRecordFieldStrippingEnabled__c', + 'LoggingLevel__c' ]; expectedFieldNames.forEach(fieldName => { const inputField = loggerSettingsElement.shadowRoot.querySelector('[data-id="' + fieldName + '"]'); @@ -311,6 +321,8 @@ describe('Logger Settings lwc tests', () => { expectedNewRecord.SetupOwnerId = mockOrganization.Id; expectedNewRecord.LoggingLevel__c = specifiedLoggingLevel; expectedNewRecord.DefaultSaveMethod__c = specifiedSaveMethod; + expectedNewRecord.setupOwnerName = mockOrganization.Name; + expectedNewRecord.setupOwnerType = setupOwnerTypeField.value; const expectedApexParameter = { settingsRecord: expectedNewRecord }; const saveRecordBtn = loggerSettingsElement.shadowRoot.querySelector('[data-id="save-btn"]'); saveRecordBtn.click(); @@ -355,6 +367,8 @@ describe('Logger Settings lwc tests', () => { expectedNewRecord.SetupOwnerId = mockOrganization.Id; expectedNewRecord.LoggingLevel__c = specifiedLoggingLevel; expectedNewRecord.IsDataMaskingEnabled__c = isDataMaskingEnabled; + expectedNewRecord.setupOwnerName = mockOrganization.Name; + expectedNewRecord.setupOwnerType = setupOwnerTypeField.value; const expectedApexParameter = { settingsRecord: expectedNewRecord }; const saveKeyboardShortcutEvent = new KeyboardEvent('keydown', { code: 'KeyS', ctrlKey: true }); @@ -509,7 +523,7 @@ describe('Logger Settings lwc tests', () => { 'IsApexSystemDebugLoggingEnabled__c', 'IsJavaScriptConsoleLoggingEnabled__c', 'IsDataMaskingEnabled__c', - 'StripInaccessibleRecordFields__c', + 'IsRecordFieldStrippingEnabled__c', 'IsAnonymousModeEnabled__c' ]; expectedEditableFields.forEach(fieldDataId => { 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 833bf7cf0..863dadbed 100644 --- a/nebula-logger/core/main/log-management/lwc/loggerSettings/loggerSettings.html +++ b/nebula-logger/core/main/log-management/lwc/loggerSettings/loggerSettings.html @@ -53,6 +53,7 @@

Logger Settings Details

+

General Settings @@ -60,23 +61,23 @@

@@ -192,10 +193,10 @@

>

- -
-

- Log Management Settings -

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

- Developer Settings -

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

- Security Settings -

- - - - - - - - - -
+ + - +