diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 17a9d8d0f..8ab8326b3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,41 +8,16 @@ env: on: push: - branches: - - main - - 'devops/**' - paths-ignore: - - 'content/**' - - 'docs/**' - - 'examples/**' - - 'packages/**' - - '.forceignore' - - '.gitignore' - - '.prettierignore' - - '.prettierrc' - - 'CONTRIBUTING.md' - - 'LICENSE' - - 'package.json' - - 'README.md' - - './**/README.md' - - 'sfdx-project.json' + paths: + - .github/workflows/** + - config/scratch-orgs/** + - nebula-logger/** pull_request: types: [opened, synchronize, reopened] - paths-ignore: - - 'content/**' - - 'docs/**' - - 'examples/**' - - 'packages/**' - - '.forceignore' - - '.gitignore' - - '.prettierignore' - - '.prettierrc' - - 'CONTRIBUTING.md' - - 'LICENSE' - - 'package.json' - - 'README.md' - - './**/README.md' - - 'sfdx-project.json' + paths: + - .github/workflows/** + - config/scratch-orgs/** + - nebula-logger/** jobs: code-quality-tests: @@ -66,8 +41,16 @@ jobs: if: steps.cache-npm.outputs.cache-hit != 'true' run: npm ci + - name: 'Check for changes in core directory' + uses: dorny/paths-filter@v3 + id: changes + with: + filters: | + core: + - './nebula-logger/core/**' + - name: 'Authorize Dev Hub' - if: ${{ github.event_name == 'pull_request' }} + if: ${{ (github.event_name == 'pull_request') && (steps.changes.outputs.core == 'true') }} shell: bash run: | npx sf version @@ -80,7 +63,7 @@ jobs: DEV_HUB_JWT_SERVER_KEY: ${{ secrets.DEV_HUB_JWT_SERVER_KEY }} - name: 'Verify package version number is updated' - if: ${{ github.event_name == 'pull_request' }} + if: ${{ (github.event_name == 'pull_request') && (steps.changes.outputs.core == 'true') }} run: npm run package:version:number:verify - name: 'Verify LWC with ESLint' @@ -98,8 +81,8 @@ jobs: - name: 'Verify formatting with Prettier' run: npm run prettier:verify - lwc-tests: - name: 'Run LWC Tests' + lwc-jest-tests: + name: 'Run LWC Jest Tests' needs: [code-quality-tests] runs-on: ubuntu-latest steps: @@ -130,6 +113,89 @@ jobs: flags: LWC token: ${{ secrets.CODECOV_TOKEN }} + advanced-scratch-org-tests: + environment: 'Advanced Scratch Org' + name: 'Run Advanced Scratch Org Tests' + needs: [code-quality-tests] + runs-on: ubuntu-latest + steps: + - name: 'Checkout source code' + uses: actions/checkout@v4 + + - name: 'Restore node_modules cache' + id: cache-npm + uses: actions/cache@v4 + with: + path: node_modules + key: npm-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + npm-${{ env.cache-name }}- + npm- + + - name: 'Install npm dependencies' + if: steps.cache-npm.outputs.cache-hit != 'true' + run: npm ci + + - name: 'Authorize Dev Hub' + shell: bash + run: | + npx sf version + echo "${{ env.DEV_HUB_JWT_SERVER_KEY }}" > ./jwt-server.key + npx sf org login jwt --instance-url ${{ env.DEV_HUB_AUTH_URL }} --client-id ${{ env.DEV_HUB_CONSUMER_KEY }} --username ${{ env.DEV_HUB_BOT_USERNAME }} --jwt-key-file ./jwt-server.key --set-default-dev-hub + env: + 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 Scratch Org' + run: npx sf org create scratch --no-namespace --no-track-source --duration-days 1 --definition-file ./config/scratch-orgs/advanced-scratch-def.json --wait 20 --set-default --json + + # https://help.salesforce.com/s/articleView?id=000394906&type=1 + - name: 'Install OmniStudio managed package v250.7.1 (Summer ‘24 release)' + run: npx sf package install --package 04t4W000002Z6oC --security-type AdminsOnly --wait 30 --no-prompt + + # To ensure that all of the Apex classes in the core directory have 75+ code coverage, + # deploy only the core directory & run all of its tests as part of the deployment, using `--test-level RunLocalTests` + - name: 'Validate Core Source in Scratch Org' + run: npx sf project deploy validate --source-dir ./nebula-logger/core/ --test-level RunLocalTests + + # Now that the core directory has been deployed & tests have passed, deploy all of the metadata + - name: 'Deploy All Source to Scratch Org' + run: npx sf project deploy start --source-dir ./nebula-logger/ + + - name: 'Deploy Test Metadata' + run: npx sf project deploy start --source-dir ./config/scratch-orgs/ + + - name: 'Assign Logger Admin Permission Set' + run: npm run permset:assign:admin + + - name: 'Validate Custom Metadata Records' + run: npx sf apex run --file ./scripts/build/validate-custom-metadata-records.apex + + # Nebula Logger has functionality that queries the AuthSession object when the current user has an active session. + # The code should work with or without an active session, so the pipeline runs the tests twice - asynchronously and synchronously. + # This is done because, based on how you execute Apex tests, the running user may have an active session (synchrously) or not (asynchronously). + # Utlimately, this could/should probably be better mocked during tests, but the AuthSession is read-only in Apex, so it's a bit difficult to work with. + # Running the Apex tests sync & async serves as an extra level of integration testing to ensure that everything works with or without an active session. + - name: 'Run Apex Tests Asynchronously' + run: npm run test:apex:nocoverage + + - name: 'Run Apex Tests Synchronously' + run: npm run test:apex -- --synchronous + + # This is the only scratch org that's used for uploading code coverage + - name: 'Upload Apex test code coverage to Codecov.io' + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + flags: Apex + token: ${{ secrets.CODECOV_TOKEN }} + + - name: 'Delete Scratch Org' + run: npx sf org delete scratch --no-prompt + if: ${{ always() }} + base-scratch-org-tests: environment: 'Base Scratch Org' name: 'Run Base Scratch Org Tests' @@ -166,9 +232,15 @@ jobs: DEV_HUB_JWT_SERVER_KEY: ${{ secrets.DEV_HUB_JWT_SERVER_KEY }} - name: 'Create Scratch Org' - run: npx sf org create scratch --no-namespace --duration-days 1 --definition-file ./config/scratch-orgs/build-base-scratch-def.json --wait 20 --set-default --json + run: npx sf org create scratch --no-namespace --no-track-source --duration-days 1 --definition-file ./config/scratch-orgs/base-scratch-def.json --wait 20 --set-default --json - - name: 'Deploy Source to Scratch Org' + # To ensure that all of the Apex classes in the core directory have 75+ code coverage, + # deploy only the core directory & run all of its tests as part of the deployment, using `--test-level RunLocalTests` + - name: 'Validate Core Source in Scratch Org' + run: npx sf project deploy validate --source-dir ./nebula-logger/core/ --test-level RunLocalTests + + # Now that the core directory has been deployed & tests have passed, deploy all of the metadata + - name: 'Deploy All Source to Scratch Org' run: npx sf project deploy start --source-dir ./nebula-logger/ - name: 'Assign Logger Admin Permission Set' @@ -188,7 +260,7 @@ jobs: - name: 'Run Apex Tests Synchronously' run: npm run test:apex:nocoverage -- --synchronous - - name: 'Delete Base Scratch Org' + - name: 'Delete Scratch Org' run: npx sf org delete scratch --no-prompt if: ${{ always() }} @@ -228,9 +300,15 @@ jobs: DEV_HUB_JWT_SERVER_KEY: ${{ secrets.DEV_HUB_JWT_SERVER_KEY }} - name: 'Create Scratch Org' - run: npx sf org create scratch --no-namespace --duration-days 1 --definition-file ./config/scratch-orgs/build-event-monitoring-scratch-def.json --wait 20 --set-default --json + run: npx sf org create scratch --no-namespace --no-track-source --duration-days 1 --definition-file ./config/scratch-orgs/event-monitoring-scratch-def.json --wait 20 --set-default --json - - name: 'Deploy Source to Scratch Org' + # To ensure that all of the Apex classes in the core directory have 75+ code coverage, + # deploy only the core directory & run all of its tests as part of the deployment, using `--test-level RunLocalTests` + - name: 'Validate Core Source in Scratch Org' + run: npx sf project deploy validate --source-dir ./nebula-logger/core/ --test-level RunLocalTests + + # Now that the core directory has been deployed & tests have passed, deploy all of the metadata + - name: 'Deploy All Source to Scratch Org' run: npx sf project deploy start --source-dir ./nebula-logger/ - name: 'Assign Logger Admin Permission Set' @@ -250,14 +328,14 @@ jobs: - name: 'Run Apex Tests Synchronously' run: npm run test:apex:nocoverage -- --synchronous - - name: 'Delete Base Scratch Org' + - name: 'Delete Scratch Org' run: npx sf org delete scratch --no-prompt if: ${{ always() }} experience-cloud-scratch-org-tests: environment: 'Experience Cloud Scratch Org' name: 'Run Experience Cloud Scratch Org Tests' - needs: [code-quality-tests, base-scratch-org-tests] + needs: [code-quality-tests, advanced-scratch-org-tests] runs-on: ubuntu-latest steps: - name: 'Checkout source code' @@ -290,13 +368,19 @@ jobs: DEV_HUB_JWT_SERVER_KEY: ${{ secrets.DEV_HUB_JWT_SERVER_KEY }} - name: 'Create Scratch Org' - run: npx sf org create scratch --no-namespace --duration-days 1 --definition-file ./config/scratch-orgs/build-experience-cloud-scratch-def.json --wait 20 --set-default --json + run: npx sf org create scratch --no-namespace --no-track-source --duration-days 1 --definition-file ./config/scratch-orgs/experience-cloud-scratch-def.json --wait 20 --set-default --json - - name: 'Deploy Source to Scratch Org' + # To ensure that all of the Apex classes in the core directory have 75+ code coverage, + # deploy only the core directory & run all of its tests as part of the deployment, using `--test-level RunLocalTests` + - name: 'Validate Core Source in Scratch Org' + run: npx sf project deploy validate --source-dir ./nebula-logger/core/ --test-level RunLocalTests + + # Now that the core directory has been deployed & tests have passed, deploy all of the metadata + - name: 'Deploy All Source to Scratch Org' run: npx sf project deploy start --source-dir ./nebula-logger/ - name: 'Deploy Test Experience Sites Metadata' - run: npm run experience:deploy + run: npx sf project deploy start --source-dir ./config/scratch-orgs/experience-cloud/ - name: 'Assign Logger Admin Permission Set' run: npm run permset:assign:admin @@ -313,17 +397,84 @@ jobs: run: npm run test:apex:nocoverage - name: 'Run Apex Tests Synchronously' - run: npm run test:apex -- --synchronous + run: npm run test:apex:nocoverage -- --synchronous - # This is the only scratch org that's used for uploading code coverage - - name: 'Upload Apex test code coverage to Codecov.io' - uses: codecov/codecov-action@v4 + - name: 'Delete Scratch Org' + run: npx sf org delete scratch --no-prompt + if: ${{ always() }} + + omnistudio-scratch-org-tests: + environment: 'OmniStudio Scratch Org' + name: 'Run OmniStudio Scratch Org Tests' + needs: [code-quality-tests, base-scratch-org-tests] + runs-on: ubuntu-latest + steps: + - name: 'Checkout source code' + uses: actions/checkout@v4 + + - name: 'Restore node_modules cache' + id: cache-npm + uses: actions/cache@v4 with: - fail_ci_if_error: true - flags: Apex - token: ${{ secrets.CODECOV_TOKEN }} + path: node_modules + key: npm-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + npm-${{ env.cache-name }}- + npm- + + - name: 'Install npm dependencies' + if: steps.cache-npm.outputs.cache-hit != 'true' + run: npm ci - - name: 'Delete Experience Cloud Scratch Org' + - name: 'Authorize Dev Hub' + shell: bash + run: | + npx sf version + echo "${{ env.DEV_HUB_JWT_SERVER_KEY }}" > ./jwt-server.key + npx sf org login jwt --instance-url ${{ env.DEV_HUB_AUTH_URL }} --client-id ${{ env.DEV_HUB_CONSUMER_KEY }} --username ${{ env.DEV_HUB_BOT_USERNAME }} --jwt-key-file ./jwt-server.key --set-default-dev-hub + env: + 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 Scratch Org' + run: npx sf org create scratch --no-namespace --no-track-source --duration-days 1 --definition-file ./config/scratch-orgs/omnistudio-scratch-def.json --wait 20 --set-default --json + + # https://help.salesforce.com/s/articleView?id=000394906&type=1 + - name: 'Install OmniStudio managed package v250.7.1 (Summer ‘24 release)' + run: npx sf package install --package 04t4W000002Z6oC --security-type AdminsOnly --wait 30 --no-prompt + + # To ensure that all of the Apex classes in the core directory have 75+ code coverage, + # deploy only the core directory & run all of its tests as part of the deployment, using `--test-level RunLocalTests` + - name: 'Validate Core Source in Scratch Org' + run: npx sf project deploy validate --source-dir ./nebula-logger/core/ --test-level RunLocalTests + + # Now that the core directory has been deployed & tests have passed, deploy all of the metadata + - name: 'Deploy All Source to Scratch Org' + run: npx sf project deploy start --source-dir ./nebula-logger/ + + - name: 'Deploy Test OmniStudio Metadata' + run: npx sf project deploy start --source-dir ./config/scratch-orgs/omnistudio/ + + - name: 'Assign Logger Admin Permission Set' + run: npm run permset:assign:admin + + - name: 'Validate Custom Metadata Records' + run: npx sf apex run --file ./scripts/build/validate-custom-metadata-records.apex + + # Nebula Logger has functionality that queries the AuthSession object when the current user has an active session. + # The code should work with or without an active session, so the pipeline runs the tests twice - asynchronously and synchronously. + # This is done because, based on how you execute Apex tests, the running user may have an active session (synchrously) or not (asynchronously). + # Utlimately, this could/should probably be better mocked during tests, but the AuthSession is read-only in Apex, so it's a bit difficult to work with. + # Running the Apex tests sync & async serves as an extra level of integration testing to ensure that everything works with or without an active session. + - name: 'Run Apex Tests Asynchronously' + run: npm run test:apex:nocoverage + + - name: 'Run Apex Tests Synchronously' + run: npm run test:apex:nocoverage -- --synchronous + + - name: 'Delete Base Scratch Org' run: npx sf org delete scratch --no-prompt if: ${{ always() }} @@ -363,9 +514,15 @@ jobs: DEV_HUB_JWT_SERVER_KEY: ${{ secrets.DEV_HUB_JWT_SERVER_KEY }} - name: 'Create Scratch Org' - run: npx sf org create scratch --no-namespace --duration-days 1 --definition-file ./config/scratch-orgs/build-platform-cache-scratch-def.json --wait 20 --set-default --json + run: npx sf org create scratch --no-namespace --no-track-source --duration-days 1 --definition-file ./config/scratch-orgs/platform-cache-scratch-def.json --wait 20 --set-default --json + + # To ensure that all of the Apex classes in the core directory have 75+ code coverage, + # deploy only the core directory & run all of its tests as part of the deployment, using `--test-level RunLocalTests` + - name: 'Validate Core Source in Scratch Org' + run: npx sf project deploy validate --source-dir ./nebula-logger/core/ --test-level RunLocalTests - - name: 'Deploy Source to Scratch Org' + # Now that the core directory has been deployed & tests have passed, deploy all of the metadata + - name: 'Deploy All Source to Scratch Org' run: npx sf project deploy start --source-dir ./nebula-logger/ - name: 'Assign Logger Admin Permission Set' @@ -385,13 +542,23 @@ jobs: - name: 'Run Apex Tests Synchronously' run: npm run test:apex:nocoverage -- --synchronous - - name: 'Delete Base Scratch Org' + - name: 'Delete Scratch Org' run: npx sf org delete scratch --no-prompt if: ${{ always() }} create-managed-package-beta: name: 'Create Managed Package Beta' - needs: [lwc-tests, base-scratch-org-tests, event-monitoring-scratch-org-tests, experience-cloud-scratch-org-tests, platform-cache-scratch-org-tests] + needs: + [ + code-quality-tests, + lwc-jest-tests, + advanced-scratch-org-tests, + base-scratch-org-tests, + event-monitoring-scratch-org-tests, + experience-cloud-scratch-org-tests, + omnistudio-scratch-org-tests, + platform-cache-scratch-org-tests + ] if: ${{ github.ref != 'refs/heads/main' }} runs-on: ubuntu-latest environment: 'Demo Org' @@ -491,6 +658,7 @@ jobs: run: npm run test:apex:nocoverage -- --targetusername nebula-logger-package-demo --synchronous - name: 'Commit New Package Version' + if: ${{ github.event_name == 'pull_request' }} run: | git config --local user.email "action@github.com" git config --local user.name "GitHub Action Bot" @@ -504,7 +672,17 @@ jobs: promote-package-versions: name: 'Promote Package Versions' - needs: [lwc-tests, base-scratch-org-tests, event-monitoring-scratch-org-tests, experience-cloud-scratch-org-tests, platform-cache-scratch-org-tests] + needs: + [ + code-quality-tests, + lwc-jest-tests, + advanced-scratch-org-tests, + base-scratch-org-tests, + event-monitoring-scratch-org-tests, + experience-cloud-scratch-org-tests, + omnistudio-scratch-org-tests, + platform-cache-scratch-org-tests + ] if: ${{ github.ref == 'refs/heads/main' }} runs-on: ubuntu-latest steps: diff --git a/.mob b/.mob deleted file mode 100644 index 3f0757eff..000000000 --- a/.mob +++ /dev/null @@ -1,11 +0,0 @@ -# Config file for using the mob CLI (https://mob.sh) with Nebula Logger - -# General mob CLI settings -MOB_TIMER="20" -MOB_TIMER_ROOM="" -MOB_TIMER_URL="" - -# Branch & commit settings -MOB_START_COMMIT_MESSAGE="[WIP] started" -MOB_WIP_BRANCH_PREFIX="wip/" -MOB_WIP_COMMIT_MESSAGE="[WIP] checkpoint" diff --git a/README.md b/README.md index 5f9170d0d..06897fbec 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,15 @@ The most robust observability solution for Salesforce experts. Built 100% natively on the platform, and designed to work seamlessly with Apex, Lightning Components, Flow, Process Builder & integrations. -## Unlocked Package - v4.14.9 +## Unlocked Package - v4.14.10 -[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oSQQAY) -[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oSQQAY) -[![View Documentation](./images/btn-view-documentation.png)](https://jongpie.github.io/NebulaLogger/) +[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oTdQAI) +[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oTdQAI) +[![View Documentation](./images/btn-view-documentation.png)](https://github.com/jongpie/NebulaLogger/wiki) -`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y0000015oSQQAY` +`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y0000015oTdQAI` -`sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y0000015oSQQAY` +`sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y0000015oTdQAI` --- @@ -29,18 +29,39 @@ The most robust observability solution for Salesforce experts. Built 100% native --- +> [!NOTE] +> Starting in September 2024, Nebula Logger's documentation is being rewritten & consolidated into [the wiki](https://github.com/jongpie/NebulaLogger/wiki). Most of the content show below will eventually be migrated to the wiki instead. + ## Features -1. Easily add log entries via Apex, Lightning Components (lightning web components (LWCs) & aura components), Flow & Process Builder to generate 1 consolidated, unified log -2. Manage & report on logging data using the `Log__c` and `LogEntry__c` objects -3. Leverage `LogEntryEvent__e` platform events for real-time monitoring & integrations -4. Enable logging and set the logging level for different users & profiles using `LoggerSettings__c` custom hierarchy setting - - In addition to the required fields on this Custom Setting record, `LoggerSettings__c` ships with `SystemLogMessageFormat__c`, which uses Handlebars-esque syntax to refer to fields on the `LogEntryEvent__e` Platform Event. You can use curly braces to denote merge field logic, eg: `{OriginLocation__c}\n{Message__c}` - this will output the contents of `LogEntryEvent__e.OriginLocation__c`, a line break, and then the contents of `LogEntryEvent__e.Message__c` -5. Automatically mask sensitive data by configuring `LogEntryDataMaskRule__mdt` custom metadata rules -6. View related log entries on any Lightning SObject flexipage by adding the 'Related Log Entries' component in App Builder -7. Dynamically assign tags to `Log__c` and `LogEntry__c` records for tagging/labeling your logs -8. Plugin framework: easily build or install plugins that enhance the `Log__c` and `LogEntry__c` objects, using Apex or Flow (not currently available in the managed package) -9. Event-Driven Integrations with [Platform Events](https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_events_intro.htm), an event-driven messaging architecture. External integrations can subscribe to log events using the `LogEntryEvent__e` object - see more details at [the Platform Events Developer Guide site](https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_events_subscribe_cometd.htm) +1. A unified logging tool that supports easily adding log entries across the Salesforce platform, using: + + - [Apex](https://github.com/jongpie/NebulaLogger/wiki/Logging-in-Apex): classes, triggers, and anonymous Apex scripts + - [Lightning Components](https://github.com/jongpie/NebulaLogger/wiki/Logging-in-Components): lightning web components (LWCs) & aura components + - [Flow & Process Builder](https://github.com/jongpie/NebulaLogger/wiki/Logging-in-Flow): any Flow type that supports invocable actions + - [OmniStudio](https://github.com/jongpie/NebulaLogger/wiki/Logging-in-OmniStudio): omniscripts and omni integration procedures + +2. Built with an event-driven pub/sub messaging architecture, using `LogEntryEvent__e` [platform events](https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_events_intro.htm). For more details on leveraging platform events, see [the Platform Events Developer Guide site](https://developer.salesforce.com/docs/atlas.en-us.platform_events.meta/platform_events/platform_events_subscribe_cometd.htm) + +3. Actionable observability data about your Salesforce org, available directly in your Salesforce org via the 5 included custom objects + + - `Log__c` + - `LogEntry__c` + - `LogEntryTag__c` + - `LoggerTag__c` + - `LoggerScenario__c` + +4. Customizable logging settings for different users & profiles, using the included `LoggerSettings__c` custom hierarchy settings object +5. Easily scales in highly complex Salesforce orgs with large data volumes, using global feature flags in `LoggerParameter__mdt` +6. Automatic data masking of sensitive data, using rules configured in the `LogEntryDataMaskRule__mdt` custom metadata type object +7. View related `LogEntry__c` records on any Lightning record page in App Builder by adding the 'Related Log Entries' component (`relatedLogEntries` LWC) +8. Dynamically assign tags to `Log__c` and `LogEntry__c` records for tagging/labeling your logs +9. Extendable with a built-in plugin framework: easily build or install plugins that enhance Nebula Logger, using Apex or Flow (not currently available in the managed package) +10. ISVs & package developers have several options for leveraging Nebula Logger in your own packages + + - **Optional Dependency**: dynamically leverage Nebula Logger in your own packages - when it's available in a subscriber's org - using [Apex's `Callable` interface](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_interface_System_Callable.htm) and Nebula Logger's included implementation `CallableLogger` (requires `v4.14.10` of Nebula Logger or newer) + - **Hard Dependency**: add either Nebula Logger's unlocked (no namespace) package or its managed package (`Nebula` namespace) as a dependency for your package to ensure customers always have a version of Nebula Logger installed + - **No Dependency**: Bundle Nebula Logger's metadata into your own project - all of Nebula Logger's metadata is fully open source & freely available. This approach provides with full control of what's included in your own app/project. Learn more about the design and history of the project on [Joys Of Apex blog post](https://www.joysofapex.com/advanced-logging-using-nebula-logger/) @@ -111,8 +132,7 @@ After installing Nebula Logger in your org, there are a few additional configura For Apex developers, the `Logger` class has several methods that can be used to add entries with different logging levels. Each logging level's method has several overloads to support multiple parameters. -```java -// This will generate a debug statement within developer console +```apex// This will generate a debug statement within developer console System.debug('Debug statement using native Apex'); // This will create a new `Log__c` record with multiple related `LogEntry__c` records @@ -183,25 +203,9 @@ This results in a `Log__c` record with related `LogEntry__c` records. --- -### All Together: Apex, Lightning Components & Flow in One Log - -After incorporating Logger into your Flows & Apex code (including controllers, trigger framework, etc.), you'll have a unified transaction log of all your declarative & custom code automations. - -```java -Case currentCase = [SELECT Id, CaseNumber, Type, Status, IsClosed FROM Case LIMIT 1]; - -Logger.info('First, log the case through Apex', currentCase); - -Logger.debug('Now, we update the case in Apex to cause our record-triggered Flow to run'); -update currentCase; - -Logger.info('Last, save our log'); -Logger.saveLog(); -``` - -This generates 1 consolidated `Log__c`, containing `LogEntry__c` records from both Apex and Flow +### Logger for OmniStudio: Quick Start -![Flow Log Results](./images/combined-apex-flow-log.png) +For OmniStudio builders, the included Apex class `CallableLogger` provides access to Nebula Logger's core features, directly in omniscripts and omni integration procedures. Simply use the `CallableLogger` class as a remote action within OmniStudio, and provide any inputs needed for the logging action. For more details (including what actions are available, and their required inputs), see [the section on the `CallableLogger` Apex class](https://github.com/jongpie/NebulaLogger/wiki/Dynamically-Call-Logger). For more details on logging in OmniStudio, [see the OmniStudio wiki page](https://github.com/jongpie/NebulaLogger/wiki/Logging-in-OmniStudio) --- @@ -232,8 +236,7 @@ This example batchable class shows how you can leverage this feature to relate a > :information_source: If you deploy this example class to your org,you can run it using `Database.executeBatch(new BatchableLoggerExample());` -```java -public with sharing class BatchableLoggerExample implements Database.Batchable, Database.Stateful { +```apexpublic with sharing class BatchableLoggerExample implements Database.Batchable, Database.Stateful { private String originalTransactionId; public Database.QueryLocator start(Database.BatchableContext batchableContext) { @@ -276,8 +279,7 @@ Queueable jobs can also leverage the parent transaction ID to relate logs togeth > :information_source: If you deploy this example class to your org,you can run it using `System.enqueueJob(new QueueableLoggerExample(3));` -```java -public with sharing class QueueableLoggerExample implements Queueable { +```apexpublic with sharing class QueueableLoggerExample implements Queueable { private Integer numberOfJobsToChain; private String parentLogTransactionId; @@ -332,8 +334,7 @@ To see the full list of overloads, check out the `Logger` class [documentation]( Each of the logging methods in `Logger` returns an instance of the class `LogEntryEventBuilder`. This class provides several additional methods together to further customize each log entry - each of the builder methods can be chained together. In this example Apex, 3 log entries are created using different approaches for calling `Logger` - all 3 approaches result in identical log entries. -```java -// Get the current user so we can log it (just as an example of logging an SObject) +```apex// Get the current user so we can log it (just as an example of logging an SObject) User currentUser = [SELECT Id, Name, Username, Email FROM User WHERE Id = :UserInfo.getUserId()]; // Using static Logger method overloads @@ -356,8 +357,7 @@ The class `LogMessage` provides the ability to generate string messages on deman 1. Improved CPU usage by skipping unnecessary calls to `String.format()` - ```java - // Without using LogMessage, String.format() is always called, even if the FINE logging level is not enabled for a user + ```apex // Without using LogMessage, String.format() is always called, even if the FINE logging level is not enabled for a user String formattedString = String.format('my example with input: {0}', List{'myString'}); Logger.fine(formattedString); @@ -367,8 +367,7 @@ The class `LogMessage` provides the ability to generate string messages on deman ``` 2. Easily build complex strings - ```java - // There are several constructors for LogMessage to support different numbers of parameters for the formatted string + ```apex // There are several constructors for LogMessage to support different numbers of parameters for the formatted string String unformattedMessage = 'my string with 3 inputs: {0} and then {1} and finally {2}'; String formattedMessage = new LogMessage(unformattedMessage, 'something', 'something else', 'one more').getMessage(); String expectedMessage = 'my string with 3 inputs: something and then something else and finally one more'; @@ -377,6 +376,46 @@ The class `LogMessage` provides the ability to generate string messages on deman For more details, check out the `LogMessage` class [documentation](https://jongpie.github.io/NebulaLogger/apex/Logger-Engine/LogMessage). +### ISVs & Package Developers: Dynamically Call Nebula Logger in Your Packages with `CallableLogger` + +As of `v4.14.10`, Nebula Logger includes the Apex class `CallableLogger`, which implements [Apex's `Callable` interface](https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_interface_System_Callable.htm). + +- The `Callable` interface only has 1 method: `Object call(String action, Map args)`. It leverages string values and generic `Object` values as a mechanism to provide loose coupling on Apex classes that may or may not exist in a Salesforce org. +- This can be used by ISVs & package developers to optionally leverage Nebula Logger for logging, when it's available in a customer's org. And when it's not available, your package can still be installed, and still be used. + +Using the provided `CallableLogger` class, a subset of Nebula Logger's features can be called dynamically in Apex. For example, this sample Apex code adds & saves 2 log entries (when Nebula Logger is available). + +```apex +// Check for both the managed package (Nebula namespace) and the unlocked package to see if either is available +Type nebulaLoggerType = Type.forName('Nebula', 'CallableLogger') ?? Type.forName('CallableLogger'); +Callable nebulaLoggerInstance = (Callable) nebulaLoggerType?.newInstance(); +if (nebulaLoggerInstance == null) { + // If it's null, then neither of Nebula Logger's packages is available in the org 🥲 + return; +} + +// Example: Add a basic "hello, world!" INFO extry +Map newEntryInput = new Map{ + 'loggingLevel' => System.LoggingLevel.INFO, + 'message' => 'hello, world!' +}; +nebulaLoggerInstance.call('newEntry', newEntryInput); + +// Example: Add an ERROR extry with an Apex exception +Exception someException = new DmlException('oops'); +Map newEntryInput = new Map{ + 'exception' => someException, + 'loggingLevel' => LoggingLevel.ERROR, + 'message' => 'An unexpected exception was thrown' +}; +nebulaLoggerInstance.call('newEntry', newEntryInput); + +// Example: Save any pending log entries +nebulaLoggerInstance.call('saveLog', null); +``` + +For more details, [visit the wiki](https://github.com/jongpie/NebulaLogger/wiki/Dynamically-Call-Nebula-Logger). + --- ## Features for Lightning Component Developers @@ -491,8 +530,7 @@ Nebula Logger supports dynamically tagging/labeling your `LogEntry__c` records v Apex developers can use 2 new methods in `LogEntryBuilder` to add tags - `LogEntryEventBuilder.addTag(String)` and `LogEntryEventBuilder.addTags(List)`. -```java -// Use addTag(String tagName) for adding 1 tag at a time +```apex// Use addTag(String tagName) for adding 1 tag at a time Logger.debug('my log message').addTag('some tag').addTag('another tag'); // Use addTags(List tagNames) for adding a list of tags in 1 method call @@ -609,8 +647,7 @@ The first step is to add a field to the platform event `LogEntryEvent__e` - In Apex, populate your field(s) by calling the instance method overloads `LogEntryEventBuilder.setField(Schema.SObjectField field, Object fieldValue)` or `LogEntryEventBuilder.setField(Map fieldToValue)` - ```apex - Logger.info('hello, world') + ```apex Logger.info('hello, world') // Set a single field .setField(LogEntryEvent__e.SomeCustomTextField__c, 'some text value') // Set multiple fields @@ -752,8 +789,7 @@ If you want to add your own automation to the `Log__c` or `LogEntry__c` objects, - Apex plugins: your Apex class should extend the abstract class `LoggerSObjectHandlerPlugin`. For example: - ```java - public class ExamplePlugin extends LoggerSObjectHandlerPlugin { + ```apex public class ExamplePlugin extends LoggerSObjectHandlerPlugin { public override void execute( TriggerOperation triggerOperationType, List triggerNew, diff --git a/config/linters/lint-staged.config.js b/config/linters/lint-staged.config.js index 39aa56714..f3da5a360 100644 --- a/config/linters/lint-staged.config.js +++ b/config/linters/lint-staged.config.js @@ -2,7 +2,7 @@ module.exports = { 'sfdx-project.json': () => { return `npm run package:version:number:fix`; }, - '*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}': filenames => filenames.map(filename => `prettier --write '${filename}'`), + '*.{apex,cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}': filenames => filenames.map(filename => `prettier --write '${filename}'`), '**/lwc/**': filenames => { return [`eslint --config ./config/linters/.eslintrc.json ${filenames.join(' ')} --fix`]; // FIXME this command should only run tests for the changed LWCs (instead of running tests for all LWCs) diff --git a/config/scratch-orgs/dev-scratch-def.json b/config/scratch-orgs/advanced-scratch-def.json similarity index 70% rename from config/scratch-orgs/dev-scratch-def.json rename to config/scratch-orgs/advanced-scratch-def.json index 82bf39c78..2a7f4818a 100644 --- a/config/scratch-orgs/dev-scratch-def.json +++ b/config/scratch-orgs/advanced-scratch-def.json @@ -1,15 +1,14 @@ { - "orgName": "Nebula Logger - Dev Scratch Org", + "orgName": "Nebula Logger - Advanced Scratch Org", "edition": "Enterprise", "hasSampleData": true, "country": "US", "language": "en_US", - "features": ["EinsteinGPTPlatform", "EventLogFile", "PlatformCache"], + "features": ["Communities", "EinsteinGPTPlatform", "EventLogFile", "OmniStudioDesigner", "OmniStudioMetadata", "OmniStudioRuntime", "PlatformCache"], "settings": { "apexSettings": { "enableAuraApexCtrlAuthUserAccessCheckPref": true, "enableAuraApexCtrlGuestUserAccessCheckPref": true, - "enableCompileOnDeploy": true, "enableRestrictCommunityExecAnon": true, "enableSecureNoArgConstructorPref": true }, @@ -19,12 +18,18 @@ "communitiesSettings": { "enableNetworksEnabled": true }, + "emailAdministrationSettings": { + "enableEnhancedEmailEnabled": true + }, "eventSettings": { "enableDeleteMonitoringData": true, "enableEventLogGeneration": true, "enableLightningLoggerEvents": true, "eventLogRetentionDuration": 30 }, + "experienceBundleSettings": { + "enableExperienceBundleMetadata": true + }, "flowSettings": { "canDebugFlowAsAnotherUser": true }, diff --git a/config/scratch-orgs/build-base-scratch-def.json b/config/scratch-orgs/base-scratch-def.json similarity index 78% rename from config/scratch-orgs/build-base-scratch-def.json rename to config/scratch-orgs/base-scratch-def.json index 2af80e017..37070494f 100644 --- a/config/scratch-orgs/build-base-scratch-def.json +++ b/config/scratch-orgs/base-scratch-def.json @@ -1,5 +1,5 @@ { - "orgName": "Nebula Logger - Build - Base Scratch Org", + "orgName": "Nebula Logger - Base Scratch Org", "edition": "Enterprise", "hasSampleData": true, "country": "US", @@ -12,6 +12,9 @@ "communitiesSettings": { "enableNetworksEnabled": false }, + "emailAdministrationSettings": { + "enableEnhancedEmailEnabled": false + }, "eventSettings": { "enableDeleteMonitoringData": false, "enableEventLogGeneration": false, diff --git a/config/scratch-orgs/build-event-monitoring-scratch-def.json b/config/scratch-orgs/event-monitoring-scratch-def.json similarity index 78% rename from config/scratch-orgs/build-event-monitoring-scratch-def.json rename to config/scratch-orgs/event-monitoring-scratch-def.json index 7c1cca417..dcc482ab9 100644 --- a/config/scratch-orgs/build-event-monitoring-scratch-def.json +++ b/config/scratch-orgs/event-monitoring-scratch-def.json @@ -1,5 +1,5 @@ { - "orgName": "Nebula Logger - Build - Event Monitoring Scratch Org", + "orgName": "Nebula Logger - Event Monitoring Scratch Org", "edition": "Enterprise", "hasSampleData": true, "country": "AU", @@ -12,6 +12,9 @@ "communitiesSettings": { "enableNetworksEnabled": false }, + "emailAdministrationSettings": { + "enableEnhancedEmailEnabled": false + }, "eventSettings": { "enableDeleteMonitoringData": true, "enableEventLogGeneration": true, diff --git a/config/scratch-orgs/build-experience-cloud-scratch-def.json b/config/scratch-orgs/experience-cloud-scratch-def.json similarity index 80% rename from config/scratch-orgs/build-experience-cloud-scratch-def.json rename to config/scratch-orgs/experience-cloud-scratch-def.json index 21f7e50f8..0c5093736 100644 --- a/config/scratch-orgs/build-experience-cloud-scratch-def.json +++ b/config/scratch-orgs/experience-cloud-scratch-def.json @@ -1,5 +1,5 @@ { - "orgName": "Nebula Logger - Build - Experience Cloud Scratch Org", + "orgName": "Nebula Logger - Experience Cloud Scratch Org", "edition": "Enterprise", "hasSampleData": true, "country": "ES", @@ -12,6 +12,9 @@ "communitiesSettings": { "enableNetworksEnabled": true }, + "emailAdministrationSettings": { + "enableEnhancedEmailEnabled": false + }, "eventSettings": { "enableDeleteMonitoringData": false, "enableEventLogGeneration": false, diff --git a/config/experience-cloud/classes/ChangePasswordController.cls b/config/scratch-orgs/experience-cloud/classes/ChangePasswordController.cls similarity index 100% rename from config/experience-cloud/classes/ChangePasswordController.cls rename to config/scratch-orgs/experience-cloud/classes/ChangePasswordController.cls diff --git a/config/experience-cloud/classes/ChangePasswordController.cls-meta.xml b/config/scratch-orgs/experience-cloud/classes/ChangePasswordController.cls-meta.xml similarity index 100% rename from config/experience-cloud/classes/ChangePasswordController.cls-meta.xml rename to config/scratch-orgs/experience-cloud/classes/ChangePasswordController.cls-meta.xml diff --git a/config/experience-cloud/classes/ChangePasswordControllerTest.cls b/config/scratch-orgs/experience-cloud/classes/ChangePasswordControllerTest.cls similarity index 100% rename from config/experience-cloud/classes/ChangePasswordControllerTest.cls rename to config/scratch-orgs/experience-cloud/classes/ChangePasswordControllerTest.cls diff --git a/config/experience-cloud/classes/ChangePasswordControllerTest.cls-meta.xml b/config/scratch-orgs/experience-cloud/classes/ChangePasswordControllerTest.cls-meta.xml similarity index 100% rename from config/experience-cloud/classes/ChangePasswordControllerTest.cls-meta.xml rename to config/scratch-orgs/experience-cloud/classes/ChangePasswordControllerTest.cls-meta.xml diff --git a/config/experience-cloud/classes/ForgotPasswordController.cls b/config/scratch-orgs/experience-cloud/classes/ForgotPasswordController.cls similarity index 100% rename from config/experience-cloud/classes/ForgotPasswordController.cls rename to config/scratch-orgs/experience-cloud/classes/ForgotPasswordController.cls diff --git a/config/experience-cloud/classes/ForgotPasswordController.cls-meta.xml b/config/scratch-orgs/experience-cloud/classes/ForgotPasswordController.cls-meta.xml similarity index 100% rename from config/experience-cloud/classes/ForgotPasswordController.cls-meta.xml rename to config/scratch-orgs/experience-cloud/classes/ForgotPasswordController.cls-meta.xml diff --git a/config/experience-cloud/classes/ForgotPasswordControllerTest.cls b/config/scratch-orgs/experience-cloud/classes/ForgotPasswordControllerTest.cls similarity index 100% rename from config/experience-cloud/classes/ForgotPasswordControllerTest.cls rename to config/scratch-orgs/experience-cloud/classes/ForgotPasswordControllerTest.cls diff --git a/config/experience-cloud/classes/ForgotPasswordControllerTest.cls-meta.xml b/config/scratch-orgs/experience-cloud/classes/ForgotPasswordControllerTest.cls-meta.xml similarity index 100% rename from config/experience-cloud/classes/ForgotPasswordControllerTest.cls-meta.xml rename to config/scratch-orgs/experience-cloud/classes/ForgotPasswordControllerTest.cls-meta.xml diff --git a/config/experience-cloud/classes/MicrobatchSelfRegController.cls b/config/scratch-orgs/experience-cloud/classes/MicrobatchSelfRegController.cls similarity index 100% rename from config/experience-cloud/classes/MicrobatchSelfRegController.cls rename to config/scratch-orgs/experience-cloud/classes/MicrobatchSelfRegController.cls diff --git a/config/experience-cloud/classes/MicrobatchSelfRegController.cls-meta.xml b/config/scratch-orgs/experience-cloud/classes/MicrobatchSelfRegController.cls-meta.xml similarity index 100% rename from config/experience-cloud/classes/MicrobatchSelfRegController.cls-meta.xml rename to config/scratch-orgs/experience-cloud/classes/MicrobatchSelfRegController.cls-meta.xml diff --git a/config/experience-cloud/classes/MicrobatchSelfRegControllerTest.cls b/config/scratch-orgs/experience-cloud/classes/MicrobatchSelfRegControllerTest.cls similarity index 100% rename from config/experience-cloud/classes/MicrobatchSelfRegControllerTest.cls rename to config/scratch-orgs/experience-cloud/classes/MicrobatchSelfRegControllerTest.cls diff --git a/config/experience-cloud/classes/MicrobatchSelfRegControllerTest.cls-meta.xml b/config/scratch-orgs/experience-cloud/classes/MicrobatchSelfRegControllerTest.cls-meta.xml similarity index 100% rename from config/experience-cloud/classes/MicrobatchSelfRegControllerTest.cls-meta.xml rename to config/scratch-orgs/experience-cloud/classes/MicrobatchSelfRegControllerTest.cls-meta.xml diff --git a/config/experience-cloud/classes/MyProfilePageController.cls b/config/scratch-orgs/experience-cloud/classes/MyProfilePageController.cls similarity index 100% rename from config/experience-cloud/classes/MyProfilePageController.cls rename to config/scratch-orgs/experience-cloud/classes/MyProfilePageController.cls diff --git a/config/experience-cloud/classes/MyProfilePageController.cls-meta.xml b/config/scratch-orgs/experience-cloud/classes/MyProfilePageController.cls-meta.xml similarity index 100% rename from config/experience-cloud/classes/MyProfilePageController.cls-meta.xml rename to config/scratch-orgs/experience-cloud/classes/MyProfilePageController.cls-meta.xml diff --git a/config/experience-cloud/classes/MyProfilePageControllerTest.cls b/config/scratch-orgs/experience-cloud/classes/MyProfilePageControllerTest.cls similarity index 100% rename from config/experience-cloud/classes/MyProfilePageControllerTest.cls rename to config/scratch-orgs/experience-cloud/classes/MyProfilePageControllerTest.cls diff --git a/config/experience-cloud/classes/MyProfilePageControllerTest.cls-meta.xml b/config/scratch-orgs/experience-cloud/classes/MyProfilePageControllerTest.cls-meta.xml similarity index 100% rename from config/experience-cloud/classes/MyProfilePageControllerTest.cls-meta.xml rename to config/scratch-orgs/experience-cloud/classes/MyProfilePageControllerTest.cls-meta.xml diff --git a/config/experience-cloud/classes/SiteLoginController.cls b/config/scratch-orgs/experience-cloud/classes/SiteLoginController.cls similarity index 100% rename from config/experience-cloud/classes/SiteLoginController.cls rename to config/scratch-orgs/experience-cloud/classes/SiteLoginController.cls diff --git a/config/experience-cloud/classes/SiteLoginController.cls-meta.xml b/config/scratch-orgs/experience-cloud/classes/SiteLoginController.cls-meta.xml similarity index 100% rename from config/experience-cloud/classes/SiteLoginController.cls-meta.xml rename to config/scratch-orgs/experience-cloud/classes/SiteLoginController.cls-meta.xml diff --git a/config/experience-cloud/classes/SiteLoginControllerTest.cls b/config/scratch-orgs/experience-cloud/classes/SiteLoginControllerTest.cls similarity index 100% rename from config/experience-cloud/classes/SiteLoginControllerTest.cls rename to config/scratch-orgs/experience-cloud/classes/SiteLoginControllerTest.cls diff --git a/config/experience-cloud/classes/SiteLoginControllerTest.cls-meta.xml b/config/scratch-orgs/experience-cloud/classes/SiteLoginControllerTest.cls-meta.xml similarity index 100% rename from config/experience-cloud/classes/SiteLoginControllerTest.cls-meta.xml rename to config/scratch-orgs/experience-cloud/classes/SiteLoginControllerTest.cls-meta.xml diff --git a/config/experience-cloud/classes/SiteRegisterController.cls b/config/scratch-orgs/experience-cloud/classes/SiteRegisterController.cls similarity index 100% rename from config/experience-cloud/classes/SiteRegisterController.cls rename to config/scratch-orgs/experience-cloud/classes/SiteRegisterController.cls diff --git a/config/experience-cloud/classes/SiteRegisterController.cls-meta.xml b/config/scratch-orgs/experience-cloud/classes/SiteRegisterController.cls-meta.xml similarity index 100% rename from config/experience-cloud/classes/SiteRegisterController.cls-meta.xml rename to config/scratch-orgs/experience-cloud/classes/SiteRegisterController.cls-meta.xml diff --git a/config/experience-cloud/classes/SiteRegisterControllerTest.cls b/config/scratch-orgs/experience-cloud/classes/SiteRegisterControllerTest.cls similarity index 100% rename from config/experience-cloud/classes/SiteRegisterControllerTest.cls rename to config/scratch-orgs/experience-cloud/classes/SiteRegisterControllerTest.cls diff --git a/config/experience-cloud/classes/SiteRegisterControllerTest.cls-meta.xml b/config/scratch-orgs/experience-cloud/classes/SiteRegisterControllerTest.cls-meta.xml similarity index 100% rename from config/experience-cloud/classes/SiteRegisterControllerTest.cls-meta.xml rename to config/scratch-orgs/experience-cloud/classes/SiteRegisterControllerTest.cls-meta.xml diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1.site-meta.xml b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1.site-meta.xml similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1.site-meta.xml rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1.site-meta.xml diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/brandingSets/buildYourOwn.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/brandingSets/buildYourOwn.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/brandingSets/buildYourOwn.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/brandingSets/buildYourOwn.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/config/languages.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/config/languages.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/config/languages.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/config/languages.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/config/loggerTestAuraSite.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/config/loggerTestAuraSite.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/config/loggerTestAuraSite.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/config/loggerTestAuraSite.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/config/loginAppPage.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/config/loginAppPage.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/config/loginAppPage.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/config/loginAppPage.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/config/mainAppPage.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/config/mainAppPage.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/config/mainAppPage.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/config/mainAppPage.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/config/nativeConfig.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/config/nativeConfig.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/config/nativeConfig.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/config/nativeConfig.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/checkPassword.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/checkPassword.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/checkPassword.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/checkPassword.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/createRecord.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/createRecord.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/createRecord.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/createRecord.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/error.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/error.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/error.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/error.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/forgotPassword.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/forgotPassword.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/forgotPassword.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/forgotPassword.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/home.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/home.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/home.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/home.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/login.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/login.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/login.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/login.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/loginError.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/loginError.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/loginError.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/loginError.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/recordDetail.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/recordDetail.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/recordDetail.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/recordDetail.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/recordList.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/recordList.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/recordList.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/recordList.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/register.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/register.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/register.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/register.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/relatedRecordList.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/relatedRecordList.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/relatedRecordList.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/relatedRecordList.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/search.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/search.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/search.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/search.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/serviceNotAvailable.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/serviceNotAvailable.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/serviceNotAvailable.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/serviceNotAvailable.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/tooManyRequests.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/tooManyRequests.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/tooManyRequests.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/routes/tooManyRequests.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/themes/buildYourOwn.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/themes/buildYourOwn.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/themes/buildYourOwn.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/themes/buildYourOwn.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/checkPassword.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/checkPassword.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/checkPassword.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/checkPassword.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/createRecord.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/createRecord.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/createRecord.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/createRecord.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/error.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/error.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/error.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/error.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/forgotPassword.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/forgotPassword.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/forgotPassword.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/forgotPassword.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/home.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/home.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/home.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/home.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/login.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/login.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/login.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/login.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/loginError.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/loginError.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/loginError.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/loginError.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/recordDetail.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/recordDetail.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/recordDetail.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/recordDetail.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/recordList.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/recordList.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/recordList.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/recordList.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/register.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/register.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/register.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/register.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/relatedRecordList.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/relatedRecordList.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/relatedRecordList.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/relatedRecordList.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/search.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/search.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/search.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/search.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/serviceNotAvailable.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/serviceNotAvailable.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/serviceNotAvailable.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/serviceNotAvailable.json diff --git a/config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/tooManyRequests.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/tooManyRequests.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_Aura_Site1/views/tooManyRequests.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_Aura_Site1/views/tooManyRequests.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1.site-meta.xml b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1.site-meta.xml similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1.site-meta.xml rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1.site-meta.xml diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/brandingSets/buildYourOwnLWR.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/brandingSets/buildYourOwnLWR.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/brandingSets/buildYourOwnLWR.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/brandingSets/buildYourOwnLWR.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/config/languages.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/config/languages.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/config/languages.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/config/languages.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/config/loggerTestLWRSite.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/config/loggerTestLWRSite.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/config/loggerTestLWRSite.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/config/loggerTestLWRSite.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/config/mainAppPage.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/config/mainAppPage.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/config/mainAppPage.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/config/mainAppPage.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/checkPassword.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/checkPassword.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/checkPassword.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/checkPassword.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/error.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/error.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/error.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/error.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/forgotPassword.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/forgotPassword.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/forgotPassword.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/forgotPassword.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/home.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/home.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/home.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/home.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/login.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/login.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/login.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/login.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/register.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/register.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/register.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/register.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/serviceNotAvailable.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/serviceNotAvailable.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/serviceNotAvailable.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/serviceNotAvailable.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/tooManyRequests.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/tooManyRequests.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/tooManyRequests.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/routes/tooManyRequests.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/themes/buildYourOwnLWR.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/themes/buildYourOwnLWR.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/themes/buildYourOwnLWR.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/themes/buildYourOwnLWR.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/views/checkPassword.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/views/checkPassword.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/views/checkPassword.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/views/checkPassword.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/views/error.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/views/error.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/views/error.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/views/error.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/views/forgotPassword.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/views/forgotPassword.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/views/forgotPassword.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/views/forgotPassword.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/views/home.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/views/home.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/views/home.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/views/home.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/views/login.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/views/login.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/views/login.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/views/login.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/views/register.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/views/register.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/views/register.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/views/register.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/views/serviceNotAvailable.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/views/serviceNotAvailable.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/views/serviceNotAvailable.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/views/serviceNotAvailable.json diff --git a/config/experience-cloud/experiences/Logger_Test_LWR_Site1/views/tooManyRequests.json b/config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/views/tooManyRequests.json similarity index 100% rename from config/experience-cloud/experiences/Logger_Test_LWR_Site1/views/tooManyRequests.json rename to config/scratch-orgs/experience-cloud/experiences/Logger_Test_LWR_Site1/views/tooManyRequests.json diff --git a/config/experience-cloud/networks/Logger Test Aura Site.network-meta.xml b/config/scratch-orgs/experience-cloud/networks/Logger Test Aura Site.network-meta.xml similarity index 100% rename from config/experience-cloud/networks/Logger Test Aura Site.network-meta.xml rename to config/scratch-orgs/experience-cloud/networks/Logger Test Aura Site.network-meta.xml diff --git a/config/experience-cloud/networks/Logger Test LWR Site.network-meta.xml b/config/scratch-orgs/experience-cloud/networks/Logger Test LWR Site.network-meta.xml similarity index 100% rename from config/experience-cloud/networks/Logger Test LWR Site.network-meta.xml rename to config/scratch-orgs/experience-cloud/networks/Logger Test LWR Site.network-meta.xml diff --git a/config/experience-cloud/pages/BandwidthExceeded.page b/config/scratch-orgs/experience-cloud/pages/BandwidthExceeded.page similarity index 100% rename from config/experience-cloud/pages/BandwidthExceeded.page rename to config/scratch-orgs/experience-cloud/pages/BandwidthExceeded.page diff --git a/config/experience-cloud/pages/BandwidthExceeded.page-meta.xml b/config/scratch-orgs/experience-cloud/pages/BandwidthExceeded.page-meta.xml similarity index 100% rename from config/experience-cloud/pages/BandwidthExceeded.page-meta.xml rename to config/scratch-orgs/experience-cloud/pages/BandwidthExceeded.page-meta.xml diff --git a/config/experience-cloud/pages/ChangePassword.page b/config/scratch-orgs/experience-cloud/pages/ChangePassword.page similarity index 100% rename from config/experience-cloud/pages/ChangePassword.page rename to config/scratch-orgs/experience-cloud/pages/ChangePassword.page diff --git a/config/experience-cloud/pages/ChangePassword.page-meta.xml b/config/scratch-orgs/experience-cloud/pages/ChangePassword.page-meta.xml similarity index 100% rename from config/experience-cloud/pages/ChangePassword.page-meta.xml rename to config/scratch-orgs/experience-cloud/pages/ChangePassword.page-meta.xml diff --git a/config/experience-cloud/pages/CommunitiesLanding.page b/config/scratch-orgs/experience-cloud/pages/CommunitiesLanding.page similarity index 100% rename from config/experience-cloud/pages/CommunitiesLanding.page rename to config/scratch-orgs/experience-cloud/pages/CommunitiesLanding.page diff --git a/config/experience-cloud/pages/CommunitiesLanding.page-meta.xml b/config/scratch-orgs/experience-cloud/pages/CommunitiesLanding.page-meta.xml similarity index 100% rename from config/experience-cloud/pages/CommunitiesLanding.page-meta.xml rename to config/scratch-orgs/experience-cloud/pages/CommunitiesLanding.page-meta.xml diff --git a/config/experience-cloud/pages/CommunitiesLogin.page b/config/scratch-orgs/experience-cloud/pages/CommunitiesLogin.page similarity index 100% rename from config/experience-cloud/pages/CommunitiesLogin.page rename to config/scratch-orgs/experience-cloud/pages/CommunitiesLogin.page diff --git a/config/experience-cloud/pages/CommunitiesLogin.page-meta.xml b/config/scratch-orgs/experience-cloud/pages/CommunitiesLogin.page-meta.xml similarity index 100% rename from config/experience-cloud/pages/CommunitiesLogin.page-meta.xml rename to config/scratch-orgs/experience-cloud/pages/CommunitiesLogin.page-meta.xml diff --git a/config/experience-cloud/pages/CommunitiesSelfReg.page b/config/scratch-orgs/experience-cloud/pages/CommunitiesSelfReg.page similarity index 100% rename from config/experience-cloud/pages/CommunitiesSelfReg.page rename to config/scratch-orgs/experience-cloud/pages/CommunitiesSelfReg.page diff --git a/config/experience-cloud/pages/CommunitiesSelfReg.page-meta.xml b/config/scratch-orgs/experience-cloud/pages/CommunitiesSelfReg.page-meta.xml similarity index 100% rename from config/experience-cloud/pages/CommunitiesSelfReg.page-meta.xml rename to config/scratch-orgs/experience-cloud/pages/CommunitiesSelfReg.page-meta.xml diff --git a/config/experience-cloud/pages/CommunitiesSelfRegConfirm.page b/config/scratch-orgs/experience-cloud/pages/CommunitiesSelfRegConfirm.page similarity index 100% rename from config/experience-cloud/pages/CommunitiesSelfRegConfirm.page rename to config/scratch-orgs/experience-cloud/pages/CommunitiesSelfRegConfirm.page diff --git a/config/experience-cloud/pages/CommunitiesSelfRegConfirm.page-meta.xml b/config/scratch-orgs/experience-cloud/pages/CommunitiesSelfRegConfirm.page-meta.xml similarity index 100% rename from config/experience-cloud/pages/CommunitiesSelfRegConfirm.page-meta.xml rename to config/scratch-orgs/experience-cloud/pages/CommunitiesSelfRegConfirm.page-meta.xml diff --git a/config/experience-cloud/pages/Exception.page b/config/scratch-orgs/experience-cloud/pages/Exception.page similarity index 100% rename from config/experience-cloud/pages/Exception.page rename to config/scratch-orgs/experience-cloud/pages/Exception.page diff --git a/config/experience-cloud/pages/Exception.page-meta.xml b/config/scratch-orgs/experience-cloud/pages/Exception.page-meta.xml similarity index 100% rename from config/experience-cloud/pages/Exception.page-meta.xml rename to config/scratch-orgs/experience-cloud/pages/Exception.page-meta.xml diff --git a/config/experience-cloud/pages/FileNotFound.page b/config/scratch-orgs/experience-cloud/pages/FileNotFound.page similarity index 100% rename from config/experience-cloud/pages/FileNotFound.page rename to config/scratch-orgs/experience-cloud/pages/FileNotFound.page diff --git a/config/experience-cloud/pages/FileNotFound.page-meta.xml b/config/scratch-orgs/experience-cloud/pages/FileNotFound.page-meta.xml similarity index 100% rename from config/experience-cloud/pages/FileNotFound.page-meta.xml rename to config/scratch-orgs/experience-cloud/pages/FileNotFound.page-meta.xml diff --git a/config/experience-cloud/pages/ForgotPasswordConfirm.page b/config/scratch-orgs/experience-cloud/pages/ForgotPasswordConfirm.page similarity index 100% rename from config/experience-cloud/pages/ForgotPasswordConfirm.page rename to config/scratch-orgs/experience-cloud/pages/ForgotPasswordConfirm.page diff --git a/config/experience-cloud/pages/ForgotPasswordConfirm.page-meta.xml b/config/scratch-orgs/experience-cloud/pages/ForgotPasswordConfirm.page-meta.xml similarity index 100% rename from config/experience-cloud/pages/ForgotPasswordConfirm.page-meta.xml rename to config/scratch-orgs/experience-cloud/pages/ForgotPasswordConfirm.page-meta.xml diff --git a/config/experience-cloud/pages/InMaintenance.page b/config/scratch-orgs/experience-cloud/pages/InMaintenance.page similarity index 100% rename from config/experience-cloud/pages/InMaintenance.page rename to config/scratch-orgs/experience-cloud/pages/InMaintenance.page diff --git a/config/experience-cloud/pages/InMaintenance.page-meta.xml b/config/scratch-orgs/experience-cloud/pages/InMaintenance.page-meta.xml similarity index 100% rename from config/experience-cloud/pages/InMaintenance.page-meta.xml rename to config/scratch-orgs/experience-cloud/pages/InMaintenance.page-meta.xml diff --git a/config/experience-cloud/pages/SiteRegisterConfirm.page b/config/scratch-orgs/experience-cloud/pages/SiteRegisterConfirm.page similarity index 100% rename from config/experience-cloud/pages/SiteRegisterConfirm.page rename to config/scratch-orgs/experience-cloud/pages/SiteRegisterConfirm.page diff --git a/config/experience-cloud/pages/SiteRegisterConfirm.page-meta.xml b/config/scratch-orgs/experience-cloud/pages/SiteRegisterConfirm.page-meta.xml similarity index 100% rename from config/experience-cloud/pages/SiteRegisterConfirm.page-meta.xml rename to config/scratch-orgs/experience-cloud/pages/SiteRegisterConfirm.page-meta.xml diff --git a/config/experience-cloud/profiles/Logger Test Aura Site Guest Profile.profile-meta.xml b/config/scratch-orgs/experience-cloud/profiles/Logger Test Aura Site Guest Profile.profile-meta.xml similarity index 100% rename from config/experience-cloud/profiles/Logger Test Aura Site Guest Profile.profile-meta.xml rename to config/scratch-orgs/experience-cloud/profiles/Logger Test Aura Site Guest Profile.profile-meta.xml diff --git a/config/experience-cloud/profiles/Logger Test LWR Site Guest Profile.profile-meta.xml b/config/scratch-orgs/experience-cloud/profiles/Logger Test LWR Site Guest Profile.profile-meta.xml similarity index 100% rename from config/experience-cloud/profiles/Logger Test LWR Site Guest Profile.profile-meta.xml rename to config/scratch-orgs/experience-cloud/profiles/Logger Test LWR Site Guest Profile.profile-meta.xml diff --git a/config/experience-cloud/profiles/Logger Test Site User Profile.profile-meta.xml b/config/scratch-orgs/experience-cloud/profiles/Logger Test Site User Profile.profile-meta.xml similarity index 100% rename from config/experience-cloud/profiles/Logger Test Site User Profile.profile-meta.xml rename to config/scratch-orgs/experience-cloud/profiles/Logger Test Site User Profile.profile-meta.xml diff --git a/config/experience-cloud/sites/Logger_Test_Aura_Site.site-meta.xml b/config/scratch-orgs/experience-cloud/sites/Logger_Test_Aura_Site.site-meta.xml similarity index 100% rename from config/experience-cloud/sites/Logger_Test_Aura_Site.site-meta.xml rename to config/scratch-orgs/experience-cloud/sites/Logger_Test_Aura_Site.site-meta.xml diff --git a/config/experience-cloud/sites/Logger_Test_LWR_Site.site-meta.xml b/config/scratch-orgs/experience-cloud/sites/Logger_Test_LWR_Site.site-meta.xml similarity index 100% rename from config/experience-cloud/sites/Logger_Test_LWR_Site.site-meta.xml rename to config/scratch-orgs/experience-cloud/sites/Logger_Test_LWR_Site.site-meta.xml diff --git a/config/scratch-orgs/omnistudio-scratch-def.json b/config/scratch-orgs/omnistudio-scratch-def.json new file mode 100644 index 000000000..00a3b539a --- /dev/null +++ b/config/scratch-orgs/omnistudio-scratch-def.json @@ -0,0 +1,27 @@ +{ + "orgName": "Nebula Logger - OmniStudio Scratch Org", + "edition": "Enterprise", + "hasSampleData": true, + "country": "US", + "language": "en_US", + "features": ["OmniStudioDesigner", "OmniStudioMetadata", "OmniStudioRuntime"], + "settings": { + "chatterSettings": { + "enableChatter": false + }, + "communitiesSettings": { + "enableNetworksEnabled": false + }, + "emailAdministrationSettings": { + "enableEnhancedEmailEnabled": true + }, + "eventSettings": { + "enableDeleteMonitoringData": false, + "enableEventLogGeneration": false, + "eventLogRetentionDuration": 0 + }, + "pathAssistantSettings": { + "pathAssistantEnabled": false + } + } +} diff --git a/config/scratch-orgs/omnistudio/omniIntegrationProcedures/demo_Logging_Procedure_1.oip-meta.xml b/config/scratch-orgs/omnistudio/omniIntegrationProcedures/demo_Logging_Procedure_1.oip-meta.xml new file mode 100644 index 000000000..9b19f830d --- /dev/null +++ b/config/scratch-orgs/omnistudio/omniIntegrationProcedures/demo_Logging_Procedure_1.oip-meta.xml @@ -0,0 +1,82 @@ + + + {} + {"ElementTypeToHTMLTemplateList":[]} + false + true + false + false + false + false + Procedure + Logger Integration Procedure Demo + + true + false + 0.0 + AddLogEntry + 0.0 + { + "executionConditionalFormula" : "", + "failureConditionalFormula" : "", + "failOnStepError" : true, + "useFormulas" : true, + "additionalInput" : { + "loggingLevel" : "INFO", + "message" : "Hello, world!" + }, + "additionalOutput" : { }, + "failureResponse" : { }, + "sendOnlyAdditionalInput" : false, + "returnOnlyAdditionalOutput" : false, + "returnOnlyFailureResponse" : false, + "responseJSONPath" : "", + "responseJSONNode" : "", + "sendJSONPath" : "", + "sendJSONNode" : "", + "remoteClass" : "CallableLogger", + "remoteOptions" : { }, + "remoteMethod" : "newEntry", + "chainOnStep" : false, + "actionMessage" : "", + "additionalChainableResponse" : { }, + "show" : null, + "label" : "RemoteAction1", + "disOnTplt" : false +} + 1.0 + Remote Action + + demo_Logging + Integration Procedure + { + "linkToExternalObject" : "", + "trackingCustomData" : { }, + "includeAllActionsInResponse" : false, + "columnsPropertyMap" : [ ], + "relationshipFieldsMap" : [ ], + "labelSingular" : "", + "labelPlural" : "", + "description" : "", + "nameColumn" : "", + "rollbackOnError" : false, + "chainableQueriesLimit" : 50, + "chainableDMLStatementsLimit" : null, + "chainableCpuLimit" : 2000, + "chainableHeapSizeLimit" : null, + "chainableDMLRowsLimit" : null, + "chainableQueryRowsLimit" : null, + "chainableSoslQueriesLimit" : null, + "chainableActualTimeLimit" : null, + "additionalChainableResponse" : { }, + "queueableChainableQueriesLimit" : 120, + "queueableChainableCpuLimit" : 40000, + "queueableChainableHeapSizeLimit" : 6, + "ttlMinutes" : 5, + "mockResponseMap" : { } +} + Logging + demo + demo_Logging_Procedure_1 + 1.0 + diff --git a/config/scratch-orgs/omnistudio/omniScripts/demo_Logging_English_1.os-meta.xml b/config/scratch-orgs/omnistudio/omniScripts/demo_Logging_English_1.os-meta.xml new file mode 100644 index 000000000..03b52917c --- /dev/null +++ b/config/scratch-orgs/omnistudio/omniScripts/demo_Logging_English_1.os-meta.xml @@ -0,0 +1,144 @@ + + + {"ElementTypeToHTMLTemplateList":[]} + true + false + false + false + false + true + English + Nebula Logger Demo + + false + false + 0.0 + Add New Log Entry with Integration Procedure Action + 0.0 + { + "controlWidth" : 12, + "label" : "", + "integrationProcedureKey" : "", + "useContinuation" : false, + "remoteOptions" : { + "preTransformBundle" : "", + "postTransformBundle" : "", + "useFuture" : false, + "chainable" : false + }, + "remoteTimeout" : 30000, + "preTransformBundle" : "", + "postTransformBundle" : "", + "sendJSONPath" : "", + "sendJSONNode" : "", + "responseJSONPath" : "", + "responseJSONNode" : "", + "extraPayload" : { + "loggingLevel" : "DEBUG", + "message" : "halp" + }, + "inProgressMessage" : "In Progress", + "postMessage" : "Done", + "failureNextLabel" : "Continue", + "failureAbortLabel" : "Abort", + "failureGoBackLabel" : "Go Back", + "failureAbortMessage" : "Are you sure?", + "validationRequired" : "Step", + "redirectPageName" : "", + "redirectTemplateUrl" : "vlcAcknowledge.html", + "redirectNextLabel" : "Next", + "redirectNextWidth" : 3, + "redirectPreviousLabel" : "Previous", + "redirectPreviousWidth" : 3, + "showPersistentComponent" : [ true, false ], + "show" : null, + "HTMLTemplateId" : "", + "wpm" : false, + "ssm" : false, + "message" : { }, + "pubsub" : false, + "svgSprite" : "", + "svgIcon" : "", + "errorMessage" : { + "custom" : [ ], + "default" : null + }, + "enableDefaultAbort" : false, + "enableActionMessage" : false, + "businessCategory" : "", + "businessEvent" : "" +} + 1.0 + Integration Procedure Action + + + true + false + 0.0 + Add New Log Entry with Remote Action + 0.0 + { + "controlWidth" : 12, + "label" : "", + "remoteClass" : "CallableLogger", + "remoteMethod" : "newEntry", + "remoteOptions" : { + "preTransformBundle" : "", + "postTransformBundle" : "" + }, + "remoteTimeout" : 30000, + "preTransformBundle" : "", + "postTransformBundle" : "", + "sendJSONPath" : "", + "sendJSONNode" : "", + "responseJSONPath" : "", + "responseJSONNode" : "", + "extraPayload" : { + "loggingLevel" : "WARN", + "message" : "hi?", + "saveLog" : true + }, + "inProgressMessage" : "In Progress", + "postMessage" : "Done", + "failureNextLabel" : "Continue", + "failureAbortLabel" : "Abort", + "failureGoBackLabel" : "Go Back", + "failureAbortMessage" : "Are you sure?", + "validationRequired" : "Step", + "redirectPageName" : "", + "redirectTemplateUrl" : "vlcAcknowledge.html", + "redirectNextLabel" : "Next", + "redirectNextWidth" : 3, + "redirectPreviousLabel" : "Previous", + "redirectPreviousWidth" : 3, + "showPersistentComponent" : [ true, false ], + "show" : null, + "HTMLTemplateId" : "", + "wpm" : false, + "ssm" : false, + "message" : { }, + "pubsub" : false, + "svgSprite" : "", + "svgIcon" : "", + "errorMessage" : { + "custom" : [ ], + "default" : null + }, + "enableDefaultAbort" : false, + "enableActionMessage" : false, + "useContinuation" : false, + "businessCategory" : "", + "businessEvent" : "", + "toastComplete" : true +} + 0.0 + Remote Action + + OmniScript + {"persistentComponent":[{"render":false,"label":"","remoteClass":"","remoteMethod":"","remoteTimeout":30000,"remoteOptions":{"preTransformBundle":"","postTransformBundle":""},"preTransformBundle":"","postTransformBundle":"","sendJSONPath":"","sendJSONNode":"","responseJSONPath":"","responseJSONNode":"","id":"vlcCart","itemsKey":"cartItems","modalConfigurationSetting":{"modalHTMLTemplateId":"vlcProductConfig.html","modalController":"ModalProductCtrl","modalSize":"lg"}},{"render":false,"dispOutsideOmni":false,"label":"","remoteClass":"","remoteMethod":"","remoteTimeout":30000,"remoteOptions":{"preTransformBundle":"","postTransformBundle":""},"preTransformBundle":"","postTransformBundle":"","id":"vlcKnowledge","itemsKey":"knowledgeItems","modalConfigurationSetting":{"modalHTMLTemplateId":"","modalController":"","modalSize":"lg"}}],"allowSaveForLater":true,"saveNameTemplate":null,"saveExpireInDays":null,"saveForLaterRedirectPageName":"sflRedirect","saveForLaterRedirectTemplateUrl":"vlcSaveForLaterAcknowledge.html","saveContentEncoded":false,"saveObjectId":"%ContextId%","saveURLPatterns":{},"autoSaveOnStepNext":false,"elementTypeToHTMLTemplateMapping":{},"seedDataJSON":{},"trackingCustomData":{},"enableKnowledge":false,"bLK":false,"lkObjName":null,"knowledgeArticleTypeQueryFieldsMap":{},"timeTracking":false,"hideStepChart":false,"mergeSavedData":false,"visualforcePagesAvailableInPreview":{},"cancelType":"SObject","allowCancel":true,"cancelSource":"%ContextId%","cancelRedirectPageName":"OmniScriptCancelled","cancelRedirectTemplateUrl":"vlcCancelled.html","consoleTabLabel":"New","wpm":false,"ssm":false,"message":{},"pubsub":false,"autoFocus":false,"currencyCode":"","showInputWidth":false,"rtpSeed":false,"consoleTabTitle":null,"consoleTabIcon":"custom:custom18","errorMessage":{"custom":[]},"stylesheet":{"newport":"","lightning":"","newportRtl":"","lightningRtl":""},"stepChartPlacement":"right","disableUnloadWarn":true,"scrollBehavior":"auto","currentLanguage":"en_US"} + Logging + demo + demo_Logging_English_6 + 6.0 + 533171c5-045f-0aab-f717-97eb6cd7e7f1 + diff --git a/config/scratch-orgs/build-platform-cache-scratch-def.json b/config/scratch-orgs/platform-cache-scratch-def.json similarity index 77% rename from config/scratch-orgs/build-platform-cache-scratch-def.json rename to config/scratch-orgs/platform-cache-scratch-def.json index 4a476ed9b..6b0aa75bd 100644 --- a/config/scratch-orgs/build-platform-cache-scratch-def.json +++ b/config/scratch-orgs/platform-cache-scratch-def.json @@ -1,5 +1,5 @@ { - "orgName": "Nebula Logger - Build - Platform Cache Scratch Org", + "orgName": "Nebula Logger - Platform Cache Scratch Org", "edition": "Enterprise", "hasSampleData": true, "country": "JP", @@ -12,6 +12,9 @@ "communitiesSettings": { "enableNetworksEnabled": false }, + "emailAdministrationSettings": { + "enableEnhancedEmailEnabled": false + }, "eventSettings": { "enableDeleteMonitoringData": false, "enableEventLogGeneration": false, diff --git a/docs/apex/Configuration/LoggerParameter.md b/docs/apex/Configuration/LoggerParameter.md index 23b25fc4f..5d98b1bcc 100644 --- a/docs/apex/Configuration/LoggerParameter.md +++ b/docs/apex/Configuration/LoggerParameter.md @@ -74,6 +74,10 @@ Controls if Nebula Logger queries `Schema.Network` data. When set to `false`, an Controls if Nebula Logger queries `Schema.Network` data is queried synchronously & populated on `LogEntryEvent__e` records. When set to `false`, any `Schema.Network` fields on `LogEntryEvent__e` will not be populated - the data will instead be queried asynchronously and populated on any resulting `Log__c` records. Controlled by the custom metadata record `LoggerParameter.QueryNetworkDataSynchronously`, or `true` as the default +#### `QUERY_OMNI_PROCESS_DATA` → `Boolean` + +Controls if Nebula Logger queries `Schema.OmniProcess` data. When set to `false`, any `Schema.OmniProcess` fields on `LogEntry__c` will not be populated Controlled by the custom metadata record `LoggerParameter.QueryOmniProcessData`, or `true` as the default + #### `QUERY_ORGANIZATION_DATA` → `Boolean` Controls if Nebula Logger queries `Schema.Organization` data. When set to `false`, any `Schema.Organization` fields on `LogEntryEvent__e` and `Log__c` will not be populated Controlled by the custom metadata record `LoggerParameter.QueryOrganizationData`, or `true` as the default diff --git a/docs/apex/Log-Management/LogManagementDataSelector.md b/docs/apex/Log-Management/LogManagementDataSelector.md index eb40e0e23..3d642491c 100644 --- a/docs/apex/Log-Management/LogManagementDataSelector.md +++ b/docs/apex/Log-Management/LogManagementDataSelector.md @@ -377,6 +377,26 @@ List<Log\_\_c> The list of matching `Log__c` records +#### `getOmniProcessProxies(List omniProcessIds)` → `Map` + +Returns a list of matching `Schema.OmniProcess` records based on the provided list of OmniProcess IDs + +##### Parameters + +| Param | Description | +| ---------------- | --------------------------------------------- | +| `omniProcessIds` | The list of `Schema.OmniProcess` IDs to query | + +##### Return + +**Type** + +Map<Id, LoggerSObjectProxy.OmniProcess> + +**Description** + +The instance of `Map<Id, SObject>` containing any matching `Schema.OmniProcess` records + #### `getProfilesById(List profileIds)` → `List` Returns a `List<Schema.Profile>` of records with the specified profile IDs diff --git a/docs/apex/Logger-Engine/CallableLogger.md b/docs/apex/Logger-Engine/CallableLogger.md new file mode 100644 index 000000000..f3a57ebfa --- /dev/null +++ b/docs/apex/Logger-Engine/CallableLogger.md @@ -0,0 +1,42 @@ +--- +layout: default +--- + +## CallableLogger class + +A class that implements the standard interface `System.Callable`. This provides 2 benefits: 1. A loosely-coupled way to optionally integrate with Nebula Logger (useful for ISVs/package developers). 2. The ability to log in OmniStudio's OmniScripts & Integration Procedures. + +### Related + +[Logger](Logger) + +[LogEntryEventBuilder](LogEntryEventBuilder) + +--- + +### Methods + +#### `call(String action, Map arguments)` → `Object` + +The one method required by the interface `System.Callable` description. It provides a `String`-based way to dynamically call Nebula Logger's code. + +##### Parameters + +| Param | Description | +| ----------- | ---------------------------------------------------------------------------------------------------------------------- | +| `action` | The `String` name of the `Logger` method to call. The supported actions are | +| `arguments` | An instance of `Map<String, Object>` containing any named arguments expected by the `Logger` method being called | + +##### Return + +**Type** + +Object + +**Description** + +The value returned by the `Logger` method called as an `Object` instance, or `null` if the method being called does not have a return value + +#### `handleCall(String action, Map input, Map output)` → `void` + +--- diff --git a/docs/apex/Logger-Engine/LoggerSObjectProxy.md b/docs/apex/Logger-Engine/LoggerSObjectProxy.md index 5cc6eb48f..58152caaf 100644 --- a/docs/apex/Logger-Engine/LoggerSObjectProxy.md +++ b/docs/apex/Logger-Engine/LoggerSObjectProxy.md @@ -91,3 +91,37 @@ Not all orgs have the SObject `Schema.Network` - it is only present in orgs that ###### `UrlPathPrefix` → `String` --- + +#### LoggerSObjectProxy.OmniProcess class + +Not all orgs have the SObject `Schema.OmniProcess` - it is only present in orgs that have enabled OmniStudio, so `Schema.OmniProcess` has to be referenced dynamically, including using hardcoded `String` values for field API names. The `LoggerSObjectProxy.OmniProcess` class acts as a substitute for a `Schema.OmniProcess` record so that the rest of the codebase can rely on strongly-typed references to fields (properties). + +--- + +##### Constructors + +###### `OmniProcess(SObject omniProcess)` + +--- + +##### Properties + +###### `CreatedBy` → `Schema.User` + +###### `CreatedById` → `Id` + +###### `CreatedDate` → `Datetime` + +###### `Id` → `String` + +###### `LastModifiedBy` → `Schema.User` + +###### `LastModifiedById` → `Id` + +###### `LastModifiedDate` → `Datetime` + +###### `OmniProcessType` → `String` + +###### `UniqueName` → `String` + +--- diff --git a/docs/apex/index.md b/docs/apex/index.md index 59c568ced..93b76c167 100644 --- a/docs/apex/index.md +++ b/docs/apex/index.md @@ -6,6 +6,10 @@ layout: default ## Logger Engine +### [CallableLogger](Logger-Engine/CallableLogger) + +A class that implements the standard interface `System.Callable`. This provides 2 benefits: 1. A loosely-coupled way to optionally integrate with Nebula Logger (useful for ISVs/package developers). 2. The ability to log in OmniStudio's OmniScripts & Integration Procedures. + ### [ComponentLogger](Logger-Engine/ComponentLogger) Controller class used by the lightning web component `logger` diff --git a/nebula-logger/core/main/configuration/classes/LoggerParameter.cls b/nebula-logger/core/main/configuration/classes/LoggerParameter.cls index 71ba009da..9eb9c5c49 100644 --- a/nebula-logger/core/main/configuration/classes/LoggerParameter.cls +++ b/nebula-logger/core/main/configuration/classes/LoggerParameter.cls @@ -227,6 +227,21 @@ public class LoggerParameter { private set; } + /** + * @description Controls if Nebula Logger queries `Schema.OmniProcess` data. + * When set to `false`, any `Schema.OmniProcess` fields on `LogEntry__c` will not be populated + * Controlled by the custom metadata record `LoggerParameter.QueryOmniProcessData`, or `true` as the default + */ + public static final Boolean QUERY_OMNI_PROCESS_DATA { + get { + if (QUERY_OMNI_PROCESS_DATA == null) { + QUERY_OMNI_PROCESS_DATA = getBoolean('QueryOmniProcessData', true); + } + return QUERY_OMNI_PROCESS_DATA; + } + private set; + } + /** * @description Controls if Nebula Logger queries `Schema.Network` data. * When set to `false`, any `Schema.Network` fields on `LogEntryEvent__e` and `Log__c` will not be populated diff --git a/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.QueryOmniProcessData.md-meta.xml b/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.QueryOmniProcessData.md-meta.xml new file mode 100644 index 000000000..79630f8d2 --- /dev/null +++ b/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.QueryOmniProcessData.md-meta.xml @@ -0,0 +1,21 @@ + + + + false + + Comments__c + + + + Description__c + Note: this parameter only applies to orgs that are using OmniStudio. + + When set to 'true' (default), the OmniProcess object will be queried to track additional details about the OmniScript or OmniIntegrationProcedure that generated a log entry - the queried data is stored in fields on LogEntry__c. + +When set to 'false', the OmniProcess object will not be queried, and the related fields will be null. + + + Value__c + true + + diff --git a/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls b/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls index b5f9bd4ce..676b8c962 100644 --- a/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls +++ b/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls @@ -336,6 +336,7 @@ public without sharing class LogEntryEventHandler extends LoggerSObjectHandler { OriginLocation__c = logEntryEvent.OriginLocation__c, OriginSourceActionName__c = logEntryEvent.OriginSourceActionName__c, OriginSourceApiName__c = logEntryEvent.OriginSourceApiName__c, + OriginSourceId__c = logEntryEvent.OriginSourceId__c, OriginSourceMetadataType__c = logEntryEvent.OriginSourceMetadataType__c, OriginType__c = logEntryEvent.OriginType__c, RecordCollectionSize__c = logEntryEvent.RecordCollectionSize__c, diff --git a/nebula-logger/core/main/log-management/classes/LogEntryHandler.cls b/nebula-logger/core/main/log-management/classes/LogEntryHandler.cls index 36b432d47..412bc1c68 100644 --- a/nebula-logger/core/main/log-management/classes/LogEntryHandler.cls +++ b/nebula-logger/core/main/log-management/classes/LogEntryHandler.cls @@ -30,6 +30,7 @@ public without sharing class LogEntryHandler extends LoggerSObjectHandler { this.setComponentFields(); this.setFlowDefinitionFields(); this.setFlowVersionFields(); + this.setOmniProcessFields(); this.setRecordNames(); this.setCheckboxFields(); } @@ -219,6 +220,49 @@ public without sharing class LogEntryHandler extends LoggerSObjectHandler { } } + private void setOmniProcessFields() { + List omniProcessIds = new List(); + List omniProcessLogEntries = new List(); + for (LogEntry__c logEntry : this.logEntries) { + if (logEntry.OriginType__c == 'OmniStudio' && String.isNotBlank(logEntry.OriginSourceId__c)) { + omniProcessIds.add(logEntry.OriginSourceId__c); + omniProcessLogEntries.add(logEntry); + } + } + + if (omniProcessIds.isEmpty()) { + return; + } + + Map omniProcessIdToProxy = LogManagementDataSelector.getInstance().getOmniProcessProxies(omniProcessIds); + for (LogEntry__c logEntry : omniProcessLogEntries) { + LoggerSObjectProxy.OmniProcess omniProcessProxy = omniProcessIdToProxy.get(logEntry.OriginSourceId__c); + + if (omniProcessProxy == null) { + continue; + } + + String originSourceMetadataType; + switch on omniProcessProxy.OmniProcessType { + when 'Integration Procedure' { + originSourceMetadataType = 'OmniIntegrationProcedure'; + } + when 'OmniScript' { + originSourceMetadataType = 'OmniScript'; + } + } + + logEntry.OriginSourceApiName__c = omniProcessProxy.UniqueName; + logEntry.OriginSourceCreatedById__c = omniProcessProxy.CreatedById; + logEntry.OriginSourceCreatedByUsername__c = omniProcessProxy.CreatedBy?.Username; + logEntry.OriginSourceCreatedDate__c = omniProcessProxy.CreatedDate; + logEntry.OriginSourceLastModifiedById__c = omniProcessProxy.LastModifiedById; + logEntry.OriginSourceLastModifiedByUsername__c = omniProcessProxy.LastModifiedBy?.Username; + logEntry.OriginSourceLastModifiedDate__c = omniProcessProxy.LastModifiedDate; + logEntry.OriginSourceMetadataType__c = originSourceMetadataType; + } + } + @SuppressWarnings('PMD.OperationWithLimitsInLoop') private void setRecordNames() { if (LoggerParameter.QUERY_RELATED_RECORD_DATA == false) { diff --git a/nebula-logger/core/main/log-management/classes/LogManagementDataSelector.cls b/nebula-logger/core/main/log-management/classes/LogManagementDataSelector.cls index 13fa730ee..f21da0bba 100644 --- a/nebula-logger/core/main/log-management/classes/LogManagementDataSelector.cls +++ b/nebula-logger/core/main/log-management/classes/LogManagementDataSelector.cls @@ -7,7 +7,7 @@ * @group Log Management * @description Selector class used for all queries that are specific to the log management layer */ -@SuppressWarnings('PMD.ApexCrudViolation, PMD.ExcessivePublicCount') +@SuppressWarnings('PMD.ApexCrudViolation, PMD.CyclomaticComplexity, PMD.ExcessivePublicCount') public without sharing virtual class LogManagementDataSelector { private static LogManagementDataSelector instance = new LogManagementDataSelector(); @@ -295,6 +295,29 @@ public without sharing virtual class LogManagementDataSelector { return [SELECT Id, OwnerId, UniqueId__c FROM LoggerScenario__c WHERE Id IN :logScenarioIds]; } + /** + * @description Returns a list of matching `Schema.OmniProcess` records based on the provided list of OmniProcess IDs + * @param omniProcessIds The list of `Schema.OmniProcess` IDs to query + * @return The instance of `Map` containing any matching `Schema.OmniProcess` records + */ + public virtual Map getOmniProcessProxies(List omniProcessIds) { + if (LoggerParameter.QUERY_OMNI_PROCESS_DATA == false) { + return new Map(); + } + + // OmniStudio may not be enabled in the org, and the Schema.OmniProcess object may not exist, + // so run everything dynamically + Map omniProcessIdToOmniProcessProxy = new Map(); + String query = + 'SELECT CreatedBy.Username, CreatedById, CreatedDate, Id, IsIntegrationProcedure, LastModifiedBy.Username, LastModifiedById, LastModifiedDate, OmniProcessType, UniqueName' + + ' FROM OmniProcess WHERE Id IN :omniProcessIds'; + for (SObject omniProcessRecord : System.Database.query(String.escapeSingleQuotes(query))) { + LoggerSObjectProxy.OmniProcess omniProcessProxy = new LoggerSObjectProxy.OmniProcess(omniProcessRecord); + omniProcessIdToOmniProcessProxy.put(omniProcessProxy.Id, omniProcessProxy); + } + return omniProcessIdToOmniProcessProxy; + } + /** * @description Returns a `List` of records with the specified profile IDs * @param profileIds The list of `ID` of the `Schema.Profile` records to query diff --git a/nebula-logger/core/main/log-management/objects/LogEntry__c/fields/OriginSourceMetadataType__c.field-meta.xml b/nebula-logger/core/main/log-management/objects/LogEntry__c/fields/OriginSourceMetadataType__c.field-meta.xml index 291dbefb5..f63d813e5 100644 --- a/nebula-logger/core/main/log-management/objects/LogEntry__c/fields/OriginSourceMetadataType__c.field-meta.xml +++ b/nebula-logger/core/main/log-management/objects/LogEntry__c/fields/OriginSourceMetadataType__c.field-meta.xml @@ -41,6 +41,16 @@ false + + OmniIntegrationProcedure + false + + + + OmniScript + false + + diff --git a/nebula-logger/core/main/log-management/objects/LogEntry__c/fields/OriginType__c.field-meta.xml b/nebula-logger/core/main/log-management/objects/LogEntry__c/fields/OriginType__c.field-meta.xml index 2395cabe6..94c54452f 100644 --- a/nebula-logger/core/main/log-management/objects/LogEntry__c/fields/OriginType__c.field-meta.xml +++ b/nebula-logger/core/main/log-management/objects/LogEntry__c/fields/OriginType__c.field-meta.xml @@ -29,6 +29,12 @@ false + + OmniStudio + #FFCC33 + false + + diff --git a/nebula-logger/core/main/log-management/objects/LogEntry__c/listViews/AllOmniStudioLogEntries.listView-meta.xml b/nebula-logger/core/main/log-management/objects/LogEntry__c/listViews/AllOmniStudioLogEntries.listView-meta.xml new file mode 100644 index 000000000..1a77843f6 --- /dev/null +++ b/nebula-logger/core/main/log-management/objects/LogEntry__c/listViews/AllOmniStudioLogEntries.listView-meta.xml @@ -0,0 +1,18 @@ + + + AllOmniStudioLogEntries + NAME + Log__c + LoggingLevel__c + LoggedByUsernameLink__c + Message__c + OriginSourceMetadataType__c + Timestamp__c + Everything + + OriginType__c + equals + OmniStudio + + + diff --git a/nebula-logger/core/main/logger-engine/classes/CallableLogger.cls b/nebula-logger/core/main/logger-engine/classes/CallableLogger.cls new file mode 100644 index 000000000..54f87cafd --- /dev/null +++ b/nebula-logger/core/main/logger-engine/classes/CallableLogger.cls @@ -0,0 +1,162 @@ +//------------------------------------------------------------------------------------------------// +// 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 Logger Engine + * @description A class that implements the standard interface `System.Callable`. This provides 2 benefits: + * 1. A loosely-coupled way to optionally integrate with Nebula Logger (useful for ISVs/package developers). + * 2. The ability to log in OmniStudio's OmniScripts & Integration Procedures. + * @see Logger + * @see LogEntryEventBuilder + */ +@SuppressWarnings('PMD.AvoidGlobalModifier') +global without sharing class CallableLogger implements System.Callable { + // Names of arguments used for both input & output (depending on which action is called) + private static final String ARGUMENT_EXCEPTION = 'exception'; + private static final String ARGUMENT_LOG_ENTRY_EVENT_BUILDER = 'logEntryEventBuilder'; + private static final String ARGUMENT_LOGGING_LEVEL = 'loggingLevel'; + private static final String ARGUMENT_MESSAGE = 'message'; + private static final String ARGUMENT_PARENT_LOG_TRANSACTION_ID = 'parentLogTransactionId'; + private static final String ARGUMENT_SCENARIO = 'scenario'; + private static final String ARGUMENT_RECORD_ID = 'recordId'; + private static final String ARGUMENT_RECORD = 'record'; + private static final String ARGUMENT_RECORD_LIST = 'recordList'; + private static final String ARGUMENT_RECORD_MAP = 'recordMap'; + private static final String ARGUMENT_SAVE_LOG = 'saveLog'; + private static final String ARGUMENT_SAVE_METHOD_NAME = 'saveMethodName'; + private static final String ARGUMENT_TAGS = 'tags'; + private static final String ARGUMENT_TRANSACTION_ID = 'transactionId'; + // Names of arguments used by OmniStudio when calling an instance of System.Callable + // Relevant OmniStudio docs: https://help.salesforce.com/s/articleView?id=sf.os_callable_implementations.htm&type=5 + private static final String OMNISTUDIO_ARGUMENT_INPUT = 'input'; + private static final String OMNISTUDIO_ARGUMENT_INPUT_OMNI_PROCESS_ID = 'omniProcessId'; + private static final String OMNISTUDIO_ARGUMENT_OUTPUT = 'output'; + // Names of arguments only used for output + private static final String OUTPUT_ARGUMENT_CALL_EXCEPTION_MESSAGE = 'exceptionMessage'; + private static final String OUTPUT_ARGUMENT_CALL_EXCEPTION_STACK_TRACE = 'exceptionStackTrace'; + private static final String OUTPUT_ARGUMENT_CALL_EXCEPTION_TYPE = 'exceptionType'; + private static final String OUTPUT_ARGUMENT_CALL_IS_SUCCESS = 'isSuccess'; + + @TestVisible + private static Boolean returnLogEntryEventBuilderInOutput = false; + + /** + * @description The one method required by the interface `System.Callable` description. It provides a `String`-based way to dynamically call Nebula Logger's code. + * @param action The `String` name of the `Logger` method to call. The supported actions are + * @param arguments An instance of `Map` containing any named arguments expected by the `Logger` method being called + * @return The value returned by the `Logger` method called as an `Object` instance, or `null` if the method being called does not have a return value + */ + global Object call(String action, Map arguments) { + LoggerStackTrace.ignoreOrigin(CallableLogger.class); + + arguments = arguments ?? new Map(); + Map input = (Map) arguments.get(OMNISTUDIO_ARGUMENT_INPUT) ?? arguments; + Map output = (Map) arguments.get(OMNISTUDIO_ARGUMENT_OUTPUT) ?? new Map(); + + try { + output.put(OUTPUT_ARGUMENT_CALL_IS_SUCCESS, true); + new CallableHandler().handleCall(action, input, output); + } catch (System.Exception thrownException) { + output.put(OUTPUT_ARGUMENT_CALL_IS_SUCCESS, false); + output.put(OUTPUT_ARGUMENT_CALL_EXCEPTION_MESSAGE, thrownException.getMessage()); + output.put(OUTPUT_ARGUMENT_CALL_EXCEPTION_STACK_TRACE, thrownException.getStackTraceString()); + output.put(OUTPUT_ARGUMENT_CALL_EXCEPTION_TYPE, thrownException.getTypeName()); + } + + return output; + } + + @SuppressWarnings('PMD.ApexDoc') + private class CallableHandler { + public void handleCall(String action, Map input, Map output) { + switch on action { + // Methods for transaction IDs / parent transaction IDs + when 'getTransactionId' { + output.put(ARGUMENT_TRANSACTION_ID, Logger.getTransactionId()); + } + when 'getParentLogTransactionId' { + output.put(ARGUMENT_PARENT_LOG_TRANSACTION_ID, Logger.getParentLogTransactionId()); + } + when 'setParentLogTransactionId' { + Logger.setParentLogTransactionId((String) input.get(ARGUMENT_PARENT_LOG_TRANSACTION_ID)); + } + // Methods for scenario-based logging + when 'getScenario' { + output.put(ARGUMENT_SCENARIO, Logger.getScenario()); + } + when 'setScenario' { + Logger.setScenario((String) input.get(ARGUMENT_SCENARIO)); + } + when 'endScenario' { + Logger.endScenario((String) input.get(ARGUMENT_SCENARIO)); + } + // Methods for adding & saving log entries + when 'newEntry' { + LogEntryEventBuilder builder = this.newEntry(input); + if (returnLogEntryEventBuilderInOutput) { + output.put(ARGUMENT_LOG_ENTRY_EVENT_BUILDER, builder); + } + if (input.get(ARGUMENT_SAVE_LOG) == true) { + this.saveLog(input); + } + } + when 'saveLog' { + this.saveLog(input); + } + when else { + throw new System.IllegalArgumentException('Unknown action: ' + action); + } + } + } + + @SuppressWarnings('PMD.CognitiveComplexity, PMD.NcssMethodCount') + private LogEntryEventBuilder newEntry(Map input) { + // The value of loggingLevel could be either a string name or an enum value from System.LoggingLevel, + // so always first convert it to a string for consistency + String loggingLevelName = input.get(ARGUMENT_LOGGING_LEVEL)?.toString(); + System.LoggingLevel loggingLevel = Logger.getLoggingLevel(loggingLevelName); + String message = (String) input.get(ARGUMENT_MESSAGE); + + LogEntryEventBuilder logEntryEventBuilder = Logger.newEntry(loggingLevel, message); + + if (input.containsKey(ARGUMENT_EXCEPTION)) { + logEntryEventBuilder.setExceptionDetails((System.Exception) input.get(ARGUMENT_EXCEPTION)); + } + if (input.containsKey(ARGUMENT_RECORD_ID)) { + logEntryEventBuilder.setRecord((String) input.get(ARGUMENT_RECORD_ID)); + } + if (input.containsKey(ARGUMENT_RECORD)) { + logEntryEventBuilder.setRecord((SObject) input.get(ARGUMENT_RECORD)); + } + if (input.containsKey(ARGUMENT_RECORD_LIST)) { + logEntryEventBuilder.setRecord((List) input.get(ARGUMENT_RECORD_LIST)); + } + if (input.containsKey(ARGUMENT_RECORD_MAP)) { + logEntryEventBuilder.setRecord((Map) input.get(ARGUMENT_RECORD_MAP)); + } + if (input.containsKey(ARGUMENT_TAGS)) { + logEntryEventBuilder.addTags((List) input.get(ARGUMENT_TAGS)); + } + if (input.containsKey(OMNISTUDIO_ARGUMENT_INPUT_OMNI_PROCESS_ID) || Logger.getCurrentQuiddity() == System.Quiddity.REMOTE_ACTION) { + LogEntryEvent__e logEntryEvent = logEntryEventBuilder.getLogEntryEvent(); + logEntryEvent.OriginLocation__c = null; + logEntryEvent.OriginSourceApiName__c = null; + logEntryEvent.OriginSourceId__c = (String) input.get(OMNISTUDIO_ARGUMENT_INPUT_OMNI_PROCESS_ID); + logEntryEvent.OriginSourceMetadataType__c = null; + logEntryEvent.OriginType__c = 'OmniStudio'; + } + + return logEntryEventBuilder; + } + + private void saveLog(Map input) { + // The value of saveMethodName could be either a string name or an enum value from Logger.SaveMethod, + // so always first convert it to a string for consistency + // System.Assert.fail(input.toString()); + String saveMethodName = input.get(ARGUMENT_SAVE_METHOD_NAME)?.toString() ?? Logger.getUserSettings().DefaultSaveMethod__c; + Logger.saveLog(saveMethodName); + } + } +} diff --git a/nebula-logger/core/main/logger-engine/classes/CallableLogger.cls-meta.xml b/nebula-logger/core/main/logger-engine/classes/CallableLogger.cls-meta.xml new file mode 100644 index 000000000..651b17293 --- /dev/null +++ b/nebula-logger/core/main/logger-engine/classes/CallableLogger.cls-meta.xml @@ -0,0 +1,5 @@ + + + 61.0 + Active + diff --git a/nebula-logger/core/main/logger-engine/classes/Logger.cls b/nebula-logger/core/main/logger-engine/classes/Logger.cls index 733e1a5c4..9fa54941a 100644 --- a/nebula-logger/core/main/logger-engine/classes/Logger.cls +++ b/nebula-logger/core/main/logger-engine/classes/Logger.cls @@ -15,7 +15,7 @@ global with sharing class Logger { // There's no reliable way to get the version number dynamically in Apex @TestVisible - private static final String CURRENT_VERSION_NUMBER = 'v4.14.9'; + private static final String CURRENT_VERSION_NUMBER = 'v4.14.10'; private static final System.LoggingLevel FALLBACK_LOGGING_LEVEL = System.LoggingLevel.DEBUG; private static final List LOG_ENTRIES_BUFFER = new List(); private static final String MISSING_SCENARIO_ERROR_MESSAGE = 'No logger scenario specified. A scenario is required for logging in this org.'; @@ -23,7 +23,6 @@ global with sharing class Logger { private static final String REQUEST_ID = System.Request.getCurrent().getRequestId(); private static final Map SAVE_METHOD_NAME_TO_SAVE_METHOD = new Map(); private static final String TRANSACTION_ID = System.UUID.randomUUID().toString(); - private static final System.Quiddity TRANSACTION_QUIDDITY = loadTransactionQuiddity(); private static AsyncContext currentAsyncContext; private static String currentEntryScenario; @@ -35,6 +34,8 @@ global with sharing class Logger { @TestVisible private static Integer saveLogCallCount = 0; private static Boolean suspendSaving = false; + @TestVisible + private static System.Quiddity transactionQuiddity = loadTransactionQuiddity(); private static String transactionScenario; private static final List CLASSES_TO_IGNORE { @@ -187,7 +188,7 @@ global with sharing class Logger { * @return System.Quiddity - The value of System.Request.getCurrent().getQuiddity() */ global static System.Quiddity getCurrentQuiddity() { - return TRANSACTION_QUIDDITY; + return transactionQuiddity; } /** diff --git a/nebula-logger/core/main/logger-engine/classes/LoggerSObjectProxy.cls b/nebula-logger/core/main/logger-engine/classes/LoggerSObjectProxy.cls index 08b9d66de..991816d1b 100644 --- a/nebula-logger/core/main/logger-engine/classes/LoggerSObjectProxy.cls +++ b/nebula-logger/core/main/logger-engine/classes/LoggerSObjectProxy.cls @@ -90,4 +90,38 @@ public without sharing class LoggerSObjectProxy { } } } + + /** + * @description Not all orgs have the SObject `Schema.OmniProcess` - it is only present in orgs that have enabled OmniStudio, + * so `Schema.OmniProcess` has to be referenced dynamically, including using hardcoded `String` values for field API names. The + * `LoggerSObjectProxy.OmniProcess` class acts as a substitute for a `Schema.OmniProcess` record so that the rest of the codebase can rely on + * strongly-typed references to fields (properties). + */ + @SuppressWarnings('PMD.FieldNamingConventions, PMD.VariableNamingConventions') + public class OmniProcess { + public Id CreatedById; + public Schema.User CreatedBy; + public Datetime CreatedDate; + public String Id; + public Id LastModifiedById; + public Schema.User LastModifiedBy; + public Datetime LastModifiedDate; + public String OmniProcessType; + public String UniqueName; + + @SuppressWarnings('PMD.ApexDoc') + public OmniProcess(SObject omniProcess) { + if (omniProcess != null) { + this.CreatedById = (String) omniProcess.get('CreatedById'); + this.CreatedBy = (Schema.User) omniProcess.getSObject('CreatedBy'); + this.CreatedDate = (Datetime) omniProcess.get('CreatedDate'); + this.Id = (String) omniProcess.get('Id'); + this.LastModifiedById = (String) omniProcess.get('LastModifiedById'); + this.LastModifiedBy = (Schema.User) omniProcess.getSObject('LastModifiedBy'); + this.LastModifiedDate = (Datetime) omniProcess.get('LastModifiedDate'); + this.OmniProcessType = (String) omniProcess.get('OmniProcessType'); + this.UniqueName = (String) omniProcess.get('UniqueName'); + } + } + } } diff --git a/nebula-logger/core/main/logger-engine/classes/LoggerStackTrace.cls b/nebula-logger/core/main/logger-engine/classes/LoggerStackTrace.cls index 5d6d86e46..0ac631744 100644 --- a/nebula-logger/core/main/logger-engine/classes/LoggerStackTrace.cls +++ b/nebula-logger/core/main/logger-engine/classes/LoggerStackTrace.cls @@ -20,7 +20,7 @@ public without sharing class LoggerStackTrace { private static final System.Pattern INVALID_NAMESPACED_STACK_TRACE_PATTERN { get { if (INVALID_NAMESPACED_STACK_TRACE_PATTERN == null) { - INVALID_NAMESPACED_STACK_TRACE_PATTERN = System.Pattern.compile('^\\([0-9A-Za-z_]+\\)$'); + INVALID_NAMESPACED_STACK_TRACE_PATTERN = System.Pattern.compile('^\\([0-9A-Za-z_ ]+\\)$'); } return INVALID_NAMESPACED_STACK_TRACE_PATTERN; } diff --git a/nebula-logger/core/main/logger-engine/lwc/logger/__tests__/logger.test.js b/nebula-logger/core/main/logger-engine/lwc/logger/__tests__/logger.test.js index 35a275ec9..1fd88ca04 100644 --- a/nebula-logger/core/main/logger-engine/lwc/logger/__tests__/logger.test.js +++ b/nebula-logger/core/main/logger-engine/lwc/logger/__tests__/logger.test.js @@ -1,6 +1,5 @@ import { createElement } from 'lwc'; import FORM_FACTOR from '@salesforce/client/formFactor'; -import { LoggerStackTrace } from '../loggerStackTrace'; // Recommended approach import { createLogger } from 'c/logger'; // Legacy approach @@ -13,7 +12,7 @@ const flushPromises = async () => { await new Promise(process.nextTick); }; -jest.mock('lightning/logger', () => ({ loadScript: jest.fn() }), { +jest.mock('lightning/logger', () => ({ log: jest.fn() }), { virtual: true }); diff --git a/nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js b/nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js index b1f63fab2..1150bda94 100644 --- a/nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js +++ b/nebula-logger/core/main/logger-engine/lwc/logger/logEntryBuilder.js @@ -6,7 +6,7 @@ import FORM_FACTOR from '@salesforce/client/formFactor'; import { log as lightningLog } from 'lightning/logger'; import { LoggerStackTrace } from './loggerStackTrace'; -const CURRENT_VERSION_NUMBER = 'v4.14.9'; +const CURRENT_VERSION_NUMBER = 'v4.14.10'; const LOGGING_LEVEL_EMOJIS = { ERROR: '⛔', diff --git a/nebula-logger/core/main/logger-engine/objects/LogEntryEvent__e/fields/OriginSourceId__c.field-meta.xml b/nebula-logger/core/main/logger-engine/objects/LogEntryEvent__e/fields/OriginSourceId__c.field-meta.xml new file mode 100644 index 000000000..ac2c1fb68 --- /dev/null +++ b/nebula-logger/core/main/logger-engine/objects/LogEntryEvent__e/fields/OriginSourceId__c.field-meta.xml @@ -0,0 +1,16 @@ + + + OriginSourceId__c + Active + None + false + false + false + false + + 18 + false + Confidential + Text + false + diff --git a/nebula-logger/core/tests/LoggerCore.testSuite-meta.xml b/nebula-logger/core/tests/LoggerCore.testSuite-meta.xml index c1abf4df2..e456428ab 100644 --- a/nebula-logger/core/tests/LoggerCore.testSuite-meta.xml +++ b/nebula-logger/core/tests/LoggerCore.testSuite-meta.xml @@ -1,5 +1,6 @@ + CallableLogger_Tests ComponentLogger_Tests FlowCollectionLogEntry_Tests FlowLogEntry_Tests diff --git a/nebula-logger/core/tests/configuration/classes/LoggerParameter_Tests.cls b/nebula-logger/core/tests/configuration/classes/LoggerParameter_Tests.cls index d415eb634..f9ce345e4 100644 --- a/nebula-logger/core/tests/configuration/classes/LoggerParameter_Tests.cls +++ b/nebula-logger/core/tests/configuration/classes/LoggerParameter_Tests.cls @@ -192,6 +192,17 @@ private class LoggerParameter_Tests { System.Assert.areEqual(mockValue, returnedValue); } + @IsTest + static void it_should_return_constant_value_for_query_omni_process_data() { + Boolean mockValue = false; + LoggerParameter__mdt mockParameter = new LoggerParameter__mdt(DeveloperName = 'QueryOmniProcessData', Value__c = System.JSON.serialize(mockValue)); + LoggerParameter.setMock(mockParameter); + + Boolean returnedValue = LoggerParameter.QUERY_OMNI_PROCESS_DATA; + + System.Assert.areEqual(mockValue, returnedValue); + } + @IsTest static void it_should_return_constant_value_for_query_network_data() { Boolean mockValue = false; diff --git a/nebula-logger/core/tests/log-management/classes/LogEntryEventHandler_Tests.cls b/nebula-logger/core/tests/log-management/classes/LogEntryEventHandler_Tests.cls index 9c0f5c52d..fa4e48474 100644 --- a/nebula-logger/core/tests/log-management/classes/LogEntryEventHandler_Tests.cls +++ b/nebula-logger/core/tests/log-management/classes/LogEntryEventHandler_Tests.cls @@ -1437,6 +1437,7 @@ private class LogEntryEventHandler_Tests { OriginLocation__c, OriginSourceActionName__c, OriginSourceApiName__c, + OriginSourceId__c, OriginSourceMetadataType__c, OriginType__c, RecordCollectionSize__c, @@ -1748,6 +1749,7 @@ private class LogEntryEventHandler_Tests { 'logEntry.OriginSourceActionName__c was not properly set' ); System.Assert.areEqual(logEntryEvent.OriginSourceApiName__c, logEntry.OriginSourceApiName__c, 'logEntry.OriginSourceApiName__c was not properly set'); + System.Assert.areEqual(logEntryEvent.OriginSourceId__c, logEntry.OriginSourceId__c, 'logEntry.OriginSourceId__c was not properly set'); System.Assert.areEqual( logEntryEvent.OriginSourceMetadataType__c, logEntry.OriginSourceMetadataType__c, diff --git a/nebula-logger/core/tests/log-management/classes/LogEntryHandler_Tests.cls b/nebula-logger/core/tests/log-management/classes/LogEntryHandler_Tests.cls index 4e2ec342c..de58d8e0c 100644 --- a/nebula-logger/core/tests/log-management/classes/LogEntryHandler_Tests.cls +++ b/nebula-logger/core/tests/log-management/classes/LogEntryHandler_Tests.cls @@ -6,6 +6,8 @@ @SuppressWarnings('PMD.ApexDoc, PMD.CyclomaticComplexity, PMD.ExcessiveParameterList, PMD.MethodNamingConventions, PMD.NcssMethodCount') @IsTest(IsParallel=true) private class LogEntryHandler_Tests { + private static final Boolean IS_OMNISTUDIO_ENABLED = System.Type.forName('Schema.OmniProcess') != null; + @TestSetup static void setupData() { LoggerSObjectHandler.shouldExecute(false); @@ -1258,6 +1260,119 @@ private class LogEntryHandler_Tests { System.Assert.isNull(logEntry.OriginSourceSnippet__c); } + @IsTest + static void it_should_set_origin_omni_process_details() { + // No need to fail the test if it's running in an org that does not have OmniStudio enabled + if (IS_OMNISTUDIO_ENABLED == false) { + return; + } + Schema.User currentUser = new Schema.User(Id = System.UserInfo.getUserId(), Username = System.UserInfo.getUsername()); + SObject mockOmniProcessRecord = (SObject) (System.Type.forName('Schema.OmniProcess').newInstance()); + LoggerSObjectProxy.OmniProcess mockOmniProcessProxy = new LoggerSObjectProxy.OmniProcess(mockOmniProcessRecord); + mockOmniProcessProxy.CreatedById = System.UserInfo.getUserId(); + mockOmniProcessProxy.CreatedBy = currentUser; + mockOmniProcessProxy.CreatedDate = System.now().addDays(-7); + mockOmniProcessProxy.Id = LoggerMockDataCreator.createId(mockOmniProcessRecord.getSObjectType()); + mockOmniProcessProxy.LastModifiedById = currentUser.Id; + mockOmniProcessProxy.LastModifiedBy = currentUser; + mockOmniProcessProxy.LastModifiedDate = System.now().addDays(-1); + mockOmniProcessProxy.OmniProcessType = 'Integration Procedure'; + mockOmniProcessProxy.UniqueName = 'Mock_OmniScript_English_1'; + MockLogManagementDataSelector mockSelector = new MockLogManagementDataSelector(); + mockSelector.setMockOmniProcess(mockOmniProcessProxy); + LogManagementDataSelector.setMock(mockSelector); + Log__c log = [SELECT Id FROM Log__c LIMIT 1]; + LogEntry__c logEntry = new LogEntry__c(Log__c = log.Id, OriginSourceId__c = mockOmniProcessProxy.Id, OriginType__c = 'OmniStudio'); + LoggerMockDataCreator.createDataBuilder(logEntry).populateRequiredFields().getRecord(); + + LoggerDataStore.getDatabase().insertRecord(logEntry); + + System.Assert.areEqual( + 2, + LoggerSObjectHandler.getExecutedHandlers().get(Schema.LogEntry__c.SObjectType).size(), + 'Handler class should have executed two times - once for BEFORE_INSERT and once for AFTER_INSERT' + ); + logEntry = [ + SELECT + Id, + OriginLocation__c, + OriginSourceActionName__c, + OriginSourceApiName__c, + OriginSourceApiVersion__c, + OriginSourceCreatedById__c, + OriginSourceCreatedByUsername__c, + OriginSourceCreatedDate__c, + OriginSourceId__c, + OriginSourceLastModifiedById__c, + OriginSourceLastModifiedByUsername__c, + OriginSourceLastModifiedDate__c, + OriginSourceMetadataType__c, + OriginType__c + FROM LogEntry__c + WHERE Id = :logEntry.Id + ]; + System.Assert.areEqual('OmniStudio', logEntry.OriginType__c); + System.Assert.isNull(logEntry.OriginLocation__c); + System.Assert.isNull(logEntry.OriginSourceActionName__c); + System.Assert.areEqual(mockOmniProcessProxy.UniqueName, logEntry.OriginSourceApiName__c); + System.Assert.isNull(logEntry.OriginSourceApiVersion__c); + System.Assert.areEqual(mockOmniProcessProxy.CreatedById, logEntry.OriginSourceCreatedById__c); + System.Assert.areEqual(mockOmniProcessProxy.CreatedBy.Username, logEntry.OriginSourceCreatedByUsername__c); + System.Assert.areEqual(mockOmniProcessProxy.CreatedDate, logEntry.OriginSourceCreatedDate__c); + System.Assert.areEqual(mockOmniProcessProxy.Id, logEntry.OriginSourceId__c); + System.Assert.areEqual(mockOmniProcessProxy.LastModifiedById, logEntry.OriginSourceLastModifiedById__c); + System.Assert.areEqual(mockOmniProcessProxy.LastModifiedBy.Username, logEntry.OriginSourceLastModifiedByUsername__c); + System.Assert.areEqual(mockOmniProcessProxy.LastModifiedDate, logEntry.OriginSourceLastModifiedDate__c); + System.Assert.areEqual('OmniIntegrationProcedure', logEntry.OriginSourceMetadataType__c); + } + + @IsTest + static void it_should_set_skip_setting_origin_omni_process_details_when_origin_source_id_is_null() { + Log__c log = [SELECT Id FROM Log__c LIMIT 1]; + LogEntry__c logEntry = new LogEntry__c(Log__c = log.Id, OriginSourceId__c = null, OriginType__c = 'OmniStudio'); + LoggerMockDataCreator.createDataBuilder(logEntry).populateRequiredFields().getRecord(); + + LoggerDataStore.getDatabase().insertRecord(logEntry); + + System.Assert.areEqual( + 2, + LoggerSObjectHandler.getExecutedHandlers().get(Schema.LogEntry__c.SObjectType).size(), + 'Handler class should have executed two times - once for BEFORE_INSERT and once for AFTER_INSERT' + ); + logEntry = [ + SELECT + Id, + OriginLocation__c, + OriginSourceActionName__c, + OriginSourceApiName__c, + OriginSourceApiVersion__c, + OriginSourceCreatedById__c, + OriginSourceCreatedByUsername__c, + OriginSourceCreatedDate__c, + OriginSourceId__c, + OriginSourceLastModifiedById__c, + OriginSourceLastModifiedByUsername__c, + OriginSourceLastModifiedDate__c, + OriginSourceMetadataType__c, + OriginType__c + FROM LogEntry__c + WHERE Id = :logEntry.Id + ]; + System.Assert.areEqual('OmniStudio', logEntry.OriginType__c); + System.Assert.isNull(logEntry.OriginLocation__c); + System.Assert.isNull(logEntry.OriginSourceActionName__c); + System.Assert.isNull(logEntry.OriginSourceApiName__c); + System.Assert.isNull(logEntry.OriginSourceApiVersion__c); + System.Assert.isNull(logEntry.OriginSourceCreatedById__c); + System.Assert.isNull(logEntry.OriginSourceCreatedByUsername__c); + System.Assert.isNull(logEntry.OriginSourceCreatedDate__c); + System.Assert.isNull(logEntry.OriginSourceId__c); + System.Assert.isNull(logEntry.OriginSourceLastModifiedById__c); + System.Assert.isNull(logEntry.OriginSourceLastModifiedByUsername__c); + System.Assert.isNull(logEntry.OriginSourceLastModifiedDate__c); + System.Assert.isNull(logEntry.OriginSourceMetadataType__c); + } + private static String getNamespacePrefix() { String className = LogEntryHandler_Tests.class.getName(); String namespacePrefix = className.contains('.') ? className.substringBefore('.') : ''; @@ -1265,6 +1380,18 @@ private class LogEntryHandler_Tests { return namespacePrefix; } + private class MockLogManagementDataSelector extends LogManagementDataSelector { + private LoggerSObjectProxy.OmniProcess mockOmniProcessProxy; + + public override Map getOmniProcessProxies(List omniProcessIds) { + return new Map{ mockOmniProcessProxy.Id => mockOmniProcessProxy }; + } + + public void setMockOmniProcess(LoggerSObjectProxy.OmniProcess omniProcessProxy) { + this.mockOmniProcessProxy = omniProcessProxy; + } + } + // Helper class for testing stack trace parsing & Schema.ApexClass querying for an inner class private class SomeInnerClass { public LoggerStackTrace getLoggerStackTrace() { diff --git a/nebula-logger/core/tests/log-management/classes/LogManagementDataSelector_Tests.cls b/nebula-logger/core/tests/log-management/classes/LogManagementDataSelector_Tests.cls index 4556953b8..b0252745a 100644 --- a/nebula-logger/core/tests/log-management/classes/LogManagementDataSelector_Tests.cls +++ b/nebula-logger/core/tests/log-management/classes/LogManagementDataSelector_Tests.cls @@ -6,6 +6,8 @@ @SuppressWarnings('PMD.ApexDoc, PMD.CyclomaticComplexity, PMD.MethodNamingConventions') @IsTest(IsParallel=false) private class LogManagementDataSelector_Tests { + private static final Boolean IS_OMNISTUDIO_ENABLED = System.Type.forName('Schema.OmniProcess') != null; + @IsTest static void it_dynamically_queries_all_records_for_specified_sobject_type_and_fields() { Schema.SObjectType targetSObjectType = Schema.Organization.SObjectType; @@ -357,6 +359,58 @@ private class LogManagementDataSelector_Tests { System.Assert.areEqual(logs.size(), returnedResults.size()); } + @IsTest + static void it_returns_logger_scenarios_for_specified_ids() { + LoggerSObjectHandler.shouldExecute(false); + List loggerScenarios = new List(); + for (Integer i = 0; i < 5; i++) { + LoggerScenario__c loggerScenario = (LoggerScenario__c) LoggerMockDataCreator.createDataBuilder(Schema.LoggerScenario__c.SObjectType) + .populateRequiredFields() + .getRecord(); + loggerScenario.Name = 'some fake scenario ' + i; + loggerScenario.UniqueId__c = 'some fake scenario ' + i; + loggerScenarios.add(loggerScenario); + } + insert loggerScenarios; + List loggerScenarioIds = new List(new Map(loggerScenarios).keySet()); + + List returnedResults = LogManagementDataSelector.getInstance().getLoggerScenariosById(loggerScenarioIds); + + System.Assert.areEqual(loggerScenarios.size(), returnedResults.size()); + } + + @IsTest + static void it_returns_omni_processes_for_specified_ids() { + // No need to fail the test if it's running in an org that does not have OmniStudio enabled + if (IS_OMNISTUDIO_ENABLED == false) { + return; + } + // Fun fact: in an anonymous Apex script, you can easily create an OmniProcess record... + // ...but when you create one in a test class, you get a gack error 🥲 + // Because of this platform limitation, this test is not great - it doesn't create records (because it can't), + // and thus, it can't validate that only the correct records are returned. But, it does validate that the the + // query string is valid & can successfully be executed. + // TODO revisit to see if there is any other way to create OmniProcess records to improve this test. + List omniProcessIds = new List(); + + List returnedResults = LogManagementDataSelector.getInstance().getLoggerScenariosById(omniProcessIds); + + System.Assert.isNotNull(returnedResults); + } + + @IsTest + static void it_does_not_query_omni_processes_when_disabled_via_logger_parameter() { + // The IDs used in the query don't particularly matter here - the main concern is checking that the query does not execute at all + List targetOmniProcessIds = new List{ System.UserInfo.getUserId() }; + Integer originalQueryCount = System.Limits.getQueries(); + LoggerParameter.setMock(new LoggerParameter__mdt(DeveloperName = 'QueryOmniProcessData', Value__c = String.valueOf(false))); + + Map returnedResults = LogManagementDataSelector.getInstance().getOmniProcessProxies(targetOmniProcessIds); + + System.Assert.areEqual(originalQueryCount, System.Limits.getQueries()); + System.Assert.areEqual(0, returnedResults.size()); + } + @IsTest static void it_returns_profiles_for_specified_profile_ids() { List expectedResults = [SELECT Id, Name FROM Profile LIMIT 10]; diff --git a/nebula-logger/core/tests/logger-engine/classes/CallableLogger_Tests.cls b/nebula-logger/core/tests/logger-engine/classes/CallableLogger_Tests.cls new file mode 100644 index 000000000..715d8d739 --- /dev/null +++ b/nebula-logger/core/tests/logger-engine/classes/CallableLogger_Tests.cls @@ -0,0 +1,518 @@ +//------------------------------------------------------------------------------------------------// +// 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. // +//------------------------------------------------------------------------------------------------// + +@SuppressWarnings('PMD.ApexDoc, PMD.ApexAssertionsShouldIncludeMessage, PMD.AvoidHardcodingId, PMD.MethodNamingConventions') +@IsTest(IsParallel=true) +private class CallableLogger_Tests { + // Not all orgs have OmniStudio, so not all orgs will have the OmniProcess SObject. + // Instead of trying to create an actual ID, this test ID can be used. It was the ID + // of an actual record in a scratch org with OmniStudio enabled. + private static final String TEST_OMNI_PROCESS_ID = '0jNDL000000Cbac2AC'; + + static { + CallableLogger.returnLogEntryEventBuilderInOutput = true; + } + + @IsTest + static void it_returns_exception_message_for_unsupported_action_when_using_standard_approach() { + String fakeActionName = 'some-action-that-will-never-exist-i-hope'; + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance.call(fakeActionName, null); + + System.Assert.areEqual(4, returnedOutput.size()); + System.Assert.areEqual(false, returnedOutput.get('isSuccess'), 'Expected isSuccess == false. Output received: ' + returnedOutput); + System.Assert.areEqual('Unknown action: ' + fakeActionName, returnedOutput.get('exceptionMessage')); + System.Assert.isNotNull(returnedOutput.get('exceptionStackTrace')); + System.Assert.areEqual(System.IllegalArgumentException.class.getName(), returnedOutput.get('exceptionType')); + } + + @IsTest + static void it_returns_exception_message_for_unsupported_action_when_using_omnistudio_approach() { + String fakeActionName = 'some-action-that-will-never-exist-i-hope'; + Map omnistudioOutput = new Map(); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + callableLoggerInstance.call(fakeActionName, new Map{ 'output' => omnistudioOutput }); + + System.Assert.areEqual(4, omnistudioOutput.size()); + System.Assert.areEqual(false, omnistudioOutput.get('isSuccess'), 'Expected isSuccess == false. Output received: ' + omnistudioOutput); + System.Assert.areEqual('Unknown action: ' + fakeActionName, omnistudioOutput.get('exceptionMessage')); + System.Assert.isNotNull(omnistudioOutput.get('exceptionStackTrace')); + System.Assert.areEqual(System.IllegalArgumentException.class.getName(), omnistudioOutput.get('exceptionType')); + } + + @IsTest + static void it_requires_case_sensitive_string_values_for_action_name() { + // The specific action being tested here (setParentLogTransactionId()) isn't really important - it could be any supported action. + // What _is_ important is verifying that the action name is case-sensitive. + String correctlyCasedActionName = 'getParentLogTransactionId'; + String incorrectlyCasedActionName = correctlyCasedActionName.toUpperCase(); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance.call(incorrectlyCasedActionName, null); + + System.Assert.areEqual(4, returnedOutput.size()); + System.Assert.areEqual(false, returnedOutput.get('isSuccess'), 'Expected isSuccess == false. Output received: ' + returnedOutput); + System.Assert.areEqual('Unknown action: ' + incorrectlyCasedActionName, returnedOutput.get('exceptionMessage')); + System.Assert.isNotNull(returnedOutput.get('exceptionStackTrace')); + System.Assert.areEqual(System.IllegalArgumentException.class.getName(), returnedOutput.get('exceptionType')); + } + + @IsTest + static void it_gets_transaction_id_when_using_standard_approach() { + System.Assert.isNotNull(Logger.getTransactionId(), 'Test has started under the wrong conditions'); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance.call('getTransactionId', null); + + System.Assert.areEqual(2, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + System.Assert.areEqual(Logger.getTransactionId(), returnedOutput.get('transactionId')); + } + + @IsTest + static void it_gets_transaction_id_when_using_omnistudio_approach() { + System.Assert.isNotNull(Logger.getTransactionId(), 'Test has started under the wrong conditions'); + Map omnistudioOutput = new Map(); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + callableLoggerInstance.call('getTransactionId', new Map{ 'output' => omnistudioOutput }); + + System.Assert.areEqual(2, omnistudioOutput.size()); + System.Assert.areEqual(true, omnistudioOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + omnistudioOutput); + System.Assert.areEqual(Logger.getTransactionId(), omnistudioOutput.get('transactionId')); + } + + @IsTest + static void it_gets_parent_log_transaction_id_when_using_standard_approach() { + String mockParentLogTransactionId = 'some transaction id'; + Logger.setParentLogTransactionId(mockParentLogTransactionId); + System.Assert.areEqual(mockParentLogTransactionId, Logger.getParentLogTransactionId(), 'Test has started under the wrong conditions'); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance.call('getParentLogTransactionId', null); + + System.Assert.areEqual(2, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + System.Assert.areEqual(mockParentLogTransactionId, returnedOutput.get('parentLogTransactionId')); + } + + @IsTest + static void it_gets_parent_log_transaction_id_when_using_omnistudio_approach() { + String mockParentLogTransactionId = 'some transaction id'; + Logger.setParentLogTransactionId(mockParentLogTransactionId); + System.Assert.areEqual(mockParentLogTransactionId, Logger.getParentLogTransactionId(), 'Test has started under the wrong conditions'); + Map omnistudioOutput = new Map(); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + callableLoggerInstance.call('getParentLogTransactionId', new Map{ 'output' => omnistudioOutput }); + + System.Assert.areEqual(2, omnistudioOutput.size()); + System.Assert.areEqual(true, omnistudioOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + omnistudioOutput); + System.Assert.areEqual(mockParentLogTransactionId, omnistudioOutput.get('parentLogTransactionId')); + } + + @IsTest + static void it_sets_parent_log_transaction_id_when_using_standard_approach() { + System.Assert.isNull(Logger.getParentLogTransactionId(), 'Test has started under the wrong conditions'); + String mockParentLogTransactionId = 'some transaction id'; + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance + .call('setParentLogTransactionId', new Map{ 'parentLogTransactionId' => mockParentLogTransactionId }); + + System.Assert.areEqual(1, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + System.Assert.areEqual(mockParentLogTransactionId, Logger.getParentLogTransactionId()); + } + + @IsTest + static void it_sets_parent_log_transaction_id_when_using_omnistudio_approach() { + System.Assert.isNull(Logger.getParentLogTransactionId(), 'Test has started under the wrong conditions'); + String mockParentLogTransactionId = 'some transaction id'; + Map omnistudioInput = new Map{ 'parentLogTransactionId' => mockParentLogTransactionId }; + Map omnistudioOutput = new Map(); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + callableLoggerInstance.call('setParentLogTransactionId', new Map{ 'input' => omnistudioInput, 'output' => omnistudioOutput }); + + System.Assert.areEqual(1, omnistudioOutput.size()); + System.Assert.areEqual(true, omnistudioOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + omnistudioOutput); + System.Assert.areEqual(mockParentLogTransactionId, Logger.getParentLogTransactionId()); + } + + @IsTest + static void it_gets_scenario_when_using_standard_approach() { + String mockScenario = 'some scenario'; + Logger.setScenario(mockScenario); + System.Assert.areEqual(mockScenario, Logger.getScenario(), 'Test has started under the wrong conditions'); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance.call('getScenario', null); + + System.Assert.areEqual(2, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + System.Assert.areEqual(Logger.getScenario(), returnedOutput.get('scenario')); + } + + @IsTest + static void it_gets_scenario_when_using_omnistudio_approach() { + String mockScenario = 'some scenario'; + Logger.setScenario(mockScenario); + System.Assert.areEqual(mockScenario, Logger.getScenario(), 'Test has started under the wrong conditions'); + Map omnistudioOutput = new Map(); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + callableLoggerInstance.call('getScenario', new Map{ 'output' => omnistudioOutput }); + + System.Assert.areEqual(2, omnistudioOutput.size()); + System.Assert.areEqual(true, omnistudioOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + omnistudioOutput); + System.Assert.areEqual(Logger.getScenario(), omnistudioOutput.get('scenario')); + } + + @IsTest + static void it_sets_scenario_when_using_standard_approach() { + System.Assert.isNull(Logger.getScenario(), 'Test has started under the wrong conditions'); + String mockScenario = 'some scenario'; + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance.call( + 'setScenario', + new Map{ 'scenario' => mockScenario } + ); + + System.Assert.areEqual(1, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + System.Assert.areEqual(mockScenario, Logger.getScenario()); + } + + @IsTest + static void it_sets_scenario_when_using_omnistudio_approach() { + System.Assert.isNull(Logger.getScenario(), 'Test has started under the wrong conditions'); + String mockScenario = 'some scenario'; + Map omnistudioInput = new Map{ 'scenario' => mockScenario }; + Map omnistudioOutput = new Map(); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + callableLoggerInstance.call('setScenario', new Map{ 'input' => omnistudioInput, 'output' => omnistudioOutput }); + + System.Assert.areEqual(1, omnistudioOutput.size()); + System.Assert.areEqual(true, omnistudioOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + omnistudioOutput); + System.Assert.areEqual(mockScenario, Logger.getScenario()); + } + + @IsTest + static void it_ends_scenario_when_using_standard_approach() { + String mockScenario = 'some scenario'; + Logger.setScenario(mockScenario); + System.Assert.areEqual(mockScenario, Logger.getScenario(), 'Test has started under the wrong conditions'); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance.call( + 'endScenario', + new Map{ 'scenario' => mockScenario } + ); + + System.Assert.areEqual(1, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + System.Assert.isNull(Logger.getScenario()); + } + + @IsTest + static void it_ends_scenario_when_using_omnistudio_approach() { + String mockScenario = 'some scenario'; + Logger.setScenario(mockScenario); + System.Assert.areEqual(mockScenario, Logger.getScenario(), 'Test has started under the wrong conditions'); + Map omnistudioInput = new Map{ 'scenario' => mockScenario }; + Map omnistudioOutput = new Map(); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + callableLoggerInstance.call('endScenario', new Map{ 'input' => omnistudioInput, 'output' => omnistudioOutput }); + + System.Assert.areEqual(1, omnistudioOutput.size()); + System.Assert.areEqual(true, omnistudioOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + omnistudioOutput); + System.Assert.isNull(Logger.getScenario()); + } + + @IsTest + static void it_adds_new_entry_when_using_standard_approach() { + System.LoggingLevel loggingLevel = System.LoggingLevel.FINE; + String message = 'some log entry message'; + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance.call( + 'newEntry', + new Map{ 'loggingLevel' => loggingLevel, 'message' => message } + ); + + System.Assert.areEqual(2, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + LogEntryEvent__e logEntryEvent = ((LogEntryEventBuilder) returnedOutput.get('logEntryEventBuilder')).getLogEntryEvent(); + System.Assert.areEqual(loggingLevel.name(), logEntryEvent.LoggingLevel__c); + System.Assert.areEqual(message, logEntryEvent.Message__c); + System.Assert.areEqual(CallableLogger_Tests.class.getName() + '.it_adds_new_entry_when_using_standard_approach', logEntryEvent.OriginLocation__c); + System.Assert.areEqual('Apex', logEntryEvent.OriginType__c); + } + + @IsTest + static void it_adds_new_entry_when_using_omnistudio_approach() { + Logger.transactionQuiddity = System.Quiddity.REMOTE_ACTION; + String loggingLevelName = System.LoggingLevel.FINE.name(); + String message = 'some log entry message'; + Map omnistudioInput = new Map{ + 'loggingLevel' => loggingLevelName, + 'message' => message, + 'omniProcessId' => TEST_OMNI_PROCESS_ID + }; + Map omnistudioOutput = new Map(); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + callableLoggerInstance.call('newEntry', new Map{ 'input' => omnistudioInput, 'output' => omnistudioOutput }); + + System.Assert.areEqual(2, omnistudioOutput.size(), System.JSON.serializePretty(omnistudioOutput)); + System.Assert.areEqual(true, omnistudioOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + omnistudioOutput); + LogEntryEvent__e logEntryEvent = ((LogEntryEventBuilder) omnistudioOutput.get('logEntryEventBuilder')).getLogEntryEvent(); + System.Assert.areEqual(loggingLevelName, logEntryEvent.LoggingLevel__c); + System.Assert.areEqual(message, logEntryEvent.Message__c); + System.Assert.isNull(logEntryEvent.OriginLocation__c, 'Expected a null for OriginLocation__c, received: ' + logEntryEvent.OriginLocation__c); + System.Assert.areEqual(TEST_OMNI_PROCESS_ID, logEntryEvent.OriginSourceId__c); + System.Assert.areEqual('OmniStudio', logEntryEvent.OriginType__c); + } + + // For the other newEntry() tests, just test the standard approach - the OmniStudio approach will use the same parameters/inputs + @IsTest + static void it_adds_new_entry_with_exception_when_using_standard_approach() { + System.LoggingLevel loggingLevel = System.LoggingLevel.FINE; + String message = 'some log entry message'; + System.Exception apexException = new System.DmlException('whoops'); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance.call( + 'newEntry', + new Map{ 'exception' => apexException, 'loggingLevel' => loggingLevel, 'message' => message } + ); + + System.Assert.areEqual(2, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + LogEntryEvent__e logEntryEvent = ((LogEntryEventBuilder) returnedOutput.get('logEntryEventBuilder')).getLogEntryEvent(); + System.Assert.areEqual(loggingLevel.name(), logEntryEvent.LoggingLevel__c); + System.Assert.areEqual(message, logEntryEvent.Message__c); + System.Assert.areEqual(apexException.getMessage(), logEntryEvent.ExceptionMessage__c); + System.Assert.areEqual(apexException.getTypeName(), logEntryEvent.ExceptionType__c); + System.Assert.areEqual( + CallableLogger_Tests.class.getName() + '.it_adds_new_entry_with_exception_when_using_standard_approach', + logEntryEvent.OriginLocation__c + ); + System.Assert.areEqual('Apex', logEntryEvent.OriginType__c); + } + + @IsTest + static void it_adds_new_entry_with_record_id_when_using_standard_approach() { + System.LoggingLevel loggingLevel = System.LoggingLevel.FINE; + String message = 'some log entry message'; + Id recordId = System.UserInfo.getUserId(); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance.call( + 'newEntry', + new Map{ 'loggingLevel' => loggingLevel, 'message' => message, 'recordId' => recordId } + ); + + System.Assert.areEqual(2, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + LogEntryEvent__e logEntryEvent = ((LogEntryEventBuilder) returnedOutput.get('logEntryEventBuilder')).getLogEntryEvent(); + System.Assert.areEqual(loggingLevel.name(), logEntryEvent.LoggingLevel__c); + System.Assert.areEqual(message, logEntryEvent.Message__c); + System.Assert.areEqual(recordId, logEntryEvent.RecordId__c); + System.Assert.areEqual( + CallableLogger_Tests.class.getName() + '.it_adds_new_entry_with_record_id_when_using_standard_approach', + logEntryEvent.OriginLocation__c + ); + System.Assert.areEqual('Apex', logEntryEvent.OriginType__c); + } + + @IsTest + static void it_adds_new_entry_with_record_when_using_standard_approach() { + System.LoggingLevel loggingLevel = System.LoggingLevel.FINE; + String message = 'some log entry message'; + SObject record = new Schema.User(Id = System.UserInfo.getUserId()); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance + .call('newEntry', new Map{ 'loggingLevel' => loggingLevel, 'message' => message, 'record' => record }); + + System.Assert.areEqual(2, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + LogEntryEvent__e logEntryEvent = ((LogEntryEventBuilder) returnedOutput.get('logEntryEventBuilder')).getLogEntryEvent(); + System.Assert.areEqual(loggingLevel.name(), logEntryEvent.LoggingLevel__c); + System.Assert.areEqual(message, logEntryEvent.Message__c); + System.Assert.areEqual(System.JSON.serializePretty(record), logEntryEvent.RecordJson__c); + System.Assert.areEqual( + CallableLogger_Tests.class.getName() + '.it_adds_new_entry_with_record_when_using_standard_approach', + logEntryEvent.OriginLocation__c + ); + System.Assert.areEqual('Apex', logEntryEvent.OriginType__c); + } + + @IsTest + static void it_adds_new_entry_with_record_list_when_using_standard_approach() { + System.LoggingLevel loggingLevel = System.LoggingLevel.FINE; + String message = 'some log entry message'; + List recordList = new List{ new Schema.User(Id = System.UserInfo.getUserId()) }; + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance + .call('newEntry', new Map{ 'loggingLevel' => loggingLevel, 'message' => message, 'recordList' => recordList }); + + System.Assert.areEqual(2, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + LogEntryEvent__e logEntryEvent = ((LogEntryEventBuilder) returnedOutput.get('logEntryEventBuilder')).getLogEntryEvent(); + System.Assert.areEqual(loggingLevel.name(), logEntryEvent.LoggingLevel__c); + System.Assert.areEqual(message, logEntryEvent.Message__c); + System.Assert.areEqual(System.JSON.serializePretty(recordList), logEntryEvent.RecordJson__c); + System.Assert.areEqual( + CallableLogger_Tests.class.getName() + '.it_adds_new_entry_with_record_list_when_using_standard_approach', + logEntryEvent.OriginLocation__c + ); + System.Assert.areEqual('Apex', logEntryEvent.OriginType__c); + } + + @IsTest + static void it_adds_new_entry_with_record_map_when_using_standard_approach() { + System.LoggingLevel loggingLevel = System.LoggingLevel.FINE; + String message = 'some log entry message'; + Map recordMap = new Map{ System.UserInfo.getUserId() => new Schema.User(Id = System.UserInfo.getUserId()) }; + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance + .call('newEntry', new Map{ 'loggingLevel' => loggingLevel, 'message' => message, 'recordMap' => recordMap }); + + System.Assert.areEqual(2, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + LogEntryEvent__e logEntryEvent = ((LogEntryEventBuilder) returnedOutput.get('logEntryEventBuilder')).getLogEntryEvent(); + System.Assert.areEqual(loggingLevel.name(), logEntryEvent.LoggingLevel__c); + System.Assert.areEqual(message, logEntryEvent.Message__c); + System.Assert.areEqual(System.JSON.serializePretty(recordMap), logEntryEvent.RecordJson__c); + System.Assert.areEqual( + CallableLogger_Tests.class.getName() + '.it_adds_new_entry_with_record_map_when_using_standard_approach', + logEntryEvent.OriginLocation__c + ); + System.Assert.areEqual('Apex', logEntryEvent.OriginType__c); + } + + @IsTest + static void it_adds_new_entry_with_tags_when_using_standard_approach() { + System.LoggingLevel loggingLevel = System.LoggingLevel.FINE; + String message = 'some log entry message'; + List tags = new List{ 'some-tag', 'another-tag' }; + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance + .call('newEntry', new Map{ 'loggingLevel' => loggingLevel, 'message' => message, 'tags' => tags }); + + System.Assert.areEqual(2, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + LogEntryEvent__e logEntryEvent = ((LogEntryEventBuilder) returnedOutput.get('logEntryEventBuilder')).getLogEntryEvent(); + System.Assert.areEqual(loggingLevel.name(), logEntryEvent.LoggingLevel__c); + System.Assert.areEqual(message, logEntryEvent.Message__c); + tags.sort(); // Tags are auto-sorted by LogEntryEventBuilder + System.Assert.areEqual(String.join(tags, '\n'), logEntryEvent.Tags__c); + System.Assert.areEqual(CallableLogger_Tests.class.getName() + '.it_adds_new_entry_with_tags_when_using_standard_approach', logEntryEvent.OriginLocation__c); + System.Assert.areEqual('Apex', logEntryEvent.OriginType__c); + } + + @IsTest + static void it_adds_new_entry_and_saves_when_using_standard_approach() { + System.LoggingLevel loggingLevel = System.LoggingLevel.FINE; + String message = 'some log entry message'; + Boolean saveLog = true; + System.Assert.areEqual(0, Logger.getBufferSize()); + System.Assert.isNull(Logger.lastSaveMethodNameUsed); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance + .call('newEntry', new Map{ 'loggingLevel' => loggingLevel, 'message' => message, 'saveLog' => saveLog }); + + System.Assert.areEqual(2, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + LogEntryEvent__e logEntryEvent = ((LogEntryEventBuilder) returnedOutput.get('logEntryEventBuilder')).getLogEntryEvent(); + System.Assert.areEqual(loggingLevel.name(), logEntryEvent.LoggingLevel__c); + System.Assert.areEqual(message, logEntryEvent.Message__c); + System.Assert.areEqual(0, Logger.getBufferSize()); + System.Assert.areEqual(Logger.getUserSettings().DefaultSaveMethod__c, Logger.lastSaveMethodNameUsed); + System.Assert.areEqual(CallableLogger_Tests.class.getName() + '.it_adds_new_entry_and_saves_when_using_standard_approach', logEntryEvent.OriginLocation__c); + System.Assert.areEqual('Apex', logEntryEvent.OriginType__c); + } + + @IsTest + static void it_saves_log_without_specifying_a_save_method_when_using_standard_approach() { + Logger.info('some log entry that will be saved using the default save method').getLogEntryEvent(); + System.Assert.areEqual(1, Logger.getBufferSize()); + System.Assert.isNull(Logger.lastSaveMethodNameUsed); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance.call('saveLog', null); + + System.Assert.areEqual(1, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + System.Assert.areEqual(0, Logger.getBufferSize()); + System.Assert.areEqual(Logger.getUserSettings().DefaultSaveMethod__c, Logger.lastSaveMethodNameUsed); + } + + @IsTest + static void it_saves_log_without_specifying_a_save_method_when_using_omnistudio_approach() { + Logger.info('some log entry that will be saved using the default save method').getLogEntryEvent(); + System.Assert.areEqual(1, Logger.getBufferSize()); + System.Assert.isNull(Logger.lastSaveMethodNameUsed); + Map omnistudioOutput = new Map(); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + callableLoggerInstance.call('saveLog', new Map{ 'output' => omnistudioOutput }); + + System.Assert.areEqual(1, omnistudioOutput.size(), omnistudioOutput.toString()); + System.Assert.areEqual(true, omnistudioOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + omnistudioOutput); + System.Assert.areEqual(0, Logger.getBufferSize()); + System.Assert.areEqual(Logger.getUserSettings().DefaultSaveMethod__c, Logger.lastSaveMethodNameUsed); + } + + @IsTest + static void it_saves_log_with_specified_save_method_when_using_standard_approach() { + Logger.info('some log entry that will be saved using the specified save method').getLogEntryEvent(); + System.Assert.areEqual(1, Logger.getBufferSize()); + System.Assert.isNull(Logger.lastSaveMethodNameUsed); + String targetSaveMethodName = Logger.SaveMethod.SYNCHRONOUS_DML.name(); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance.call( + 'saveLog', + new Map{ 'saveMethodName' => targetSaveMethodName } + ); + + System.Assert.areEqual(1, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + System.Assert.areEqual(0, Logger.getBufferSize()); + System.Assert.areEqual(targetSaveMethodName, Logger.lastSaveMethodNameUsed); + } + + @IsTest + static void it_saves_log_with_specified_save_method_when_using_omnistudio_approach() { + Logger.info('some log entry that will be saved using the specified save method').getLogEntryEvent(); + System.Assert.areEqual(1, Logger.getBufferSize()); + System.Assert.isNull(Logger.lastSaveMethodNameUsed); + String targetSaveMethodName = Logger.SaveMethod.SYNCHRONOUS_DML.name(); + + System.Callable callableLoggerInstance = (System.Callable) System.Type.forName('CallableLogger').newInstance(); + Map returnedOutput = (Map) callableLoggerInstance.call( + 'saveLog', + new Map{ 'saveMethodName' => targetSaveMethodName } + ); + + System.Assert.areEqual(1, returnedOutput.size()); + System.Assert.areEqual(true, returnedOutput.get('isSuccess'), 'Expected isSuccess == true. Output received: ' + returnedOutput); + System.Assert.areEqual(0, Logger.getBufferSize()); + System.Assert.areEqual(targetSaveMethodName, Logger.lastSaveMethodNameUsed); + } +} diff --git a/nebula-logger/core/tests/logger-engine/classes/CallableLogger_Tests.cls-meta.xml b/nebula-logger/core/tests/logger-engine/classes/CallableLogger_Tests.cls-meta.xml new file mode 100644 index 000000000..7d5f9e8a3 --- /dev/null +++ b/nebula-logger/core/tests/logger-engine/classes/CallableLogger_Tests.cls-meta.xml @@ -0,0 +1,5 @@ + + + 61.0 + Active + \ No newline at end of file diff --git a/nebula-logger/core/tests/logger-engine/classes/LoggerSObjectProxy_Tests.cls b/nebula-logger/core/tests/logger-engine/classes/LoggerSObjectProxy_Tests.cls index 993ffe825..680bc019c 100644 --- a/nebula-logger/core/tests/logger-engine/classes/LoggerSObjectProxy_Tests.cls +++ b/nebula-logger/core/tests/logger-engine/classes/LoggerSObjectProxy_Tests.cls @@ -6,7 +6,8 @@ @SuppressWarnings('PMD.ApexDoc, PMD.MethodNamingConventions') @IsTest(IsParallel=true) private class LoggerSObjectProxy_Tests { - private static final Boolean IS_EXPERIENCE_CLOUD_ENABLED = System.Type.forName('NetworkMember') != null; + private static final Boolean IS_EXPERIENCE_CLOUD_ENABLED = System.Type.forName('Schema.Network') != null; + private static final Boolean IS_OMNISTUDIO_ENABLED = System.Type.forName('Schema.OmniProcess') != null; @IsTest static void it_converts_auth_session_record_to_proxy() { @@ -94,4 +95,46 @@ private class LoggerSObjectProxy_Tests { System.Assert.areEqual((String) mockNetworkRecord.get(nameFieldName), networkProxy.Name); System.Assert.areEqual((String) mockNetworkRecord.get(urlPathPrefixFieldName), networkProxy.UrlPathPrefix); } + + @IsTest + static void it_converts_omni_process_record_to_proxy() { + // No need to fail the test if it's running in an org that does not have OmniStudio enabled + if (IS_OMNISTUDIO_ENABLED == false) { + return; + } + Schema.User currentUser = new Schema.User(Id = System.UserInfo.getUserId(), Username = System.UserInfo.getUsername()); + // String createdByFieldName = 'CreatedBy'; + String createdByIdFieldName = 'CreatedById'; + String createdDateFieldName = 'CreatedDate'; + String idFieldName = 'Id'; + // String lastModifiedByFieldName = 'LastModifiedBy'; + String lastModifiedByIdFieldName = 'LastModifiedById'; + String lastModifiedDateFieldName = 'LastModifiedDate'; + String omniProcessTypeFieldName = 'OmniProcessType'; + String uniqueFieldName = 'UniqueName'; + // Some audit fields, like CreatedById, can't be set on an SObject, so start with a Map + Map mockOmniProcessUntyped = new Map{ 'attributes' => new Map{ 'type' => 'OmniProcess' } }; + mockOmniProcessUntyped.put(createdByIdFieldName, currentUser.Id); + // mockOmniProcessUntyped.put(createdByFieldName, currentUser); + mockOmniProcessUntyped.put(createdDateFieldName, System.now().addDays(-7)); + mockOmniProcessUntyped.put(lastModifiedByIdFieldName, currentUser.Id); + // mockOmniProcessUntyped.put(lastModifiedByFieldName, currentUser); + mockOmniProcessUntyped.put(lastModifiedDateFieldName, System.now().addDays(-1)); + mockOmniProcessUntyped.put(omniProcessTypeFieldName, 'Integration Procedure'); + mockOmniProcessUntyped.put(uniqueFieldName, 'Mock_OmniScript_English_1'); + SObject mockOmniProcessRecord = (SObject) System.JSON.deserialize(System.JSON.serialize(mockOmniProcessUntyped), SObject.class); + mockOmniProcessRecord.put(idFieldName, LoggerMockDataCreator.createId(mockOmniProcessRecord.getSObjectType())); + + LoggerSObjectProxy.OmniProcess omniProcessProxy = new LoggerSObjectProxy.OmniProcess(mockOmniProcessRecord); + + // System.Assert.areEqual((Schema.User) mockOmniProcessRecord.get(createdByFieldName), omniProcessProxy.CreatedBy); + System.Assert.areEqual((String) mockOmniProcessRecord.get(createdByIdFieldName), omniProcessProxy.CreatedById); + System.Assert.areEqual((Datetime) mockOmniProcessRecord.get(createdDateFieldName), omniProcessProxy.CreatedDate); + System.Assert.areEqual((Id) mockOmniProcessRecord.get(idFieldName), omniProcessProxy.Id); + // System.Assert.areEqual((Schema.User) mockOmniProcessRecord.get(lastModifiedByFieldName), omniProcessProxy.LastModifiedBy); + System.Assert.areEqual((String) mockOmniProcessRecord.get(lastModifiedByIdFieldName), omniProcessProxy.LastModifiedById); + System.Assert.areEqual((Datetime) mockOmniProcessRecord.get(lastModifiedDateFieldName), omniProcessProxy.LastModifiedDate); + System.Assert.areEqual((String) mockOmniProcessRecord.get(omniProcessTypeFieldName), omniProcessProxy.OmniProcessType); + System.Assert.areEqual((String) mockOmniProcessRecord.get(uniqueFieldName), omniProcessProxy.UniqueName); + } } diff --git a/nebula-logger/core/tests/logger-engine/classes/LoggerStackTrace_Tests.cls b/nebula-logger/core/tests/logger-engine/classes/LoggerStackTrace_Tests.cls index 5fe6c7147..54404ed98 100644 --- a/nebula-logger/core/tests/logger-engine/classes/LoggerStackTrace_Tests.cls +++ b/nebula-logger/core/tests/logger-engine/classes/LoggerStackTrace_Tests.cls @@ -225,6 +225,18 @@ private class LoggerStackTrace_Tests { System.Assert.isNull(stackTrace.Source); } + @IsTest + static void it_should_not_set_details_for_system_code_stack_trace_string() { + String namespacedPparenthesisStackTrace = '(System Code)'; + + LoggerStackTrace stackTrace = new LoggerStackTrace(namespacedPparenthesisStackTrace); + + System.Assert.areEqual(LoggerStackTrace.SourceLanguage.Apex, stackTrace.Language); + System.Assert.isNull(stackTrace.Location); + System.Assert.isNull(stackTrace.ParsedStackTraceString); + System.Assert.isNull(stackTrace.Source); + } + private class DebugStringExample { private System.Exception constructorStackTraceGenerator; private System.Exception methodStackTraceGenerator; diff --git a/nebula-logger/extra-tests/classes/name-shadowing/Schema/OmniProcess.cls b/nebula-logger/extra-tests/classes/name-shadowing/Schema/OmniProcess.cls new file mode 100644 index 000000000..26afa224d --- /dev/null +++ b/nebula-logger/extra-tests/classes/name-shadowing/Schema/OmniProcess.cls @@ -0,0 +1,10 @@ +//------------------------------------------------------------------------------------------------// +// 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. // +//------------------------------------------------------------------------------------------------// + +// This class intentionally does nothing - it's here to ensure that any references +// in Nebula Logger's codebase use `Schema.OmniProcess` instead of just `OmniProcess` +@SuppressWarnings('PMD.ApexDoc, PMD.EmptyStatementBlock') +public without sharing class OmniProcess { +} diff --git a/nebula-logger/extra-tests/classes/name-shadowing/Schema/OmniProcess.cls-meta.xml b/nebula-logger/extra-tests/classes/name-shadowing/Schema/OmniProcess.cls-meta.xml new file mode 100644 index 000000000..651b17293 --- /dev/null +++ b/nebula-logger/extra-tests/classes/name-shadowing/Schema/OmniProcess.cls-meta.xml @@ -0,0 +1,5 @@ + + + 61.0 + Active + diff --git a/nebula-logger/extra-tests/classes/name-shadowing/System/Callable.cls b/nebula-logger/extra-tests/classes/name-shadowing/System/Callable.cls new file mode 100644 index 000000000..3bd6ad27f --- /dev/null +++ b/nebula-logger/extra-tests/classes/name-shadowing/System/Callable.cls @@ -0,0 +1,10 @@ +//------------------------------------------------------------------------------------------------// +// 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. // +//------------------------------------------------------------------------------------------------// + +// This class intentionally does nothing - it's here to ensure that any references +// in Nebula Logger's codebase use `System.Callable` instead of just `Callable` +@SuppressWarnings('PMD.ApexDoc, PMD.EmptyStatementBlock') +public without sharing class Callable { +} diff --git a/nebula-logger/extra-tests/classes/name-shadowing/System/Callable.cls-meta.xml b/nebula-logger/extra-tests/classes/name-shadowing/System/Callable.cls-meta.xml new file mode 100644 index 000000000..651b17293 --- /dev/null +++ b/nebula-logger/extra-tests/classes/name-shadowing/System/Callable.cls-meta.xml @@ -0,0 +1,5 @@ + + + 61.0 + Active + diff --git a/nebula-logger/managed-package/sfdx-project.json b/nebula-logger/managed-package/sfdx-project.json index 8af3023f8..14fb313ae 100644 --- a/nebula-logger/managed-package/sfdx-project.json +++ b/nebula-logger/managed-package/sfdx-project.json @@ -7,7 +7,7 @@ "package": "Nebula Logger - Managed Package", "path": "./nebula-logger/managed-package/core", "default": true, - "definitionFile": "./config/scratch-orgs/build-base-scratch-def.json", + "definitionFile": "./config/scratch-orgs/base-scratch-def.json", "postInstallScript": "LoggerInstallHandler", "scopeProfiles": true, "ancestorVersion": "HIGHEST", diff --git a/nebula-logger/plugins/slack/README.md b/nebula-logger/plugins/slack/README.md index 291526965..dff3a2129 100644 --- a/nebula-logger/plugins/slack/README.md +++ b/nebula-logger/plugins/slack/README.md @@ -2,8 +2,8 @@ > :information_source: This plugin requires `v4.7.1` or newer of Nebula Logger's unlocked package -[![Install Unlocked Package Plugin in a Sandbox](../.images/btn-install-unlocked-package-plugin-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oERQAY) -[![Install Unlocked Package Plugin in Production](../.images/btn-install-unlocked-package-plugin-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oERQAY) +[![Install Unlocked Package Plugin in a Sandbox](../.images/btn-install-unlocked-package-plugin-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oTTQAY) +[![Install Unlocked Package Plugin in Production](../.images/btn-install-unlocked-package-plugin-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oTTQAY) Adds a Slack integration for the unlocked package edition of Nebula Logger. Any logs with log entries that meet a certain (configurable) logging level will automatically be posted to your Slack channel via an asynchronous `Queueable` job. diff --git a/nebula-logger/plugins/slack/plugin/slack/classes/SlackLoggerPlugin.cls b/nebula-logger/plugins/slack/plugin/slack/classes/SlackLoggerPlugin.cls index d8f58767c..d96f89255 100644 --- a/nebula-logger/plugins/slack/plugin/slack/classes/SlackLoggerPlugin.cls +++ b/nebula-logger/plugins/slack/plugin/slack/classes/SlackLoggerPlugin.cls @@ -275,7 +275,7 @@ public without sharing class SlackLoggerPlugin implements LoggerPlugin.Triggerab String stringValue = String.valueOf(fieldValue); Integer maxShortLength = 255; - fieldDto.isShort = stringValue.contains('\n') == false || stringValue.length() <= maxShortLength; + fieldDto.isShort = stringValue.contains('\n') == false && stringValue.length() <= maxShortLength; String valueWrapper = fieldDto.isShort ? '`' : '\n```\n'; fieldDto.value = valueWrapper + stringValue + valueWrapper; return fieldDto; diff --git a/nebula-logger/plugins/slack/plugin/slack/customMetadata/LoggerParameter.SlackEndpoint.md-meta.xml b/nebula-logger/plugins/slack/plugin/slack/customMetadata/LoggerParameter.SlackEndpoint.md-meta.xml index 402d618bb..798f1a083 100644 --- a/nebula-logger/plugins/slack/plugin/slack/customMetadata/LoggerParameter.SlackEndpoint.md-meta.xml +++ b/nebula-logger/plugins/slack/plugin/slack/customMetadata/LoggerParameter.SlackEndpoint.md-meta.xml @@ -2,10 +2,6 @@ false - - Comments__c - - Description__c The incoming webhook URL for your Slack app diff --git a/nebula-logger/plugins/slack/plugin/slack/customMetadata/LoggerParameter.SlackLogNotificationFieldSet.md-meta.xml b/nebula-logger/plugins/slack/plugin/slack/customMetadata/LoggerParameter.SlackLogNotificationFieldSet.md-meta.xml index a10748746..0812e19c7 100644 --- a/nebula-logger/plugins/slack/plugin/slack/customMetadata/LoggerParameter.SlackLogNotificationFieldSet.md-meta.xml +++ b/nebula-logger/plugins/slack/plugin/slack/customMetadata/LoggerParameter.SlackLogNotificationFieldSet.md-meta.xml @@ -2,10 +2,6 @@ false - - Comments__c - - Description__c The name of a field set on Log__c containing fields to include when sending notifications to Slack diff --git a/nebula-logger/plugins/slack/plugin/slack/customMetadata/LoggerParameter.SlackNotificationLoggingLevel.md-meta.xml b/nebula-logger/plugins/slack/plugin/slack/customMetadata/LoggerParameter.SlackNotificationLoggingLevel.md-meta.xml index eb62593a5..d1dd0f014 100644 --- a/nebula-logger/plugins/slack/plugin/slack/customMetadata/LoggerParameter.SlackNotificationLoggingLevel.md-meta.xml +++ b/nebula-logger/plugins/slack/plugin/slack/customMetadata/LoggerParameter.SlackNotificationLoggingLevel.md-meta.xml @@ -2,10 +2,6 @@ false - - Comments__c - - Description__c The logging level name that triggers a Slack notification to be sent. Possible logging levels are: ERROR, WARN, INFO, DEBUG, FINE, FINER, or FINEST diff --git a/package.json b/package.json index afdbf0d95..4aaf72cdf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nebula-logger", - "version": "4.14.9", + "version": "4.14.10", "description": "The most robust logger for Salesforce. Works with Apex, Lightning Components, Flow, Process Builder & Integrations. Designed for Salesforce admins, developers & architects.", "author": "Jonathan Gillespie", "license": "MIT", @@ -38,7 +38,6 @@ "docs:fix:apex": "pwsh ./scripts/build/generate-apex-docs.ps1 && git add ./docs/apex", "docs:fix:lwc": "pwsh ./scripts/build/generate-lwc-docs.ps1 && git add ./docs/lightning-components", "docs:verify": "pwsh ./scripts/build/verify-docs-up-to-date.ps1", - "experience:deploy": "sf project deploy start --source-dir ./config/experience-cloud --wait 30", "husky:pre-commit": "lint-staged --config ./config/linters/lint-staged.config.js", "package:version:create:managed": "pwsh ./scripts/build/create-managed-package-beta-version.ps1", "package:version:create:unlocked": "sf package version create --json --package \"Nebula Logger - Core\" --skip-ancestor-check --code-coverage --installation-key-bypass --wait 30", diff --git a/sfdx-project.json b/sfdx-project.json index b1a926582..0169bbefc 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -7,11 +7,11 @@ { "package": "Nebula Logger - Core", "path": "./nebula-logger/core", - "definitionFile": "./config/scratch-orgs/build-base-scratch-def.json", + "definitionFile": "./config/scratch-orgs/base-scratch-def.json", "scopeProfiles": true, - "versionNumber": "4.14.9.NEXT", - "versionName": "Bugfix: Apex Code Snippets Auto-Truncated", - "versionDescription": "Updated LogEntryHandler to automatically truncate the code snippets stored in OriginSourceSnippet__c and ExceptionSourceSnippet__c", + "versionNumber": "4.14.10.NEXT", + "versionName": "New CallableLogger Apex class", + "versionDescription": "Added a new CallableLogger class that provides support for both OmniStudio logging, as well as the ability to dynamically call Nebula Logger in Apex when it's available", "releaseNotesUrl": "https://github.com/jongpie/NebulaLogger/releases", "unpackagedMetadata": { "path": "./nebula-logger/extra-tests" @@ -65,9 +65,9 @@ "package": "Nebula Logger - Core@4.7.1-plugin-framework-overhaul" } ], - "versionNumber": "1.5.3.NEXT", - "versionName": "Removed ApiVersion__c in Field Set", - "versionDescription": "Removed the deprecated field Log__c.ApiVersion__c from the field set Log__c.SlackNotificationFields", + "versionNumber": "1.5.4.NEXT", + "versionName": "Fixed Formatting Text Fields with Line Breaks", + "versionDescription": "Corrected the logic used in SlackLoggerPlugin so that text fields with line breaks are formatted as code blocks", "default": false }, { @@ -194,6 +194,7 @@ "Nebula Logger - Core@4.14.7-bugfix:-us-social-security-number-data-mask-rule": "04t5Y0000015oRrQAI", "Nebula Logger - Core@4.14.8-store-httprequest-header-keys-&-values": "04t5Y0000015oS1QAI", "Nebula Logger - Core@4.14.9-bugfix:-apex-code-snippets-auto-truncated": "04t5Y0000015oSQQAY", + "Nebula Logger - Core@4.14.10-new-callablelogger-apex-class": "04t5Y0000015oTdQAI", "Nebula Logger - Core Plugin - Async Failure Additions": "0Ho5Y000000blO4SAI", "Nebula Logger - Core Plugin - Async Failure Additions@1.0.0": "04t5Y0000015lhiQAA", "Nebula Logger - Core Plugin - Async Failure Additions@1.0.1": "04t5Y0000015lhsQAA", @@ -212,6 +213,7 @@ "Nebula Logger - Core Plugin - Slack@1.5.0": "04t5Y0000015lvVQAQ", "Nebula Logger - Core Plugin - Slack@1.5.1": "04t5Y0000023Qu8QAE", "Nebula Logger - Core Plugin - Slack@1.5.2": "04t5Y0000015nDJQAY", - "Nebula Logger - Core Plugin - Slack@1.5.3": "04t5Y0000015oERQAY" + "Nebula Logger - Core Plugin - Slack@1.5.3": "04t5Y0000015oERQAY", + "Nebula Logger - Core Plugin - Slack@1.5.4": "04t5Y0000015oTTQAY" } }