From c6839bde5b0d8a444ceadc510e5682eb6e546d00 Mon Sep 17 00:00:00 2001 From: Ryan Schierholz Date: Mon, 2 Dec 2024 17:08:36 -0700 Subject: [PATCH 01/33] feat: Add App_Logs_Home dashboard page for unified logging view --- .../App_Logs_Home.flexipage-meta.xml | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 force-app/main/default/flexipages/App_Logs_Home.flexipage-meta.xml diff --git a/force-app/main/default/flexipages/App_Logs_Home.flexipage-meta.xml b/force-app/main/default/flexipages/App_Logs_Home.flexipage-meta.xml new file mode 100644 index 0000000..a4b0bd0 --- /dev/null +++ b/force-app/main/default/flexipages/App_Logs_Home.flexipage-meta.xml @@ -0,0 +1,62 @@ + + + + + + logReader + c_logReader + + + region1 + Region + + + + + + enableInlineEdit + true + + + entityName + AppLog__c + + + filterName + All + + + hideActionBar + false + + + hideSearchBar + false + + + pageSize + 3 + + flexipage:filterListCard + flexipage_filterListCard + + + region2 + Region + + + + + appLogStorage + c_appLogStorage + + + region3 + Region + + App Logs Home + + AppPage + From f4d9996c9f59723c59a8bf6ed03f001559ca5d8d Mon Sep 17 00:00:00 2001 From: Ryan Schierholz Date: Tue, 3 Dec 2024 06:50:59 -0700 Subject: [PATCH 02/33] feat: Add @AuraEnabled to logging methods for LWC support --- force-app/main/default/classes/LogService.cls | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/force-app/main/default/classes/LogService.cls b/force-app/main/default/classes/LogService.cls index 4ce8c4a..9382740 100644 --- a/force-app/main/default/classes/LogService.cls +++ b/force-app/main/default/classes/LogService.cls @@ -20,6 +20,7 @@ global without sharing class LogService { * @param message message to be logged * @param className . if applicable */ + @AuraEnabled(cacheable=false) global static void debug(String message, String className) { logger.debug(message, className); } @@ -30,6 +31,7 @@ global without sharing class LogService { * @param message message to be logged * @param className . if applicable */ + @AuraEnabled(cacheable=false) global static void info(String message, String className) { logger.info(message, className); } @@ -40,6 +42,7 @@ global without sharing class LogService { * @param message message to be logged * @param className . if applicable */ + @AuraEnabled(cacheable=false) global static void warn(String message, String className) { logger.warn(message, className); } @@ -124,4 +127,4 @@ global without sharing class LogService { @InvocableVariable(Label='Object Id' Description='Id of object error occurred on') global String affectedId; } -} \ No newline at end of file +} \ No newline at end of file From 786971dec025acc88828d55a54fc4ab051629c14 Mon Sep 17 00:00:00 2001 From: Ryan Schierholz <33879609+RyanSchierholz@users.noreply.github.com> Date: Thu, 19 Dec 2024 08:48:33 -0700 Subject: [PATCH 03/33] Delete force-app/main/default/flexipages/App_Logs_Home.flexipage-meta.xml Home page is in a different PR --- .../App_Logs_Home.flexipage-meta.xml | 62 ------------------- 1 file changed, 62 deletions(-) delete mode 100644 force-app/main/default/flexipages/App_Logs_Home.flexipage-meta.xml diff --git a/force-app/main/default/flexipages/App_Logs_Home.flexipage-meta.xml b/force-app/main/default/flexipages/App_Logs_Home.flexipage-meta.xml deleted file mode 100644 index a4b0bd0..0000000 --- a/force-app/main/default/flexipages/App_Logs_Home.flexipage-meta.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - logReader - c_logReader - - - region1 - Region - - - - - - enableInlineEdit - true - - - entityName - AppLog__c - - - filterName - All - - - hideActionBar - false - - - hideSearchBar - false - - - pageSize - 3 - - flexipage:filterListCard - flexipage_filterListCard - - - region2 - Region - - - - - appLogStorage - c_appLogStorage - - - region3 - Region - - App Logs Home - - AppPage - From fab7b379d5c3286e70bb38fa9639dcb42eaf80a4 Mon Sep 17 00:00:00 2001 From: Ryan Schierholz <33879609+RyanSchierholz@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:48:19 -0700 Subject: [PATCH 04/33] Feature/app log writer clean (#3) * feat: Add AppLogWriter LWC with Lightning Toast notifications * feat: Improve appLogWriter using Lightning Components in HTML * Add .localdevserver to gitignore * Add .localdevserver to gitignore --------- Co-authored-by: Ryan Schierholz --- .gitignore | 3 +- .../lwc/appLogWriter/appLogWriter.html | 27 ++++++++ .../default/lwc/appLogWriter/appLogWriter.js | 66 +++++++++++++++++++ .../lwc/appLogWriter/appLogWriter.js-meta.xml | 11 ++++ 4 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 force-app/main/default/lwc/appLogWriter/appLogWriter.html create mode 100644 force-app/main/default/lwc/appLogWriter/appLogWriter.js create mode 100644 force-app/main/default/lwc/appLogWriter/appLogWriter.js-meta.xml diff --git a/.gitignore b/.gitignore index 7bbe5a6..99466b6 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ IlluminatedCloud **/profileSessionSettings/**.* force-app/main/default/settings/ -force-app/main/default/profiles/ \ No newline at end of file +force-app/main/default/profiles/.localdevserver/ +.localdevserver/ diff --git a/force-app/main/default/lwc/appLogWriter/appLogWriter.html b/force-app/main/default/lwc/appLogWriter/appLogWriter.html new file mode 100644 index 0000000..f57a5b7 --- /dev/null +++ b/force-app/main/default/lwc/appLogWriter/appLogWriter.html @@ -0,0 +1,27 @@ + + \ No newline at end of file diff --git a/force-app/main/default/lwc/appLogWriter/appLogWriter.js b/force-app/main/default/lwc/appLogWriter/appLogWriter.js new file mode 100644 index 0000000..3457cbb --- /dev/null +++ b/force-app/main/default/lwc/appLogWriter/appLogWriter.js @@ -0,0 +1,66 @@ +// appLogWriter.js +import { LightningElement } from 'lwc'; +import { ShowToastEvent } from 'lightning/platformShowToastEvent'; +import debug from '@salesforce/apex/LogService.debug'; +import info from '@salesforce/apex/LogService.info'; +import warn from '@salesforce/apex/LogService.warn'; + +export default class AppLogWriter extends LightningElement { + message = ''; + + get logLevelOptions() { + return [ + { label: 'INFO', value: 'INFO', iconName: 'utility:info' }, + { label: 'DEBUG', value: 'DEBUG', iconName: 'utility:bug' }, + { label: 'WARN', value: 'WARN', iconName: 'utility:warning' } + ]; + } + + get isSubmitDisabled() { + return !this.message || this.message.trim().length === 0; + } + + handleLogLevelChange(event) { + this.logLevel = event.target.value; + } + + handleMessageChange(event) { + this.message = event.target.value; + } + + async handleSubmit(event) { + try { + const className = 'AppLogWriter.component'; + + switch(event.target.value) { + case 'DEBUG': + await debug({ message: this.message, className: className }); + break; + case 'INFO': + await info({ message: this.message, className: className }); + break; + case 'WARN': + await warn({ message: this.message, className: className }); + break; + } + + this.showToast('Success', 'Log entry created successfully', 'success'); + this.clearForm(); + } catch (error) { + this.showToast('Error', 'Error creating log entry: ' + error.message, 'error'); + } + } + + clearForm() { + this.message = ''; + } + + showToast(title, message, variant) { + const event = new ShowToastEvent({ + title: title, + message: message, + variant: variant + }); + this.dispatchEvent(event); + } +} \ No newline at end of file diff --git a/force-app/main/default/lwc/appLogWriter/appLogWriter.js-meta.xml b/force-app/main/default/lwc/appLogWriter/appLogWriter.js-meta.xml new file mode 100644 index 0000000..6e19b32 --- /dev/null +++ b/force-app/main/default/lwc/appLogWriter/appLogWriter.js-meta.xml @@ -0,0 +1,11 @@ + + + 62.0 + true + App Log Writer + Component for creating log entries with different log levels + + lightning__AppPage + lightning__HomePage + + \ No newline at end of file From 9efd7269f7fdfdccaea32fbdf57a6eafe1d3c62f Mon Sep 17 00:00:00 2001 From: Ryan Schierholz <33879609+RyanSchierholz@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:50:03 -0700 Subject: [PATCH 05/33] Feature/log reader toolbar improvements (#4) * feat:Improved Button and UI Layout for App Log Reader * Delete force-app/main/default/flexipages/App_Logs_Home.flexipage-meta.xml --- .../main/default/lwc/logReader/logReader.css | 18 -- .../main/default/lwc/logReader/logReader.html | 158 +++++++++--------- .../main/default/lwc/logReader/logReader.js | 63 +++++-- 3 files changed, 125 insertions(+), 114 deletions(-) diff --git a/force-app/main/default/lwc/logReader/logReader.css b/force-app/main/default/lwc/logReader/logReader.css index 307f346..fa96b74 100644 --- a/force-app/main/default/lwc/logReader/logReader.css +++ b/force-app/main/default/lwc/logReader/logReader.css @@ -5,22 +5,4 @@ border: 1px solid #ccc; border-collapse: collapse; margin: 2em 0; -} - -.log-level{ - margin-right: 1em; -} - -.logs-per-page{ - display: inline-block; - width: 6rem; -} - -.refresh { - padding: 4px; -} - -.right-tool-bar { - text-align: right; - margin-top: -24px; } \ No newline at end of file diff --git a/force-app/main/default/lwc/logReader/logReader.html b/force-app/main/default/lwc/logReader/logReader.html index f946315..f127a1b 100644 --- a/force-app/main/default/lwc/logReader/logReader.html +++ b/force-app/main/default/lwc/logReader/logReader.html @@ -1,81 +1,88 @@ \ No newline at end of file diff --git a/force-app/main/default/lwc/logReader/logReader.js b/force-app/main/default/lwc/logReader/logReader.js index c25a542..efce272 100644 --- a/force-app/main/default/lwc/logReader/logReader.js +++ b/force-app/main/default/lwc/logReader/logReader.js @@ -1,6 +1,8 @@ /** * author mikelockett * date 2020-15-03 + * contributor: Ryan Schierholz + * updated: 2024-12-20 */ import { LightningElement, track } from "lwc"; import { subscribe, unsubscribe } from "lightning/empApi"; @@ -41,22 +43,19 @@ const defaultColumns = [ export default class LogReader extends LightningElement { @track logs; - @track - logsPerPage = "10"; - logsPerPageOptions = [ - { label: "10", value: "10" }, - { label: "20", value: "20" }, - { label: "50", value: "50" } + @track lppSettings = [ + {label: '10', value:10, checked:true }, + {label: '20', value:20, checked:false}, + {label: '50', value:50, checked:false}, ]; @track logLevelsSelected = { - info: false, - debug: false, - warn: false, - error: true + info: false, + debug: false, + warn: false, + error: true }; - isSubscribeDisabled = false; isUnsubscribeDisabled = !this.isSubscribeDisabled; columns = defaultColumns; @@ -66,6 +65,13 @@ export default class LogReader extends LightningElement { logsError; logsErrorJson; + tailButton = { + variant: "brand-outline", + label: "Tail", + iconName: "utility:play", + title: "Tail the logs", + }; + paramsForGetLog = { logLevels: ["INFO", "DEBUG"], cacheBuster: "1", @@ -90,7 +96,7 @@ export default class LogReader extends LightningElement { } this.logsError = undefined; this.logsErrorJson = undefined; - console.log("log success: " + this.logs); + //console.log("log success: " + this.logs); }) .catch(error => { this.logsError = error; @@ -100,9 +106,9 @@ export default class LogReader extends LightningElement { } handleButtonClick(event) { - let logLevel = event.target.getAttribute("data-log-level"); + let logLevel = event.target.name; this.logLevelsSelected[logLevel] = !this.logLevelsSelected[logLevel]; - console.log(JSON.stringify(this.logLevelsSelected)); + //console.log(JSON.stringify(this.logLevelsSelected)); this.handleButtonChange(); } @@ -137,6 +143,27 @@ export default class LogReader extends LightningElement { this.loadLogs(); } + handleSettingLpp(event){ + for (let i in this.lppSettings){ + if(this.lppSettings[i].value === event.target.value){ + this.lppSettings[i].checked = true; + this.paramsForGetLog.logsPerPage = event.target.value; + } else { + this.lppSettings[i].checked = false; + } + } + this.loadLogs(); + } + + toggleSubscribe(){ + this.isSubscribeDisabled = !this.isSubscribeDisabled; + if (this.isSubscribeDisabled){ + this.handleSubscribe(); + } else { + this.handleUnsubscribe(); + } + } + // Handles subscribe button click handleSubscribe() { // Callback invoked whenever a new event message is received @@ -154,12 +181,20 @@ export default class LogReader extends LightningElement { console.log("Successfully subscribed to : ", JSON.stringify(response.channel)); this.subscription = response; this.toggleSubscribeButton(true); + this.tailButton.variant = "brand"; + this.tailButton.iconName = "utility:stop"; + this.tailButton.title = "Stop tailing the logs"; + this.tailButton.label = "Stop"; }); } // Handles unsubscribe button click handleUnsubscribe() { this.toggleSubscribeButton(false); + this.tailButton.variant = "brand-outline"; + this.tailButton.iconName = "utility:play"; + this.tailButton.title = "Tail the logs"; + this.tailButton.label = "Tail"; // Invoke unsubscribe method of empApi unsubscribe(this.subscription, response => { From 79244e3b27d15a82deb837a8513707fa65fb80f1 Mon Sep 17 00:00:00 2001 From: Ryan Schierholz <33879609+RyanSchierholz@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:03:00 -0700 Subject: [PATCH 06/33] feat: add refresh button and filter; improved table, delete option (#5) Co-authored-by: Ryan Schierholz --- .../main/default/classes/LogUiController.cls | 70 ++++++ .../lwc/appLogStorage/appLogStorage.html | 122 +++++++++-- .../lwc/appLogStorage/appLogStorage.js | 201 ++++++++++++++++-- .../appLogStorage/appLogStorage.js-meta.xml | 18 ++ 4 files changed, 369 insertions(+), 42 deletions(-) diff --git a/force-app/main/default/classes/LogUiController.cls b/force-app/main/default/classes/LogUiController.cls index 6fa0416..3e5ff3a 100644 --- a/force-app/main/default/classes/LogUiController.cls +++ b/force-app/main/default/classes/LogUiController.cls @@ -42,6 +42,76 @@ public without sharing class LogUiController { return results; } + @AuraEnabled + public static List getLogCountByLevelDate(String relDateFilter){ + // Valid filters list + Set validFilters = new Set{ + 'TODAY', + 'YESTERDAY', + 'THIS_WEEK', + 'LAST_WEEK', + 'THIS_MONTH', + 'LAST_MONTH', + 'THIS_YEAR', + 'LAST_YEAR' + }; + + // Validate input + if (relDateFilter != null && !validFilters.contains(relDateFilter.toUpperCase())) { + throw new AuraHandledException('Invalid date filter provided'); + } + + String query = 'SELECT COUNT(Id) LogCount, MIN(CreatedDate) FirstCreatedDate, LogLevel__c ' + + 'FROM AppLog__c '; + + // Add date filter if provided (null means ALL TIME) + if (validFilters.contains(relDateFilter)) { + query += ' WHERE CreatedDate = ' + relDateFilter; // Use the literal directly in SOQL + } + // Order by custom order: INFO, DEBUG, WARN, ERROR + query += ' GROUP BY LogLevel__c ' + + 'ORDER BY LogLevel__c '; + + return Database.query(query); + } + + @AuraEnabled + public static void deleteLogsByLevel(String logLevel, String relDateFilter) { + system.debug('Deleting logs with log level: ' + logLevel + ' and date filter: ' + relDateFilter); + // Valid filters list + Set validFilters = new Set{ + 'TODAY', + 'YESTERDAY', + 'THIS_WEEK', + 'LAST_WEEK', + 'THIS_MONTH', + 'LAST_MONTH', + 'THIS_YEAR', + 'LAST_YEAR' + }; + + // Validate inputs + if (String.isBlank(logLevel)) { + throw new AuraHandledException('Log level must be specified'); + } + if (relDateFilter != null && !validFilters.contains(relDateFilter.toUpperCase())) { + throw new AuraHandledException('Invalid date filter provided'); + } + + String query = 'SELECT Id FROM AppLog__c WHERE LogLevel__c = :logLevel'; + + // Add date filter if provided (null means ALL TIME) + if (relDateFilter != null) { + query += ' AND CreatedDate = ' + relDateFilter; + } + try { + List logsToDelete = Database.query(query); + delete logsToDelete; + } catch (Exception e) { + throw new AuraHandledException('Error deleting logs: ' + e.getMessage()); + } + } + public class LogParamWrapper { @AuraEnabled public List logLevels { get; set; } diff --git a/force-app/main/default/lwc/appLogStorage/appLogStorage.html b/force-app/main/default/lwc/appLogStorage/appLogStorage.html index 348ed8a..2b530b5 100644 --- a/force-app/main/default/lwc/appLogStorage/appLogStorage.html +++ b/force-app/main/default/lwc/appLogStorage/appLogStorage.html @@ -1,30 +1,110 @@ +* List view of log entries with filtering and management capabilities +* @author mikelockett +* @contributors Ryan Schierholz (added date filtering, log deletion, and automatic refresh) +* @created 6/3/23 +* @updated 12/2/23 +* @description Shows aggregate log counts by level with the ability to: +* - Filter by date ranges +* - Delete logs by level +* - Auto-refresh data +* - View earliest log dates +--> \ No newline at end of file diff --git a/force-app/main/default/lwc/logReader/logReader.js b/force-app/main/default/lwc/logReader/logReader.js index efce272..42bfbe5 100644 --- a/force-app/main/default/lwc/logReader/logReader.js +++ b/force-app/main/default/lwc/logReader/logReader.js @@ -4,8 +4,9 @@ * contributor: Ryan Schierholz * updated: 2024-12-20 */ -import { LightningElement, track } from "lwc"; +import { LightningElement, api, track } from "lwc"; import { subscribe, unsubscribe } from "lightning/empApi"; +import { ShowToastEvent } from 'lightning/platformShowToastEvent'; import getLogs from "@salesforce/apex/LogUiController.getLogs"; // fields @@ -41,19 +42,23 @@ const defaultColumns = [ ]; export default class LogReader extends LightningElement { + // Props from meta.xml + @api defaultLogLevels; // e.g., "INFO,DEBUG,WARN" + @api defaultLogsPerPage; // e.g., 10 + @track logs; @track lppSettings = [ - {label: '10', value:10, checked:true }, - {label: '20', value:20, checked:false}, - {label: '50', value:50, checked:false}, + { label: '10', value: 10, checked: false, icon: 'utility:number_input' }, + { label: '20', value: 20, checked: false, icon: 'utility:number_input' }, + { label: '50', value: 50, checked: false, icon: 'utility:number_input' }, ]; @track logLevelsSelected = { info: false, debug: false, warn: false, - error: true + error: false }; isSubscribeDisabled = false; @@ -83,6 +88,37 @@ export default class LogReader extends LightningElement { return !!this.logs.length; } + connectedCallback() { + this.initializeLogLevels(); + this.initializeLogsPerPage(); + } + + initializeLogLevels() { + // Split comma-separated string from property: "INFO,DEBUG,WARN" + if (this.defaultLogLevels) { + const levels = this.defaultLogLevels.toLowerCase().split(','); + levels.forEach(level => { + const trimmed = level.trim(); + if (this.logLevelsSelected.hasOwnProperty(trimmed)) { + this.logLevelsSelected[trimmed] = true; + } + }); + } + } + + initializeLogsPerPage() { + // If admin configured a value, parse it; otherwise fallback to 10. + const chosenValue = this.defaultLogsPerPage + ? parseInt(this.defaultLogsPerPage, 10) + : 10; + + // Mark the matching setting as checked. + this.lppSettings = this.lppSettings.map(setting => ({ + ...setting, + checked: setting.value === chosenValue + })); + } + loadLogs() { this.paramsForGetLog.cacheBuster = (new Date()).getTime(); console.log("Called loadLogs: " + JSON.stringify(this.paramsForGetLog)); @@ -105,6 +141,7 @@ export default class LogReader extends LightningElement { }); } + // track if the log levels have changed handleButtonClick(event) { let logLevel = event.target.name; this.logLevelsSelected[logLevel] = !this.logLevelsSelected[logLevel]; @@ -112,17 +149,18 @@ export default class LogReader extends LightningElement { this.handleButtonChange(); } - handleButtonChange() { - let logLevelData = []; - for (const [logLevel, isSelected] of Object.entries(this.logLevelsSelected)) { - if (isSelected) { - logLevelData.push(logLevel.toUpperCase()); - } - } + // NO LONGER USED + // handleButtonChange() { + // let logLevelData = []; + // for (const [logLevel, isSelected] of Object.entries(this.logLevelsSelected)) { + // if (isSelected) { + // logLevelData.push(logLevel.toUpperCase()); + // } + // } - this.paramsForGetLog.logLevels = logLevelData; - this.loadLogs(); - } + // this.paramsForGetLog.logLevels = logLevelData; + // this.loadLogs(); + // } handleRowAction(event) { const action = event.detail.action; @@ -137,11 +175,12 @@ export default class LogReader extends LightningElement { } } - handleLogsPerPageChange(event) { - this.logsPerPage = event.detail.value; - this.paramsForGetLog.logsPerPage = this.logsPerPage; - this.loadLogs(); - } + // NOT USED + // handleLogsPerPageChange(event) { + // this.logsPerPage = event.detail.value; + // this.paramsForGetLog.logsPerPage = this.logsPerPage; + // this.loadLogs(); + // } handleSettingLpp(event){ for (let i in this.lppSettings){ @@ -152,7 +191,10 @@ export default class LogReader extends LightningElement { this.lppSettings[i].checked = false; } } - this.loadLogs(); + // if tailing, reload logs + if(this.isSubscribeDisabled){ + this.loadLogs(); + } } toggleSubscribe(){ @@ -176,16 +218,18 @@ export default class LogReader extends LightningElement { }; // Invoke subscribe method of empApi. Pass reference to messageCallback - subscribe(CHANNEL_NAME, -1, messageCallback).then(response => { - // Response contains the subscription information on successful subscribe call - console.log("Successfully subscribed to : ", JSON.stringify(response.channel)); - this.subscription = response; - this.toggleSubscribeButton(true); - this.tailButton.variant = "brand"; - this.tailButton.iconName = "utility:stop"; - this.tailButton.title = "Stop tailing the logs"; - this.tailButton.label = "Stop"; - }); + subscribe(CHANNEL_NAME, -1, messageCallback) + .then(response => { + // Response contains the subscription information on successful subscribe call + console.log("Successfully subscribed to : ", JSON.stringify(response.channel)); + this.subscription = response; + this.toggleSubscribeButton(true); + this.tailButton.variant = "brand"; + this.tailButton.iconName = "utility:stop"; + this.tailButton.title = "Stop tailing the logs"; + this.tailButton.label = "Stop"; + this.showToast('Success', 'New log entries are now being tailed.', 'success'); + }); } // Handles unsubscribe button click @@ -207,4 +251,13 @@ export default class LogReader extends LightningElement { this.isSubscribeDisabled = enableSubscribe; this.isUnsubscribeDisabled = !enableSubscribe; } + + showToast(title, message, variant) { + const event = new ShowToastEvent({ + title: title, + message: message, + variant: variant + }); + this.dispatchEvent(event); + } } \ No newline at end of file diff --git a/force-app/main/default/lwc/logReader/logReader.js-meta.xml b/force-app/main/default/lwc/logReader/logReader.js-meta.xml index fd6f712..be13bf3 100644 --- a/force-app/main/default/lwc/logReader/logReader.js-meta.xml +++ b/force-app/main/default/lwc/logReader/logReader.js-meta.xml @@ -1,11 +1,38 @@ 57.0 - Log Reader + Log Reader + This component displays log entries with customizable defaults. true lightning__AppPage lightning__HomePage - Log Reader + + + + + + + + + + \ No newline at end of file From 969befe6279b88b267337c24bf4ed7e5ce31abdd Mon Sep 17 00:00:00 2001 From: Ryan Schierholz <33879609+RyanSchierholz@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:52:03 -0700 Subject: [PATCH 08/33] feat: Add LogEntryItem LWC and update LogReader" (#7) --- .gitignore | 1 + .../main/default/classes/LogUiController.cls | 9 ++ force-app/main/default/lwc/jsconfig.json | 8 +- .../__tests__/logEntryItem.test.js | 25 +++ .../default/lwc/logEntryItem/logEntryItem.css | 45 ++++++ .../lwc/logEntryItem/logEntryItem.html | 101 ++++++++++++ .../default/lwc/logEntryItem/logEntryItem.js | 145 ++++++++++++++++++ .../lwc/logEntryItem/logEntryItem.js-meta.xml | 5 + .../main/default/lwc/logReader/logReader.css | 7 +- .../main/default/lwc/logReader/logReader.html | 85 ++++++---- .../main/default/lwc/logReader/logReader.js | 71 ++++++--- .../lwc/logReader/logReader.js-meta.xml | 13 +- 12 files changed, 457 insertions(+), 58 deletions(-) create mode 100644 force-app/main/default/lwc/logEntryItem/__tests__/logEntryItem.test.js create mode 100644 force-app/main/default/lwc/logEntryItem/logEntryItem.css create mode 100644 force-app/main/default/lwc/logEntryItem/logEntryItem.html create mode 100644 force-app/main/default/lwc/logEntryItem/logEntryItem.js create mode 100644 force-app/main/default/lwc/logEntryItem/logEntryItem.js-meta.xml diff --git a/.gitignore b/.gitignore index 99466b6..897e7fc 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ IlluminatedCloud force-app/main/default/settings/ force-app/main/default/profiles/.localdevserver/ .localdevserver/ +force-app/main/default/lwc/jsconfig.json \ No newline at end of file diff --git a/force-app/main/default/classes/LogUiController.cls b/force-app/main/default/classes/LogUiController.cls index 3e5ff3a..66a1038 100644 --- a/force-app/main/default/classes/LogUiController.cls +++ b/force-app/main/default/classes/LogUiController.cls @@ -75,6 +75,15 @@ public without sharing class LogUiController { return Database.query(query); } + @AuraEnabled + public static void deleteLog(String logId) { + try { + delete new AppLog__c(Id = logId); + } catch (Exception e) { + throw new AuraHandledException('Error deleting log: ' + e.getMessage()); + } + } + @AuraEnabled public static void deleteLogsByLevel(String logLevel, String relDateFilter) { system.debug('Deleting logs with log level: ' + logLevel + ' and date filter: ' + relDateFilter); diff --git a/force-app/main/default/lwc/jsconfig.json b/force-app/main/default/lwc/jsconfig.json index c613acd..33cf856 100644 --- a/force-app/main/default/lwc/jsconfig.json +++ b/force-app/main/default/lwc/jsconfig.json @@ -1,6 +1,12 @@ { "compilerOptions": { - "experimentalDecorators": true + "experimentalDecorators": true, + "baseUrl": ".", + "paths": { + "c/*": [ + "*" + ] + } }, "include": [ "**/*", diff --git a/force-app/main/default/lwc/logEntryItem/__tests__/logEntryItem.test.js b/force-app/main/default/lwc/logEntryItem/__tests__/logEntryItem.test.js new file mode 100644 index 0000000..be7b65e --- /dev/null +++ b/force-app/main/default/lwc/logEntryItem/__tests__/logEntryItem.test.js @@ -0,0 +1,25 @@ +import { createElement } from 'lwc'; +import LogEntryItem from 'c/logEntryItem'; + +describe('c-log-entry-item', () => { + afterEach(() => { + // The jsdom instance is shared across test cases in a single file so reset the DOM + while (document.body.firstChild) { + document.body.removeChild(document.body.firstChild); + } + }); + + it('TODO: test case generated by CLI command, please fill in test logic', () => { + // Arrange + const element = createElement('c-log-entry-item', { + is: LogEntryItem + }); + + // Act + document.body.appendChild(element); + + // Assert + // const div = element.shadowRoot.querySelector('div'); + expect(1).toBe(1); + }); +}); \ No newline at end of file diff --git a/force-app/main/default/lwc/logEntryItem/logEntryItem.css b/force-app/main/default/lwc/logEntryItem/logEntryItem.css new file mode 100644 index 0000000..a5a3b4b --- /dev/null +++ b/force-app/main/default/lwc/logEntryItem/logEntryItem.css @@ -0,0 +1,45 @@ +.log-entry-container { + transition: background-color 0.5s ease-in-out; +} + +.highlight-flash { + background-color: #fffdcc; /* briefly highlight new entries */ +} + +/* Wrapper around the lightning-icon */ +.level-icon-container { + border-radius: 4px; + padding: 0.25rem; + display: inline-flex; /* keep the icon centered and tidy */ +} + +.badge { + width: 100%; + display: block !important; + text-align: center; +} +/* Colors for different log levels */ +.debug-background { + /*background-color: #f3f2f2; gray color 3 for debug */ + background-color: #dddbda; /* gray color 5 for debug */ +} +.info-background { + /*background-color: #eef4ff; blue 95 for info */ + background-color: #aacbff; /* blue 80 for info */ +} +.warn-background { + /* background-color: #fff1ea; Orange 95 for warn */ + background-color: #ffba90; /* Orange 80 for warn */ +} +.error-background { + /*background-color: #fef1ee; RED 95 for error */ + background-color: #feb8ab; /* RED 80 for error */ +} +.INFO{ + /* --slds-c-icon-color-foreground-defaults: orange;*/ + /*--slds-c-icon-color-foreground-default: #0b5cab; blue 40*/ + --slds-c-icon-color-foreground-default: #0176d3; /*blue 50*/ + } + .DEBUG{ + --slds-c-icon-color-foreground-default: #06a59a; /*teal 60*/ + } \ No newline at end of file diff --git a/force-app/main/default/lwc/logEntryItem/logEntryItem.html b/force-app/main/default/lwc/logEntryItem/logEntryItem.html new file mode 100644 index 0000000..a4090a0 --- /dev/null +++ b/force-app/main/default/lwc/logEntryItem/logEntryItem.html @@ -0,0 +1,101 @@ + \ No newline at end of file diff --git a/force-app/main/default/lwc/logEntryItem/logEntryItem.js b/force-app/main/default/lwc/logEntryItem/logEntryItem.js new file mode 100644 index 0000000..e9daba6 --- /dev/null +++ b/force-app/main/default/lwc/logEntryItem/logEntryItem.js @@ -0,0 +1,145 @@ +import { LightningElement, api, track } from 'lwc'; +import { NavigationMixin } from 'lightning/navigation'; + +export default class LogEntryItem extends NavigationMixin(LightningElement) { + @api logEntry; // Object containing all the log info (level, timestamp, shortMessage, location, etc.) + @api isNew = false; // Pass this as true when you initially insert a log entry, so we can highlight it + @api useIcons = false; // Pass this as true if you want to use an icon for the log level + @track isExpanded = false; + + connectedCallback() { + console.log('useIcons: ' + this.useIcons); + // if new, we add highlight, then fade out after a short delay. + if (this.isNew || this.logEntry.isNew) { + // Trigger highlight flash for newly inserted logs + setTimeout(() => { + this.isNew = false; + }, 2000); + } + } + + // Example: deriving an icon name based on level + get logLevelIconVariant() { + switch ((this.logEntry.LogLevel__c || '').toLowerCase()) { + case 'warn': return 'warning'; + case 'error': return 'error'; + default: return ''; // fallback + } + } + + // Example: deriving an icon name based on level + get logLevelIcon() { + switch ((this.logEntry.LogLevel__c || '').toLowerCase()) { + case 'debug': return 'utility:bug'; + case 'info': return 'utility:info'; + case 'warn': return 'utility:warning'; + case 'error': return 'utility:error'; + default: return 'utility:info'; // fallback + } + } + + // Assign a CSS class for background color behind the icon + get levelContainerClass() { + let classes = 'level-icon-container'; + const level = (this.logEntry.LogLevel__c || '').toLowerCase(); + switch (level) { + case 'debug': + classes += ' debug-background'; + break; + case 'info': + classes += ' info-background'; + break; + case 'warn': + classes += ' warn-background'; + break; + case 'error': + classes += ' error-background'; + break; + default: + classes += ' info-background'; // fallback + } + return classes; + } + + get logLevelBadgeClass() { + let classes = 'slds-badge badge'; + const level = (this.logEntry.LogLevel__c || '').toLowerCase(); + switch (level) { + case 'debug': + classes += ' slds-badge_inverse'; + break; + case 'warn': + classes += ' slds-theme_warning'; + break; + case 'error': + classes += ' slds-theme_error'; + break; + default: + classes += ''; // fallback and info + } + return classes; + } + + // Container CSS classes for highlight fade + get entryContainerClass() { + let classes = ' log-entry-container'; + //classes += this.levelContainerClass; // Add the level-specific background color; trying BADGE instead + + if (this.isNew) { + classes += ' highlight-flash'; + } + return classes; + } + + // Convert timestamp to local time zone using toLocaleString() + get localTimestamp() { + if (!this.logEntry.CreatedDate) { + return ''; + } + const dateObj = new Date(this.logEntry.CreatedDate); + + // Adjust these options as needed + const options = { + year: '2-digit', // Two-digit year + month: 'short', // Abbreviated month (e.g., Jan) + day: '2-digit', // Always 2-digit day + hour: 'numeric', // Locale-based hour (12 or 24) + minute: 'numeric', + second: 'numeric', + hour12: true // If you want AM/PM style + }; + + return dateObj.toLocaleString(undefined, options); + } + + get detailsButtonLabel() { + return this.isExpanded ? 'Hide Details' : 'View Details'; + } + get showDetailsIcon(){ + return this.isExpanded ? 'utility:chevrondown' : 'utility:chevronleft'; + } + + handleToggleDetails() { + this.isExpanded = !this.isExpanded; + } + + handleDelete() { + // Dispatch an event to parent to remove this entry + const evt = new CustomEvent('delete', { + detail: { id: this.logEntry.Id } + }); + this.dispatchEvent(evt); + } + + openRecord() { + this[NavigationMixin.Navigate]({ + type: 'standard__recordPage', + attributes: { + recordId: this.logEntry.Id, + objectApiName: 'LogEntry__c', + actionName: 'view' + } + }); + + } +} \ No newline at end of file diff --git a/force-app/main/default/lwc/logEntryItem/logEntryItem.js-meta.xml b/force-app/main/default/lwc/logEntryItem/logEntryItem.js-meta.xml new file mode 100644 index 0000000..353961e --- /dev/null +++ b/force-app/main/default/lwc/logEntryItem/logEntryItem.js-meta.xml @@ -0,0 +1,5 @@ + + + 62.0 + false + \ No newline at end of file diff --git a/force-app/main/default/lwc/logReader/logReader.css b/force-app/main/default/lwc/logReader/logReader.css index fa96b74..24f5173 100644 --- a/force-app/main/default/lwc/logReader/logReader.css +++ b/force-app/main/default/lwc/logReader/logReader.css @@ -1,8 +1,7 @@ /** * Created by mikelockett on 3/15/20. */ -.log-list{ - border: 1px solid #ccc; - border-collapse: collapse; - margin: 2em 0; +.spinner { + vertical-align: middle; + white-space: nowrap; } \ No newline at end of file diff --git a/force-app/main/default/lwc/logReader/logReader.html b/force-app/main/default/lwc/logReader/logReader.html index 735c618..2d1f279 100644 --- a/force-app/main/default/lwc/logReader/logReader.html +++ b/force-app/main/default/lwc/logReader/logReader.html @@ -56,6 +56,16 @@ onclick={toggleSubscribe} > + + + +
+ -
- - + + +
diff --git a/force-app/main/default/lwc/logReader/logReader.js b/force-app/main/default/lwc/logReader/logReader.js index 42bfbe5..553e220 100644 --- a/force-app/main/default/lwc/logReader/logReader.js +++ b/force-app/main/default/lwc/logReader/logReader.js @@ -8,6 +8,7 @@ import { LightningElement, api, track } from "lwc"; import { subscribe, unsubscribe } from "lightning/empApi"; import { ShowToastEvent } from 'lightning/platformShowToastEvent'; import getLogs from "@salesforce/apex/LogUiController.getLogs"; +import deleteLog from "@salesforce/apex/LogUiController.deleteLog"; // fields import SHORT_MESSAGE_FIELD from "@salesforce/schema/AppLog__c.ShortMessage__c"; @@ -43,9 +44,9 @@ const defaultColumns = [ export default class LogReader extends LightningElement { // Props from meta.xml - @api defaultLogLevels; // e.g., "INFO,DEBUG,WARN" + @api defaultLogLevels; // e.g., "info,debug,warn" @api defaultLogsPerPage; // e.g., 10 - + @api useIcons; // e.g., true @track logs; @track lppSettings = [ @@ -78,9 +79,7 @@ export default class LogReader extends LightningElement { }; paramsForGetLog = { - logLevels: ["INFO", "DEBUG"], cacheBuster: "1", - logsPerPage: 10 }; paramsForGetLogJson; @@ -88,6 +87,10 @@ export default class LogReader extends LightningElement { return !!this.logs.length; } + get noLogs(){ + return !this.logs || !this.logs.length; + } + connectedCallback() { this.initializeLogLevels(); this.initializeLogsPerPage(); @@ -96,13 +99,18 @@ export default class LogReader extends LightningElement { initializeLogLevels() { // Split comma-separated string from property: "INFO,DEBUG,WARN" if (this.defaultLogLevels) { - const levels = this.defaultLogLevels.toLowerCase().split(','); + // Remove all spaces, then convert to lowercase + const cleanedString = this.defaultLogLevels.replace(/\s+/g, '').toLowerCase(); + // Now split by commas + const levels = cleanedString.split(','); + levels.forEach(level => { const trimmed = level.trim(); if (this.logLevelsSelected.hasOwnProperty(trimmed)) { this.logLevelsSelected[trimmed] = true; } }); + this.paramsForGetLog.logLevels = levels; } } @@ -111,6 +119,7 @@ export default class LogReader extends LightningElement { const chosenValue = this.defaultLogsPerPage ? parseInt(this.defaultLogsPerPage, 10) : 10; + this.paramsForGetLog.logsPerPage = chosenValue; // Mark the matching setting as checked. this.lppSettings = this.lppSettings.map(setting => ({ @@ -119,12 +128,12 @@ export default class LogReader extends LightningElement { })); } + // working function, but would like to pass the new items to highlight to the logEntryItem component loadLogs() { this.paramsForGetLog.cacheBuster = (new Date()).getTime(); console.log("Called loadLogs: " + JSON.stringify(this.paramsForGetLog)); getLogs({ params: this.paramsForGetLog }) .then(result => { - this.logs = []; for (const appLog of result) { appLog.recordLink = "/" + appLog.Id; @@ -132,7 +141,6 @@ export default class LogReader extends LightningElement { } this.logsError = undefined; this.logsErrorJson = undefined; - //console.log("log success: " + this.logs); }) .catch(error => { this.logsError = error; @@ -141,6 +149,7 @@ export default class LogReader extends LightningElement { }); } + // track if the log levels have changed handleButtonClick(event) { let logLevel = event.target.name; @@ -149,18 +158,20 @@ export default class LogReader extends LightningElement { this.handleButtonChange(); } - // NO LONGER USED - // handleButtonChange() { - // let logLevelData = []; - // for (const [logLevel, isSelected] of Object.entries(this.logLevelsSelected)) { - // if (isSelected) { - // logLevelData.push(logLevel.toUpperCase()); - // } - // } + // USED Above. May be able to be combined... + handleButtonChange() { + let logLevelData = []; + for (const [logLevel, isSelected] of Object.entries(this.logLevelsSelected)) { + if (isSelected) { + logLevelData.push(logLevel.toUpperCase()); + } + } - // this.paramsForGetLog.logLevels = logLevelData; - // this.loadLogs(); - // } + this.paramsForGetLog.logLevels = logLevelData; + if(this.isSubscribeDisabled){ + this.loadLogs(); + } + } handleRowAction(event) { const action = event.detail.action; @@ -252,6 +263,30 @@ export default class LogReader extends LightningElement { this.isUnsubscribeDisabled = !enableSubscribe; } + clearLogs(){ + this.logs = undefined; + } + + handleDeleteEntry(event) { + const toDeleteId = event.detail.id; + // Remove from logs array + this.logs = this.logs.filter(entry => entry.Id !== toDeleteId); + + // Remove entry from database. + deleteLog({ logId: toDeleteId }) + .then(result => { + this.showToast('Success', 'Log entry deleted.', 'success'); + this.logsError = undefined; + this.logsErrorJson = undefined; + }) + .catch(error => { + this.showToast('Error', error, 'error'); + this.logsError = error; + this.logsErrorJson = JSON.stringify(error); + this.paramsForGetLogJson = JSON.stringify(this.paramsForGetLog); + }); + } + showToast(title, message, variant) { const event = new ShowToastEvent({ title: title, diff --git a/force-app/main/default/lwc/logReader/logReader.js-meta.xml b/force-app/main/default/lwc/logReader/logReader.js-meta.xml index be13bf3..a78229f 100644 --- a/force-app/main/default/lwc/logReader/logReader.js-meta.xml +++ b/force-app/main/default/lwc/logReader/logReader.js-meta.xml @@ -19,7 +19,7 @@ name="defaultLogLevels" label="Default Log Levels" type="String" - description="Comma-separated list of default log levels to track, e.g., INFO,DEBUG,WARN,ERROR." + description="Comma-separated list of default log levels to track, e.g., info,debug,warn,error." /> + \ No newline at end of file From 1515ca26f1489bc8e9b3d7abc98311eebe22a880 Mon Sep 17 00:00:00 2001 From: Ryan Schierholz <33879609+RyanSchierholz@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:58:54 -0700 Subject: [PATCH 09/33] docs: add author credits (#8) --- force-app/main/default/lwc/logEntryItem/logEntryItem.css | 3 +++ force-app/main/default/lwc/logEntryItem/logEntryItem.html | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/force-app/main/default/lwc/logEntryItem/logEntryItem.css b/force-app/main/default/lwc/logEntryItem/logEntryItem.css index a5a3b4b..e9b58c5 100644 --- a/force-app/main/default/lwc/logEntryItem/logEntryItem.css +++ b/force-app/main/default/lwc/logEntryItem/logEntryItem.css @@ -1,3 +1,6 @@ +/** + * @author Ryan Schierholz + */ .log-entry-container { transition: background-color 0.5s ease-in-out; } diff --git a/force-app/main/default/lwc/logEntryItem/logEntryItem.html b/force-app/main/default/lwc/logEntryItem/logEntryItem.html index a4090a0..f162e42 100644 --- a/force-app/main/default/lwc/logEntryItem/logEntryItem.html +++ b/force-app/main/default/lwc/logEntryItem/logEntryItem.html @@ -1,8 +1,9 @@ -