From 956205deb8a7b8f6e4056189ca5b18871557f2c8 Mon Sep 17 00:00:00 2001 From: benhar Mariasoosai Date: Tue, 22 Nov 2022 17:08:52 -0600 Subject: [PATCH] Enhancements for logEntryEventStream LWC (#337) * Closed #293 - new logEntryEventStream enhancements to add split-view toggling, add full-screen mode, and add a tabular view (with fields configurable via LoggerParameter.LogEntryEventStreamDisplayFields) * Removed some problematic system log entries in LogEntryEventHandler * Updated some GitHub actions in build.yml to use the latest versions --- .github/workflows/build.yml | 28 +- README.md | 6 +- ...gEntryEventStreamDisplayFields.md-meta.xml | 19 ++ .../classes/LogEntryEventHandler.cls | 15 - .../classes/LogEntryEventStreamController.cls | 37 +++ ...LogEntryEventStreamController.cls-meta.xml | 5 + .../__tests__/logBatchPurge.test.js | 18 +- .../data/getDatatableDisplayFields.json | 1 + .../__tests__/logEntryEventStream.test.js | 206 ++++++++++++- .../logEntryEventStream.css | 29 +- .../logEntryEventStream.html | 274 +++++++++++------- .../logEntryEventStream.js | 109 ++++++- .../LoggerAdmin.permissionset-meta.xml | 4 + .../LoggerLogViewer.permissionset-meta.xml | 4 + .../main/logger-engine/classes/Logger.cls | 2 +- .../LogEntryEventStreamController_Tests.cls | 32 ++ ...ryEventStreamController_Tests.cls-meta.xml | 5 + package.json | 2 +- sfdx-project.json | 7 +- 19 files changed, 622 insertions(+), 181 deletions(-) create mode 100644 nebula-logger/core/main/configuration/customMetadata/LoggerParameter.LogEntryEventStreamDisplayFields.md-meta.xml create mode 100644 nebula-logger/core/main/log-management/classes/LogEntryEventStreamController.cls create mode 100644 nebula-logger/core/main/log-management/classes/LogEntryEventStreamController.cls-meta.xml create mode 100644 nebula-logger/core/main/log-management/lwc/logEntryEventStream/__tests__/data/getDatatableDisplayFields.json create mode 100644 nebula-logger/core/tests/log-management/classes/LogEntryEventStreamController_Tests.cls create mode 100644 nebula-logger/core/tests/log-management/classes/LogEntryEventStreamController_Tests.cls-meta.xml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2052e6c0c..d515d360b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,11 +45,11 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout source code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: 'Restore node_modules cache' id: cache-npm - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: node_modules key: npm-${{ hashFiles('**/package-lock.json') }} @@ -98,11 +98,11 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout source code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: 'Restore node_modules cache' id: cache-npm - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: node_modules key: npm-${{ hashFiles('**/package-lock.json') }} @@ -130,11 +130,11 @@ jobs: environment: 'Base Scratch Org' steps: - name: 'Checkout source code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: 'Restore node_modules cache' id: cache-npm - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: node_modules key: npm-${{ hashFiles('**/package-lock.json') }} @@ -191,11 +191,11 @@ jobs: environment: 'Experience Cloud Scratch Org' steps: - name: 'Checkout source code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: 'Restore node_modules cache' id: cache-npm - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: node_modules key: npm-${{ hashFiles('**/package-lock.json') }} @@ -265,13 +265,13 @@ jobs: environment: 'Demo Org' steps: - name: 'Checkout source code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.ref }} - name: 'Restore node_modules cache' id: cache-npm - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: node_modules key: npm-${{ hashFiles('**/package-lock.json') }} @@ -305,13 +305,13 @@ jobs: environment: 'Demo Org' steps: - name: 'Checkout source code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.ref }} - name: 'Restore node_modules cache' id: cache-npm - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: node_modules key: npm-${{ hashFiles('**/package-lock.json') }} @@ -374,11 +374,11 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout source code' - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: 'Restore node_modules cache' id: cache-npm - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: node_modules key: npm-${{ hashFiles('**/package-lock.json') }} diff --git a/README.md b/README.md index 514f592a2..3177017cc 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ The most robust logger for Salesforce. Works with Apex, Lightning Components, Flow, Process Builder & Integrations. Designed for Salesforce admins, developers & architects. -## Unlocked Package - v4.9.1 +## Unlocked Package - v4.9.2 -[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000023R79QAE) -[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000023R79QAE) +[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000023R7iQAE) +[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000023R7iQAE) [![View Documentation](./images/btn-view-documentation.png)](https://jongpie.github.io/NebulaLogger/) ## Managed Package - v4.9.0 diff --git a/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.LogEntryEventStreamDisplayFields.md-meta.xml b/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.LogEntryEventStreamDisplayFields.md-meta.xml new file mode 100644 index 000000000..715f3f440 --- /dev/null +++ b/nebula-logger/core/main/configuration/customMetadata/LoggerParameter.LogEntryEventStreamDisplayFields.md-meta.xml @@ -0,0 +1,19 @@ + + + + false + + Description__c + Contains the fields to be displayed in the Tabular view of the Log Entry Event Stream Tab under the Logger Console. + + + Value__c + ["Timestamp__c", "LoggedByUsername__c", "OriginLocation__c", "LoggingLevel__c", "Message__c"] + + diff --git a/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls b/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls index 8763eec31..498a7d7ae 100644 --- a/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls +++ b/nebula-logger/core/main/log-management/classes/LogEntryEventHandler.cls @@ -649,10 +649,6 @@ public without sharing class LogEntryEventHandler extends LoggerSObjectHandler { @future(callout=true) private static void setStatusApiDetails() { - if (LoggerParameter.ENABLE_SYSTEM_MESSAGES == true) { - Logger.debug('Logger - Calling Status API for org details'); - } - Organization organization = LoggerEngineDataSelector.getInstance().getCachedOrganization(); String statusApiEndpoint = 'https://api.status.salesforce.com/v1/instances/' + organization.InstanceName + '/status'; @@ -661,7 +657,6 @@ public without sharing class LogEntryEventHandler extends LoggerSObjectHandler { request.setMethod('GET'); HttpResponse response = new Http().send(request); - if (response.getStatusCode() >= 400) { String errorMessage = 'Callout failed for ' + @@ -674,10 +669,6 @@ public without sharing class LogEntryEventHandler extends LoggerSObjectHandler { } StatusApiResponse statusApiResponse = (StatusApiResponse) JSON.deserialize(response.getBody(), StatusApiResponse.class); - if (LoggerParameter.ENABLE_SYSTEM_MESSAGES == true) { - Logger.debug('Logger - Status API response: ' + statusApiResponse); - } - List logsToUpdate = new List(); for (Log__c log : [ SELECT Id @@ -691,13 +682,7 @@ public without sharing class LogEntryEventHandler extends LoggerSObjectHandler { logsToUpdate.add(log); } - - if (LoggerParameter.ENABLE_SYSTEM_MESSAGES == true) { - Logger.debug('Logger - logs to update: ' + logsToUpdate); - } - LoggerDataStore.getDatabase().updateRecords(logsToUpdate); - Logger.saveLog(); } // Private class for handling the response from api.status.salesforce.com diff --git a/nebula-logger/core/main/log-management/classes/LogEntryEventStreamController.cls b/nebula-logger/core/main/log-management/classes/LogEntryEventStreamController.cls new file mode 100644 index 000000000..3b416d551 --- /dev/null +++ b/nebula-logger/core/main/log-management/classes/LogEntryEventStreamController.cls @@ -0,0 +1,37 @@ +//------------------------------------------------------------------------------------------------// +// This file is part of the Nebula Logger project, released under the MIT License. // +// See LICENSE file or go to https://github.com/jongpie/NebulaLogger for full license details. // +//------------------------------------------------------------------------------------------------// + +/** + * @group Log Management + * @description Controller class for lwc `logEntryEventStream`, used to stream Log Entries in console and Tabular view. + */ +@SuppressWarnings('PMD.ApexCRUDViolation, PMD.CyclomaticComplexity, PMD.ExcessivePublicCount') +public with sharing class LogEntryEventStreamController { + @TestVisible + private static final String DISPLAY_FIELDS_PARAMETER_NAME = 'LogEntryEventStreamDisplayFields'; + @TestVisible + private static final List DEFAULT_DISPLAY_FIELDS = new List{ + Schema.LogEntryEvent__e.Timestamp__c.getDescribe().getLocalName(), + Schema.LogEntryEvent__e.LoggedByUsername__c.getDescribe().getLocalName(), + Schema.LogEntryEvent__e.OriginLocation__c.getDescribe().getLocalName(), + Schema.LogEntryEvent__e.LoggingLevel__c.getDescribe().getLocalName(), + Schema.LogEntryEvent__e.Message__c.getDescribe().getLocalName() + }; + /** + * @description Returns the list of columns to be displayed in + * LogEntryEventStream Datatable. The fields are configured + * in the Custom Meta Data (LoggerParameter.LogEntryEventStreamDisplayFields). + * @return The instance of `List`, containing the list of columns to be displayed in + * LogEntryEventStream Datatable . + */ + @AuraEnabled + public static List getDatatableDisplayFields() { + try { + return LoggerParameter.getStringList(DISPLAY_FIELDS_PARAMETER_NAME, DEFAULT_DISPLAY_FIELDS); + } catch (Exception e) { + throw new AuraHandledException(e.getMessage()); + } + } +} diff --git a/nebula-logger/core/main/log-management/classes/LogEntryEventStreamController.cls-meta.xml b/nebula-logger/core/main/log-management/classes/LogEntryEventStreamController.cls-meta.xml new file mode 100644 index 000000000..1248daa86 --- /dev/null +++ b/nebula-logger/core/main/log-management/classes/LogEntryEventStreamController.cls-meta.xml @@ -0,0 +1,5 @@ + + + 56.0 + Active + diff --git a/nebula-logger/core/main/log-management/lwc/logBatchPurge/__tests__/logBatchPurge.test.js b/nebula-logger/core/main/log-management/lwc/logBatchPurge/__tests__/logBatchPurge.test.js index a930a3d68..28a73ba33 100644 --- a/nebula-logger/core/main/log-management/lwc/logBatchPurge/__tests__/logBatchPurge.test.js +++ b/nebula-logger/core/main/log-management/lwc/logBatchPurge/__tests__/logBatchPurge.test.js @@ -156,9 +156,9 @@ describe('logBatchPurge lwc tests', () => { const runPurgeBatch = logBatchPurgeElement.shadowRoot.querySelector('lightning-button[data-id="run-purge-button"]'); expect(runPurgeBatch).toBeTruthy(); - const purgeBatchJobsDataTable = logBatchPurgeElement.shadowRoot.querySelector('lightning-datatable[data-id="purge-batch-jobs"'); - expect(purgeBatchJobsDataTable).toBeTruthy(); - expect(purgeBatchJobsDataTable.data).toEqual(mockGetPurgeBatchJobRecords); + const purgeBatchJobsDatatable = logBatchPurgeElement.shadowRoot.querySelector('lightning-datatable[data-id="purge-batch-jobs"'); + expect(purgeBatchJobsDatatable).toBeTruthy(); + expect(purgeBatchJobsDatatable.data).toEqual(mockGetPurgeBatchJobRecords); }); it('displays log metrics for today by default', async () => { @@ -331,9 +331,9 @@ describe('logBatchPurge lwc tests', () => { it('displays the purge job records in datatable.', async () => { const logBatchPurgeElement = await initializeElement(true); - const purgeBatchJobsDataTable = logBatchPurgeElement.shadowRoot.querySelector('lightning-datatable[data-id="purge-batch-jobs"'); - expect(purgeBatchJobsDataTable).toBeTruthy(); - expect(purgeBatchJobsDataTable.data).toEqual(mockGetPurgeBatchJobRecords); + const purgeBatchJobsDatatable = logBatchPurgeElement.shadowRoot.querySelector('lightning-datatable[data-id="purge-batch-jobs"'); + expect(purgeBatchJobsDatatable).toBeTruthy(); + expect(purgeBatchJobsDatatable.data).toEqual(mockGetPurgeBatchJobRecords); }); it('it show success toast when user click on run batch button.', async () => { @@ -357,10 +357,10 @@ describe('logBatchPurge lwc tests', () => { await Promise.resolve('resolves loadpurgeBatchJobRecords()'); await Promise.resolve('resolves canUserRunLogBatchPurger()'); - const purgeBatchJobsDataTable = logBatchPurgeElement.shadowRoot.querySelector('lightning-datatable[data-id="purge-batch-jobs"'); - expect(purgeBatchJobsDataTable).toBeTruthy(); + const purgeBatchJobsDatatable = logBatchPurgeElement.shadowRoot.querySelector('lightning-datatable[data-id="purge-batch-jobs"'); + expect(purgeBatchJobsDatatable).toBeTruthy(); - expect(purgeBatchJobsDataTable.data).toEqual(mockGetPurgeBatchJobRecords); + expect(purgeBatchJobsDatatable.data).toEqual(mockGetPurgeBatchJobRecords); }); it('it refresh the purge batch records for every 10 sec.', async () => { diff --git a/nebula-logger/core/main/log-management/lwc/logEntryEventStream/__tests__/data/getDatatableDisplayFields.json b/nebula-logger/core/main/log-management/lwc/logEntryEventStream/__tests__/data/getDatatableDisplayFields.json new file mode 100644 index 000000000..5d8e62966 --- /dev/null +++ b/nebula-logger/core/main/log-management/lwc/logEntryEventStream/__tests__/data/getDatatableDisplayFields.json @@ -0,0 +1 @@ +["Timestamp__c", "LoggedByUsername__c"] diff --git a/nebula-logger/core/main/log-management/lwc/logEntryEventStream/__tests__/logEntryEventStream.test.js b/nebula-logger/core/main/log-management/lwc/logEntryEventStream/__tests__/logEntryEventStream.test.js index 7c4369e7d..43727a3e4 100644 --- a/nebula-logger/core/main/log-management/lwc/logEntryEventStream/__tests__/logEntryEventStream.test.js +++ b/nebula-logger/core/main/log-management/lwc/logEntryEventStream/__tests__/logEntryEventStream.test.js @@ -2,6 +2,7 @@ import { createElement } from 'lwc'; import LogEntryEventStream from 'c/logEntryEventStream'; import { jestMockPublish } from 'lightning/empApi'; import getSchemaForName from '@salesforce/apex/LoggerSObjectMetadata.getSchemaForName'; +import getDatatableDisplayFields from '@salesforce/apex/LogEntryEventStreamController.getDatatableDisplayFields'; const loggingLevels = { FINEST: 2, @@ -26,6 +27,7 @@ const mockLogEntryEventTemplate = { }; const mockLogEntryEventSchemaTemplate = require('./data/getSchemaForName.json'); +const mockTableViewDisplayFields = require('./data/getDatatableDisplayFields.json'); jest.mock( '@salesforce/apex/LoggerSObjectMetadata.getSchemaForName', @@ -36,15 +38,27 @@ jest.mock( }, { virtual: true } ); +jest.mock( + '@salesforce/apex/LogEntryEventStreamController.getDatatableDisplayFields', + () => { + return { + default: jest.fn() + }; + }, + { virtual: true } +); + +const flushPromises = () => new Promise(process.nextTick); async function createStreamElement(namespace) { + getDatatableDisplayFields.mockResolvedValue(mockTableViewDisplayFields); const mockLogEntryEventSchema = generateLogEntryEventSchema(namespace); getSchemaForName.mockResolvedValue(mockLogEntryEventSchema); const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); document.body.appendChild(element); - await Promise.resolve(); + await flushPromises(); return element; } @@ -112,6 +126,7 @@ describe('LogEntryEventStream tests', () => { }); const namespaces = ['', 'SomeNamespace']; + it('streams a single log entry event', async () => { await Promise.all( namespaces.map(async namespace => { @@ -126,6 +141,7 @@ describe('LogEntryEventStream tests', () => { }) ); }); + it('toggles streaming when button clicked', async () => { await Promise.all( namespaces.map(async namespace => { @@ -172,6 +188,7 @@ describe('LogEntryEventStream tests', () => { const loggingLevelFilterDropdown = element.shadowRoot.querySelector('lightning-combobox[data-id="loggingLevelFilter"]'); loggingLevelFilterDropdown.value = loggingLevels.DEBUG; loggingLevelFilterDropdown.dispatchEvent(new CustomEvent('change')); + await flushPromises(); const matchingLogEntryEvent = generatePlatformEvent(namespace); const namespacePrefix = !!namespace ? namespace + '__' : ''; @@ -180,7 +197,6 @@ describe('LogEntryEventStream tests', () => { expect(matchingLogEntryEvent[namespacePrefix + 'LoggingLevelOrdinal__c']).toBeGreaterThan(Number(loggingLevelFilterDropdown.value)); await publishPlatformEvent(namespace, matchingLogEntryEvent); - const expectedStreamText = getPlatformEventText(matchingLogEntryEvent, namespace); const eventStreamDiv = element.shadowRoot.querySelector('.event-stream'); expect(eventStreamDiv.textContent).toBe(expectedStreamText); @@ -211,12 +227,14 @@ describe('LogEntryEventStream tests', () => { }); it('includes matching log entry event for origin type filter', async () => { getSchemaForName.mockResolvedValue(mockLogEntryEventSchemaTemplate); + getDatatableDisplayFields.mockResolvedValue(mockTableViewDisplayFields); const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); document.body.appendChild(element); - await Promise.resolve(); + await flushPromises(); + const originTypeFilterDropdown = element.shadowRoot.querySelector('lightning-combobox[data-id="originTypeFilter"]'); originTypeFilterDropdown.value = 'Flow'; originTypeFilterDropdown.dispatchEvent(new CustomEvent('change')); @@ -236,16 +254,18 @@ describe('LogEntryEventStream tests', () => { }); it('excludes non-matching log entry event for origin type filter', async () => { getSchemaForName.mockResolvedValue(mockLogEntryEventSchemaTemplate); - + getDatatableDisplayFields.mockResolvedValue(mockTableViewDisplayFields); const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); document.body.appendChild(element); - await Promise.resolve(); + const originTypeFilterDropdown = element.shadowRoot.querySelector('lightning-combobox[data-id="originTypeFilter"]'); originTypeFilterDropdown.value = 'Flow'; originTypeFilterDropdown.dispatchEvent(new CustomEvent('change')); + await flushPromises(); + const nonMatchingLogEntryEvent = { ...mockLogEntryEventTemplate }; nonMatchingLogEntryEvent.OriginType__c = 'Apex'; expect(nonMatchingLogEntryEvent.OriginType__c).not.toEqual(originTypeFilterDropdown.value); @@ -260,15 +280,16 @@ describe('LogEntryEventStream tests', () => { }); it('includes matching log entry event for origin location filter', async () => { getSchemaForName.mockResolvedValue(mockLogEntryEventSchemaTemplate); - + getDatatableDisplayFields.mockResolvedValue(mockTableViewDisplayFields); const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); document.body.appendChild(element); - await Promise.resolve(); + const originLocationFilterDropdown = element.shadowRoot.querySelector('lightning-input[data-id="originLocationFilter"]'); originLocationFilterDropdown.value = 'SomeClass.someMethod'; originLocationFilterDropdown.dispatchEvent(new CustomEvent('change')); + await flushPromises(); const matchingLogEntryEvent = { ...mockLogEntryEventTemplate }; matchingLogEntryEvent.OriginLocation__c = 'SomeClass.someMethod'; @@ -285,16 +306,19 @@ describe('LogEntryEventStream tests', () => { }); it('excludes non-matching log entry event for origin location filter', async () => { getSchemaForName.mockResolvedValue(mockLogEntryEventSchemaTemplate); + getDatatableDisplayFields.mockResolvedValue(mockTableViewDisplayFields); const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); document.body.appendChild(element); - await Promise.resolve(); + const originLocationFilterDropdown = element.shadowRoot.querySelector('lightning-input[data-id="originLocationFilter"]'); originLocationFilterDropdown.value = 'SomeClass.someMethod'; originLocationFilterDropdown.dispatchEvent(new CustomEvent('change')); + await flushPromises(); + const nonMatchingLogEntryEvent = { ...mockLogEntryEventTemplate }; nonMatchingLogEntryEvent.OriginLocation__c = 'AnotherClass.someOtherMethod'; expect(nonMatchingLogEntryEvent.OriginLocation__c).not.toEqual(originLocationFilterDropdown.value); @@ -309,15 +333,17 @@ describe('LogEntryEventStream tests', () => { }); it('includes matching log entry event for logged by filter', async () => { getSchemaForName.mockResolvedValue(mockLogEntryEventSchemaTemplate); + getDatatableDisplayFields.mockResolvedValue(mockTableViewDisplayFields); const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); document.body.appendChild(element); - await Promise.resolve(); + const originLocationFilterDropdown = element.shadowRoot.querySelector('lightning-input[data-id="loggedByFilter"]'); originLocationFilterDropdown.value = 'some.person@test.com'; originLocationFilterDropdown.dispatchEvent(new CustomEvent('change')); + await flushPromises(); const matchingLogEntryEvent = { ...mockLogEntryEventTemplate }; matchingLogEntryEvent.LoggedByUsername__c = 'some.person@test.com'; @@ -334,12 +360,14 @@ describe('LogEntryEventStream tests', () => { }); it('excludes non-matching log entry event for logged by filter', async () => { getSchemaForName.mockResolvedValue(mockLogEntryEventSchemaTemplate); + getDatatableDisplayFields.mockResolvedValue(mockTableViewDisplayFields); const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); document.body.appendChild(element); - await Promise.resolve(); + await flushPromises(); + const originLocationFilterDropdown = element.shadowRoot.querySelector('lightning-input[data-id="loggedByFilter"]'); originLocationFilterDropdown.value = 'some.person@test.com'; originLocationFilterDropdown.dispatchEvent(new CustomEvent('change')); @@ -358,12 +386,13 @@ describe('LogEntryEventStream tests', () => { }); it('includes matching log entry event using string for message filter', async () => { getSchemaForName.mockResolvedValue(mockLogEntryEventSchemaTemplate); - + getDatatableDisplayFields.mockResolvedValue(mockTableViewDisplayFields); const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); document.body.appendChild(element); - await Promise.resolve(); + await flushPromises(); + const messageFilterTextarea = element.shadowRoot.querySelector('lightning-textarea[data-id="messageFilter"]'); messageFilterTextarea.value = 'matching text'; messageFilterTextarea.dispatchEvent(new CustomEvent('change')); @@ -383,12 +412,14 @@ describe('LogEntryEventStream tests', () => { }); it('excludes non-matching log entry event for message filter', async () => { getSchemaForName.mockResolvedValue(mockLogEntryEventSchemaTemplate); + getDatatableDisplayFields.mockResolvedValue(mockTableViewDisplayFields); const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); document.body.appendChild(element); - await Promise.resolve(); + await flushPromises(); + const messageFilterTextarea = element.shadowRoot.querySelector('lightning-textarea[data-id="messageFilter"]'); messageFilterTextarea.value = 'non-matching text'; messageFilterTextarea.dispatchEvent(new CustomEvent('change')); @@ -408,16 +439,18 @@ describe('LogEntryEventStream tests', () => { it('includes matching log entry event using regex for message filter', async () => { getSchemaForName.mockResolvedValue(mockLogEntryEventSchemaTemplate); + getDatatableDisplayFields.mockResolvedValue(mockTableViewDisplayFields); const element = createElement('log-entry-event-stream', { is: LogEntryEventStream }); document.body.appendChild(element); - await Promise.resolve(); + await flushPromises(); const messageFilterTextarea = element.shadowRoot.querySelector('lightning-textarea[data-id="messageFilter"]'); messageFilterTextarea.value = 'Something.+? blah$'; messageFilterTextarea.dispatchEvent(new CustomEvent('change')); + await Promise.resolve(); const matchingLogEntryEvent = { ...mockLogEntryEventTemplate }; matchingLogEntryEvent.Message__c = 'Something, something, something, beep boop beep!!!!!!!%@#$!%, blah, blah, blah'; @@ -431,4 +464,149 @@ describe('LogEntryEventStream tests', () => { const eventStreamDiv = element.shadowRoot.querySelector('.event-stream'); expect(eventStreamDiv.textContent).toBe(expectedStreamText); }); + + it('it should show the splitview as expanded by default', async () => { + getDatatableDisplayFields.mockResolvedValue(mockTableViewDisplayFields); + getSchemaForName.mockResolvedValue(mockLogEntryEventSchemaTemplate); + + const element = createElement('log-entry-event-stream', { + is: LogEntryEventStream + }); + const nonMatchingLogEntryEvent = { ...mockLogEntryEventTemplate }; + nonMatchingLogEntryEvent.LoggedByUsername__c = 'a.different.person@test.com'; + await jestMockPublish('/event/LogEntryEvent__e', { + data: { + payload: nonMatchingLogEntryEvent + } + }); + document.body.appendChild(element); + await flushPromises(); + + const splitViewContainer = element.shadowRoot.querySelector('[data-id="split-view-container"]'); + expect(splitViewContainer.className).toContain('slds-is-open'); + + const toggleSplitViewButton = element.shadowRoot.querySelector('[data-id="split-view-button"]'); + expect(toggleSplitViewButton.className).toContain('slds-is-open'); + + const eventStreamDiv = element.shadowRoot.querySelector('.event-stream'); + }); + it('it should colapse the split view when user click on the splitview panel button', async () => { + getDatatableDisplayFields.mockResolvedValue(mockTableViewDisplayFields); + getSchemaForName.mockResolvedValue(mockLogEntryEventSchemaTemplate); + + const element = createElement('log-entry-event-stream', { + is: LogEntryEventStream + }); + const nonMatchingLogEntryEvent = { ...mockLogEntryEventTemplate }; + nonMatchingLogEntryEvent.LoggedByUsername__c = 'a.different.person@test.com'; + await jestMockPublish('/event/LogEntryEvent__e', { + data: { + payload: nonMatchingLogEntryEvent + } + }); + + document.body.appendChild(element); + await flushPromises(); + + const splitViewContainer = element.shadowRoot.querySelector('[data-id="split-view-container"]'); + const toggleSplitViewButton = element.shadowRoot.querySelector('[data-id="split-view-button"]'); + toggleSplitViewButton.click(); + + expect(splitViewContainer.className).toContain('slds-is-closed'); + expect(toggleSplitViewButton.className).toContain('slds-is-closed'); + + const eventStreamDiv = element.shadowRoot.querySelector('.event-stream'); + }); + + it('it should expand the console when user click on the expand button', async () => { + getDatatableDisplayFields.mockResolvedValue(mockTableViewDisplayFields); + getSchemaForName.mockResolvedValue(mockLogEntryEventSchemaTemplate); + + const element = createElement('log-entry-event-stream', { + is: LogEntryEventStream + }); + const nonMatchingLogEntryEvent = { ...mockLogEntryEventTemplate }; + nonMatchingLogEntryEvent.LoggedByUsername__c = 'a.different.person@test.com'; + await jestMockPublish('/event/LogEntryEvent__e', { + data: { + payload: nonMatchingLogEntryEvent + } + }); + + document.body.appendChild(element); + await flushPromises(); + + const toggleExpandButton = element.shadowRoot.querySelector('lightning-button-stateful[data-id="expand-toggle"]'); + toggleExpandButton.click(); + + const consoleBlock = element.shadowRoot.querySelector('[data-id="event-stream-console"]'); + expect(consoleBlock.className).toContain('expanded'); + }); + + it('it should exit full screen mode when user click on the contract button', async () => { + getDatatableDisplayFields.mockResolvedValue(mockTableViewDisplayFields); + getSchemaForName.mockResolvedValue(mockLogEntryEventSchemaTemplate); + + const element = createElement('log-entry-event-stream', { + is: LogEntryEventStream + }); + const nonMatchingLogEntryEvent = { ...mockLogEntryEventTemplate }; + nonMatchingLogEntryEvent.LoggedByUsername__c = 'a.different.person@test.com'; + await jestMockPublish('/event/LogEntryEvent__e', { + data: { + payload: nonMatchingLogEntryEvent + } + }); + + document.body.appendChild(element); + await flushPromises(); + + const toggleExpandButton = element.shadowRoot.querySelector('lightning-button-stateful[data-id="expand-toggle"]'); + toggleExpandButton.click(); //expanded + toggleExpandButton.click(); //contracted + + const consoleBlock = element.shadowRoot.querySelector('[data-id="event-stream-console"]'); + expect(consoleBlock.className).not.toContain('expanded'); + }); + + it('it should show log entries in console when user select console view ', async () => { + await Promise.all( + namespaces.map(async namespace => { + const element = await createStreamElement(namespace); + const mockLogEntryEvent = await generatePlatformEvent(namespace); + + await publishPlatformEvent(namespace, mockLogEntryEvent); + const buttonMenu = element.shadowRoot.querySelector('lightning-button-menu'); + buttonMenu.dispatchEvent(new CustomEvent('select', { detail: { value: 'console' } })); + await flushPromises(); + + const datatable = element.shadowRoot.querySelector('[data-id="event-stream-datatable"]'); + expect(datatable).toBe(null); + const expectedStreamText = getPlatformEventText(mockLogEntryEvent, namespace); + const eventStreamDiv = element.shadowRoot.querySelector('.event-stream'); + expect(eventStreamDiv.textContent).toBe(expectedStreamText); + }) + ); + }); + + it('it should show log entries in datatable when user select tabular view ', async () => { + await Promise.all( + namespaces.map(async namespace => { + const element = await createStreamElement(namespace); + const mockLogEntryEvent = await generatePlatformEvent(namespace); + + await publishPlatformEvent(namespace, mockLogEntryEvent); + const buttonMenu = element.shadowRoot.querySelector('lightning-button-menu'); + buttonMenu.dispatchEvent(new CustomEvent('select', { detail: { value: 'table' } })); + await flushPromises(); + + const datatable = element.shadowRoot.querySelector('[data-id="event-stream-datatable"]'); + expect(datatable.columns.length).toBe(2); + expect(datatable.data.length).toBe(1); + const expectedStreamText = getPlatformEventText(mockLogEntryEvent, namespace); + const eventStreamDiv = element.shadowRoot.querySelector('.event-stream'); + expect(eventStreamDiv).toBe(null); + }) + ); + }); }); diff --git a/nebula-logger/core/main/log-management/lwc/logEntryEventStream/logEntryEventStream.css b/nebula-logger/core/main/log-management/lwc/logEntryEventStream/logEntryEventStream.css index 0d07c6497..0974f8a20 100644 --- a/nebula-logger/core/main/log-management/lwc/logEntryEventStream/logEntryEventStream.css +++ b/nebula-logger/core/main/log-management/lwc/logEntryEventStream/logEntryEventStream.css @@ -2,13 +2,11 @@ position: fixed; top: 0; left: 0; - height: 100%; - width: 100%; - z-index: 999; -} - -.expanded .event-stream { height: 100vh; + width: 100vw; + z-index: 9999; + padding: 0; + margin: 0; } .event-stream { @@ -17,8 +15,7 @@ font-family: 'Lucida Console', 'Courier New', monospace; text-indent: -32px; padding-left: 32px; - height: calc(100vh - 300px); - margin-right: 8px; + height: 100%; overflow-x: hidden; overflow-y: scroll; @@ -53,3 +50,19 @@ margin-right: 8px; padding: 4px; } + +.main-container { + display: flex; + width: 100vw; +} +.event-stream-container { + width: calc(100vw - 24rem); +} + +.height-full { + height: 100%; +} + +.card-body { + height: calc(100% - 70px); +} diff --git a/nebula-logger/core/main/log-management/lwc/logEntryEventStream/logEntryEventStream.html b/nebula-logger/core/main/log-management/lwc/logEntryEventStream/logEntryEventStream.html index c1500d0e5..40a7c83b1 100644 --- a/nebula-logger/core/main/log-management/lwc/logEntryEventStream/logEntryEventStream.html +++ b/nebula-logger/core/main/log-management/lwc/logEntryEventStream/logEntryEventStream.html @@ -4,114 +4,186 @@ **********************************************************************************************-->