diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Logs_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Logs_spec.js index ff76d6f46fe..bd76f78d7c5 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Logs_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OtherUIFeatures/Logs_spec.js @@ -118,7 +118,64 @@ describe("Debugger logs", function() { agHelper.GetNAssertContains(locator._debuggerLogMessage, logStringChild); }); - it("8. Console log on text widget with normal moustache binding", function() { + it("8. Console log grouping on button click", function() { + agHelper.GetNClick(locator._debuggerClearLogs); + // Testing with normal log in iifee + ee.SelectEntityByName("Button1"); + propPane.EnterJSContext( + "onClick", + `{{ function () { + console.log('${logString}'); + console.log('${logString}'); + console.log('${logString}'); + console.log('${logString}'); + console.log('${logString}'); + } () }}`, + ); + agHelper.ClickButton("Submit"); + agHelper.GetNAssertContains(locator._debuggerLogMessage, logString); + agHelper.GetNAssertContains(locator._debuggerLogMessageOccurence, "5"); + }); + + it("9. Console log grouping on button click with different log in between", function() { + agHelper.GetNClick(locator._debuggerClearLogs); + // Testing with normal log in iifee + ee.SelectEntityByName("Button1"); + propPane.EnterJSContext( + "onClick", + `{{ function () { + console.log('${logString}'); + console.log('${logString}'); + console.log('Different ${logString}'); + console.log('${logString}'); + } () }}`, + ); + agHelper.ClickButton("Submit"); + agHelper.GetNAssertContains(locator._debuggerLogMessage, logString); + agHelper.GetNAssertContains( + locator._debuggerLogMessage, + `Different ${logString}`, + ); + agHelper.GetNAssertContains(locator._debuggerLogMessageOccurence, "2"); + }); + + it("10. Console log grouping on button click from different source", function() { + agHelper.GetNClick(locator._debuggerClearLogs); + // Testing with normal log in iifee + ee.SelectEntityByName("Button1"); + propPane.EnterJSContext("onClick", `{{console.log("${logString}")}}`); + // Add another button + ee.DragDropWidgetNVerify("buttonwidget", 400, 200); + propPane.UpdatePropertyFieldValue("Label", "Submit2"); + propPane.EnterJSContext("onClick", `{{console.log("${logString}")}}`); + agHelper.Sleep(2000); + agHelper.ClickButton("Submit"); + agHelper.ClickButton("Submit2"); + agHelper.GetNAssertContains(locator._debuggerLogMessage, logString); + agHelper.AssertElementAbsence(locator._debuggerLogMessageOccurence); + }); + + it("11. Console log on text widget with normal moustache binding", function() { ee.DragDropWidgetNVerify("textwidget", 400, 400); propPane.UpdatePropertyFieldValue( "Text", @@ -136,7 +193,7 @@ describe("Debugger logs", function() { agHelper.GetNAssertContains(locator._debuggerLogMessage, logString); }); - it("9. Console log in sync function", function() { + it("12. Console log in sync function", function() { ee.NavigateToSwitcher("explorer"); jsEditor.CreateJSObject( `export default { @@ -160,7 +217,7 @@ describe("Debugger logs", function() { agHelper.GetNAssertContains(locator._debuggerLogMessage, logString); }); - it("10. Console log in async function", function() { + it("13. Console log in async function", function() { ee.NavigateToSwitcher("explorer"); jsEditor.CreateJSObject( `export default { @@ -186,7 +243,7 @@ describe("Debugger logs", function() { agHelper.GetNAssertContains(locator._debuggerLogMessage, logString); }); - it("11. Console log after API succedes", function() { + it("14. Console log after API succedes", function() { ee.NavigateToSwitcher("explorer"); apiPage.CreateAndFillApi(dataSet.baseUrl + dataSet.methods, "Api1"); const returnText = "success"; @@ -239,7 +296,7 @@ describe("Debugger logs", function() { }); }); - it("12. Console log after API execution fails", function() { + it("15. Console log after API execution fails", function() { ee.NavigateToSwitcher("explorer"); apiPage.CreateAndFillApi(dataSet.baseUrl + dataSet.methods + "xyz", "Api2"); jsEditor.CreateJSObject( @@ -278,7 +335,7 @@ describe("Debugger logs", function() { ); }); - it("13. Console log source inside nested function", function() { + it("16. Console log source inside nested function", function() { jsEditor.CreateJSObject( `export default { myFun1: async () => { @@ -309,6 +366,34 @@ describe("Debugger logs", function() { ); }); + it("17. Console log grouping", function() { + jsEditor.CreateJSObject( + `export default { + myFun1: async () => { + console.log("${logString}"); + console.log("${logString}"); + console.log("${logString}"); + console.log("${logString}"); + console.log("${logString}"); + }, + myFun2: () => { + return 1; + } + }`, + { + paste: true, + completeReplace: true, + toRun: false, + shouldCreateNewJSObj: true, + }, + ); + agHelper.WaitUntilAllToastsDisappear(); + agHelper.GetNClick(jsEditor._runButton); + agHelper.GetNClick(jsEditor._logsTab); + agHelper.GetNAssertContains(locator._debuggerLogMessage, `${logString}`); + agHelper.GetNAssertContains(locator._debuggerLogMessageOccurence, "5"); + }); + // it("Api headers need to be shown as headers in logs", function() { // // TODO // }); diff --git a/app/client/cypress/support/Objects/CommonLocators.ts b/app/client/cypress/support/Objects/CommonLocators.ts index a92cca77c78..aa49def804e 100644 --- a/app/client/cypress/support/Objects/CommonLocators.ts +++ b/app/client/cypress/support/Objects/CommonLocators.ts @@ -1,96 +1,157 @@ export class CommonLocators { - _loading = "#loading" - _spinner = ".bp3-spinner" - _runBtnSpinner = ".cs-spinner" - _queryName = ".t--action-name-edit-field span" - _queryNameTxt = ".t--action-name-edit-field input" - _dsName = ".t--edit-datasource-name span" - _dsNameTxt = ".t--edit-datasource-name input" - _widgetName = (widgetName: string) => ".editable-text-container:contains('"+widgetName+"') span.bp3-editable-text-content" - _widgetNameTxt = ".editable-text-container input.bp3-editable-text-input" - _saveStatusSuccess = ".t--save-status-success" - _codeMirrorTextArea = ".CodeMirror textarea" - _codeMirrorCode = ".CodeMirror-code" - _codeEditorTargetTextArea = ".CodeEditorTarget textarea" - _codeEditorTarget = "div.CodeEditorTarget" - _entityExplorersearch = "#entity-explorer-search" - _propertyControl = ".t--property-control-" - _textWidget = ".t--draggable-textwidget span" - _inputWidget = ".t--draggable-inputwidgetv2 input" - _publishButton = ".t--application-publish-btn" - _widgetInCanvas = (widgetType: string) => `.t--draggable-${widgetType}` - _widgetInDeployed = (widgetType: string) => `.t--widget-${widgetType}` - _widgetInputSelector = (widgetType: string) => this._widgetInDeployed(widgetType) + " input" - _textWidgetInDeployed = this._widgetInDeployed("textwidget") + " span" - _inputWidgetv1InDeployed = this._widgetInDeployed("inputwidget") + " input" - _textAreainputWidgetv1InDeployed = this._widgetInDeployed("inputwidget") + " textarea" - _textAreainputWidgetv2InDeployed = this._widgetInDeployed("inputwidgetv2") + " textarea" - _imageWidget = ".t--draggable-imagewidget" - _backToEditor = ".t--back-to-editor" - _newPage = ".pages .t--entity-add-btn" - _toastMsg = "div.t--toast-action" - _toastContainer = "div.Toastify__toast-container" - _specificToast = (toastText: string) => this._toastMsg + ":contains('" + toastText + "')" - //_specificToast = (toastText: string | RegExp) => this._toastMsg + ":contains("+ (typeof toastText == 'string' ? "'"+ toastText+"'" : toastText)+ ")"//not working! - _empty = "span[name='no-response']" - _contextMenuInPane = "span[name='context-menu']" - _contextMenuSubItemDiv = (item: string) => "//div[text()='" + item + "'][contains(@class, 'bp3-fill')]" - _visibleTextDiv = (divText: string) => "//div[text()='" + divText + "']" - _visibleTextSpan = (spanText: string) => "//span[text()='" + spanText + "']"; - _openWidget = ".widgets .t--entity-add-btn" - _dropHere = ".t--drop-target" - _crossBtn = "span.cancel-icon" - _createNew = ".t--entity-add-btn.group.files" - _uploadFiles = "div.uppy-Dashboard-AddFiles input" - _uploadBtn = "button.uppy-StatusBar-actionBtn--upload" - _debuggerIcon = ".t--debugger svg" - _errorTab = "[data-cy=t--tab-ERROR]" - _responseTab = "[data-cy=t--tab-response]" - _debugErrorMsg = ".t--debugger-message" - _debuggerLogState = ".t--debugger-log-state" - _debuggerLogMessage = ".t--debugger-log-message" - _debuggerClearLogs = ".t--debugger-clear-logs" - _debuggerLabel = "span.debugger-label" - _modal = ".t--modal-widget" - _entityProperties = (entityNameinLeftSidebar: string) => "//div[text()='" + entityNameinLeftSidebar + "']/ancestor::div[contains(@class, 't--entity-item')]/following-sibling::div//div[contains(@class, 't--entity-property')]//code" - _entityNameEditing = (entityNameinLeftSidebar: string) => "//span[text()='" + entityNameinLeftSidebar + "']/parent::div[contains(@class, 't--entity-name editing')]/input" - _jsToggle = (controlToToggle: string) => ".t--property-control-" + controlToToggle + " .t--js-toggle" - _spanButton = (btnVisibleText: string) => `//span[text()="${btnVisibleText}"]/parent::button` - _selectPropDropdown = (ddName: string) => "//div[contains(@class, 't--property-control-" + ddName.replace(/ +/g, "").toLowerCase() + "')]//button[contains(@class, 't--open-dropdown-Select-Action')]" - _dropDownValue = (dropdownOption: string) => ".single-select:contains('" + dropdownOption + "')" - _selectOptionValue = (dropdownOption: string) => ".menu-item-link:contains('" + dropdownOption + "')" - _selectedDropdownValue = "//button[contains(@class, 'select-button')]/span[@class='bp3-button-text']" - _actionTextArea = (actionName: string) => "//label[text()='" + actionName + "']/following-sibling::div//div[contains(@class, 'CodeMirror')]//textarea" - _existingDefaultTextInput = ".t--property-control-defaulttext .CodeMirror-code" - _widgetPageIcon = (widgetType: string) => `.t--widget-card-draggable-${widgetType}` - _propertyToggleValue = (controlToToggle: string) => "//div[contains(@class, 't--property-control-" + controlToToggle + "')]//input[@type='checkbox']/parent::label" - _openNavigationTab = (tabToOpen: string) => `#switcher--${tabToOpen}` - _selectWidgetDropdown = (widgetType: string) => `//div[contains(@class, 't--draggable-${widgetType}')]//button` - _selectWidgetDropdownInDeployed = (widgetType: string) => `//div[contains(@class, 't--widget-${widgetType}')]//button` - _inputFieldByName = (fieldName: string) => "//p[text()='" + fieldName + "']/ancestor::label/parent::div/following-sibling::div" - _existingFieldTextByName = (fieldName: string) => "//label[text()='" + fieldName + "']/ancestor::div[contains(@class, 't--property-control-" + fieldName.replace(/ +/g, "").toLowerCase() + "')]" - _existingFieldValueByName = (fieldName: string) => this._existingFieldTextByName(fieldName) + "//div[contains(@class,'CodeMirror-code')]" - _existingActualValueByName = (fieldName: string) => this._existingFieldValueByName(fieldName) + "//span/span" - _codeMirrorValue = "//div[contains(@class,'CodeMirror-code')]//span/span" - _evaluatedCurrentValue = "div:last-of-type .t--CodeEditor-evaluatedValue > div:last-of-type pre" - _multiSelectOptions = (option: string) => "div[title='" + option + "'] input[type='checkbox']" - _divWithClass = (className: string) => "//div[contains(@class, '" + className + "')]" - _multiSelectItem = (item: string) => "//span[text()='" + item + "']/ancestor::div[@class ='rc-select-selection-overflow-item']//span[contains(@class, 'remove-icon')]" - _listWidget = "div[type='LIST_WIDGET']" - _dropdownText = ".t--dropdown-option" - _jsonFormInputField = (fieldName: string) => `.t--jsonformfield-${fieldName} input` - _jsonFormHeader = ".t--jsonform-body > div:first-child" - _jsonFormWidget = ".t--widget-jsonformwidget" - _lintErrorElement = `span.CodeMirror-lint-mark-error` - _lintWarningElement = "span.CodeMirror-lint-mark-warning" - _codeEditorWrapper = ".unfocused-code-editor" - _datePicker = (date: number) => "//div[@class ='bp3-datepicker']//div[contains(@class, 'DayPicker-Day')]//div[text()='" + date + "']"; - _inputWidgetValueField= (fieldName: string, input : boolean = true) => `//label[contains(@class, 't--input-widget-label')][text()='${fieldName}']/ancestor::div[@data-testid='input-container']//${input ? "input" : "textarea"}` - _deleteIcon = "button .bp3-icon-delete" - _datePickerValue = "div[data-testid='datepicker-container'] input" - _switchToggle = (switchName: string) => "//div[contains(@class, 't--switch-widget-label')][text()='"+switchName+"']/parent::label/span" - _jsonToggle = (fieldName: string) => `//p[text()='${fieldName}']/parent::div//following-sibling::div//input[@type='checkbox']` - _deployedPage = `.t--page-switch-tab` - _hints = "ul.CodeMirror-hints li" - _cancelActionExecution = ".t--cancel-action-button" + _loading = "#loading"; + _spinner = ".bp3-spinner"; + _runBtnSpinner = ".cs-spinner"; + _queryName = ".t--action-name-edit-field span"; + _queryNameTxt = ".t--action-name-edit-field input"; + _dsName = ".t--edit-datasource-name span"; + _dsNameTxt = ".t--edit-datasource-name input"; + _widgetName = (widgetName: string) => + ".editable-text-container:contains('" + + widgetName + + "') span.bp3-editable-text-content"; + _widgetNameTxt = ".editable-text-container input.bp3-editable-text-input"; + _saveStatusSuccess = ".t--save-status-success"; + _codeMirrorTextArea = ".CodeMirror textarea"; + _codeMirrorCode = ".CodeMirror-code"; + _codeEditorTargetTextArea = ".CodeEditorTarget textarea"; + _codeEditorTarget = "div.CodeEditorTarget"; + _entityExplorersearch = "#entity-explorer-search"; + _propertyControl = ".t--property-control-"; + _textWidget = ".t--draggable-textwidget span"; + _inputWidget = ".t--draggable-inputwidgetv2 input"; + _publishButton = ".t--application-publish-btn"; + _widgetInCanvas = (widgetType: string) => `.t--draggable-${widgetType}`; + _widgetInDeployed = (widgetType: string) => `.t--widget-${widgetType}`; + _widgetInputSelector = (widgetType: string) => + this._widgetInDeployed(widgetType) + " input"; + _textWidgetInDeployed = this._widgetInDeployed("textwidget") + " span"; + _inputWidgetv1InDeployed = this._widgetInDeployed("inputwidget") + " input"; + _textAreainputWidgetv1InDeployed = + this._widgetInDeployed("inputwidget") + " textarea"; + _textAreainputWidgetv2InDeployed = + this._widgetInDeployed("inputwidgetv2") + " textarea"; + _imageWidget = ".t--draggable-imagewidget"; + _backToEditor = ".t--back-to-editor"; + _newPage = ".pages .t--entity-add-btn"; + _toastMsg = "div.t--toast-action"; + _toastContainer = "div.Toastify__toast-container"; + _specificToast = (toastText: string) => + this._toastMsg + ":contains('" + toastText + "')"; + //_specificToast = (toastText: string | RegExp) => this._toastMsg + ":contains("+ (typeof toastText == 'string' ? "'"+ toastText+"'" : toastText)+ ")"//not working! + _empty = "span[name='no-response']"; + _contextMenuInPane = "span[name='context-menu']"; + _contextMenuSubItemDiv = (item: string) => + "//div[text()='" + item + "'][contains(@class, 'bp3-fill')]"; + _visibleTextDiv = (divText: string) => "//div[text()='" + divText + "']"; + _visibleTextSpan = (spanText: string) => "//span[text()='" + spanText + "']"; + _openWidget = ".widgets .t--entity-add-btn"; + _dropHere = ".t--drop-target"; + _crossBtn = "span.cancel-icon"; + _createNew = ".t--entity-add-btn.group.files"; + _uploadFiles = "div.uppy-Dashboard-AddFiles input"; + _uploadBtn = "button.uppy-StatusBar-actionBtn--upload"; + _debuggerIcon = ".t--debugger svg"; + _errorTab = "[data-cy=t--tab-ERROR]"; + _responseTab = "[data-cy=t--tab-response]"; + _debugErrorMsg = ".t--debugger-message"; + _debuggerLogState = ".t--debugger-log-state"; + _debuggerLogMessage = ".t--debugger-log-message"; + _debuggerLogMessageOccurence = ".t--debugger-log-message-occurence"; + _debuggerClearLogs = ".t--debugger-clear-logs"; + _debuggerLabel = "span.debugger-label"; + _modal = ".t--modal-widget"; + _entityProperties = (entityNameinLeftSidebar: string) => + "//div[text()='" + + entityNameinLeftSidebar + + "']/ancestor::div[contains(@class, 't--entity-item')]/following-sibling::div//div[contains(@class, 't--entity-property')]//code"; + _entityNameEditing = (entityNameinLeftSidebar: string) => + "//span[text()='" + + entityNameinLeftSidebar + + "']/parent::div[contains(@class, 't--entity-name editing')]/input"; + _jsToggle = (controlToToggle: string) => + ".t--property-control-" + controlToToggle + " .t--js-toggle"; + _spanButton = (btnVisibleText: string) => + `//span[text()="${btnVisibleText}"]/parent::button`; + _selectPropDropdown = (ddName: string) => + "//div[contains(@class, 't--property-control-" + + ddName.replace(/ +/g, "").toLowerCase() + + "')]//button[contains(@class, 't--open-dropdown-Select-Action')]"; + _dropDownValue = (dropdownOption: string) => + ".single-select:contains('" + dropdownOption + "')"; + _selectOptionValue = (dropdownOption: string) => + ".menu-item-link:contains('" + dropdownOption + "')"; + _selectedDropdownValue = + "//button[contains(@class, 'select-button')]/span[@class='bp3-button-text']"; + _actionTextArea = (actionName: string) => + "//label[text()='" + + actionName + + "']/following-sibling::div//div[contains(@class, 'CodeMirror')]//textarea"; + _existingDefaultTextInput = + ".t--property-control-defaulttext .CodeMirror-code"; + _widgetPageIcon = (widgetType: string) => + `.t--widget-card-draggable-${widgetType}`; + _propertyToggleValue = (controlToToggle: string) => + "//div[contains(@class, 't--property-control-" + + controlToToggle + + "')]//input[@type='checkbox']/parent::label"; + _openNavigationTab = (tabToOpen: string) => `#switcher--${tabToOpen}`; + _selectWidgetDropdown = (widgetType: string) => + `//div[contains(@class, 't--draggable-${widgetType}')]//button`; + _selectWidgetDropdownInDeployed = (widgetType: string) => + `//div[contains(@class, 't--widget-${widgetType}')]//button`; + _inputFieldByName = (fieldName: string) => + "//p[text()='" + + fieldName + + "']/ancestor::label/parent::div/following-sibling::div"; + _existingFieldTextByName = (fieldName: string) => + "//label[text()='" + + fieldName + + "']/ancestor::div[contains(@class, 't--property-control-" + + fieldName.replace(/ +/g, "").toLowerCase() + + "')]"; + _existingFieldValueByName = (fieldName: string) => + this._existingFieldTextByName(fieldName) + + "//div[contains(@class,'CodeMirror-code')]"; + _existingActualValueByName = (fieldName: string) => + this._existingFieldValueByName(fieldName) + "//span/span"; + _codeMirrorValue = "//div[contains(@class,'CodeMirror-code')]//span/span"; + _evaluatedCurrentValue = + "div:last-of-type .t--CodeEditor-evaluatedValue > div:last-of-type pre"; + _multiSelectOptions = (option: string) => + "div[title='" + option + "'] input[type='checkbox']"; + _divWithClass = (className: string) => + "//div[contains(@class, '" + className + "')]"; + _multiSelectItem = (item: string) => + "//span[text()='" + + item + + "']/ancestor::div[@class ='rc-select-selection-overflow-item']//span[contains(@class, 'remove-icon')]"; + _listWidget = "div[type='LIST_WIDGET']"; + _dropdownText = ".t--dropdown-option"; + _jsonFormInputField = (fieldName: string) => + `.t--jsonformfield-${fieldName} input`; + _jsonFormHeader = ".t--jsonform-body > div:first-child"; + _jsonFormWidget = ".t--widget-jsonformwidget"; + _lintErrorElement = `span.CodeMirror-lint-mark-error`; + _lintWarningElement = "span.CodeMirror-lint-mark-warning"; + _codeEditorWrapper = ".unfocused-code-editor"; + _datePicker = (date: number) => + "//div[@class ='bp3-datepicker']//div[contains(@class, 'DayPicker-Day')]//div[text()='" + + date + + "']"; + _inputWidgetValueField = (fieldName: string, input: boolean = true) => + `//label[contains(@class, 't--input-widget-label')][text()='${fieldName}']/ancestor::div[@data-testid='input-container']//${ + input ? "input" : "textarea" + }`; + _deleteIcon = "button .bp3-icon-delete"; + _datePickerValue = "div[data-testid='datepicker-container'] input"; + _switchToggle = (switchName: string) => + "//div[contains(@class, 't--switch-widget-label')][text()='" + + switchName + + "']/parent::label/span"; + _jsonToggle = (fieldName: string) => + `//p[text()='${fieldName}']/parent::div//following-sibling::div//input[@type='checkbox']`; + _deployedPage = `.t--page-switch-tab`; + _hints = "ul.CodeMirror-hints li"; + _cancelActionExecution = ".t--cancel-action-button"; } diff --git a/app/client/src/components/editorComponents/Debugger/LogItem.tsx b/app/client/src/components/editorComponents/Debugger/LogItem.tsx index db2602a40b0..65fc9d6b341 100644 --- a/app/client/src/components/editorComponents/Debugger/LogItem.tsx +++ b/app/client/src/components/editorComponents/Debugger/LogItem.tsx @@ -29,6 +29,7 @@ import { TROUBLESHOOT_ISSUE, } from "@appsmith/constants/messages"; import ContextualMenu from "./ContextualMenu"; +import { Colors } from "constants/Colors"; const InnerWrapper = styled.div` display: flex; @@ -91,6 +92,26 @@ const Wrapper = styled.div<{ collapsed: boolean }>` color: ${(props) => props.theme.colors.debugger.warning.time}; } } + .debugger-occurences{ + height: 18px; + width: 18px; + border-radius: 36px; + display: inline-flex; + align-items: center; + justify-content: center; + color: ${Colors.GRAY_900}; + &.${Severity.INFO} { + background-color: ${Colors.GREY_200}; + } + margin-right: 4px; + &.${Severity.ERROR} { + background-color: ${Colors.RED_150}; + } + &.${Severity.WARNING} { + background-color: ${Colors.WARNING_DEBUGGER_GROUPING_BADGE}; + } + ${(props) => getTypographyByKey(props, "u2")} + } .debugger-description { display: flex; align-items: center; @@ -208,6 +229,7 @@ export const getLogItemProps = (e: Log) => { id: e.source ? e.source.id : undefined, messages: e.messages, collapsable: showToggleIcon(e), + occurences: e.occurrenceCount || 1, }; }; @@ -226,6 +248,7 @@ type LogItemProps = { source?: SourceEntity; expand?: boolean; messages?: Message[]; + occurences: number; }; function LogItem(props: LogItemProps) { @@ -286,6 +309,13 @@ function LogItem(props: LogItemProps) { props.category === LOG_CATEGORY.USER_GENERATED ) && (
+ {props.occurences > 1 && ( + + {props.occurences} + + )} e.stopPropagation()} diff --git a/app/client/src/components/editorComponents/Debugger/hooks/debuggerHooks.ts b/app/client/src/components/editorComponents/Debugger/hooks/debuggerHooks.ts index 612ddb1c4e3..2ea4b89e2d1 100644 --- a/app/client/src/components/editorComponents/Debugger/hooks/debuggerHooks.ts +++ b/app/client/src/components/editorComponents/Debugger/hooks/debuggerHooks.ts @@ -47,7 +47,6 @@ export const useFilteredLogs = (query: string, filter?: any) => { return true; }); } - return logs; }; @@ -59,7 +58,7 @@ export const usePagination = (data: Log[], itemsPerPage = 50) => { useEffect(() => { const data = currentData(); setPaginatedData(data); - }, [currentPage, data.length]); + }, [currentPage, data.length, data[data.length - 1]?.occurrenceCount]); const currentData = useCallback(() => { const newMaxPage = Math.ceil(data.length / itemsPerPage); diff --git a/app/client/src/constants/Colors.tsx b/app/client/src/constants/Colors.tsx index a36c9eb98e5..f5bbf9135b8 100644 --- a/app/client/src/constants/Colors.tsx +++ b/app/client/src/constants/Colors.tsx @@ -111,6 +111,7 @@ export const Colors = { WARNING_SOLID: "#FEB811", WARNING_SOLID_HOVER: "#EFA903", + WARNING_DEBUGGER_GROUPING_BADGE: "#EAD592", WARNING_ORANGE: "#FFF8E2", WARNING_OUTLINE_HOVER: "#FFFAE9", WARNING_GHOST_HOVER: "#FBEED0", @@ -203,6 +204,7 @@ export const Colors = { /* RED colors */ RED_50: "#FFEAEC", RED_100: "#FFCACE", + RED_150: "#F1B6B6", RED_200: "#F09493", RED_300: "#E56A69", RED_400: "#EE4643", diff --git a/app/client/src/entities/AppsmithConsole/index.ts b/app/client/src/entities/AppsmithConsole/index.ts index 8454ae7af9e..8602d739c6f 100644 --- a/app/client/src/entities/AppsmithConsole/index.ts +++ b/app/client/src/entities/AppsmithConsole/index.ts @@ -61,6 +61,8 @@ export interface LogActionPayload { logType?: LOG_TYPE; // This is the preview of the log that the user sees. text: string; + // Number of times this log has been repeated + occurrenceCount?: number; // Deconstructed data of the log, this includes the whole nested objects/arrays/strings etc. logData?: any[]; messages?: Array; diff --git a/app/client/src/reducers/uiReducers/debuggerReducer.ts b/app/client/src/reducers/uiReducers/debuggerReducer.ts index 9095b46bc94..4c3cb9eb602 100644 --- a/app/client/src/reducers/uiReducers/debuggerReducer.ts +++ b/app/client/src/reducers/uiReducers/debuggerReducer.ts @@ -5,6 +5,7 @@ import { ReduxActionTypes, } from "@appsmith/constants/ReduxActionConstants"; import { omit, isUndefined } from "lodash"; +import equal from "fast-deep-equal"; const initialState: DebuggerReduxState = { logs: [], @@ -15,12 +16,42 @@ const initialState: DebuggerReduxState = { currentTab: "", }; +// check the last message from the current log and update the occurrence count +const removeRepeatedLogsAndMerge = ( + currentLogs: Log[], + incomingLogs: Log[], +) => { + const outputArray = incomingLogs.reduce((acc: Log[], incomingLog: Log) => { + if (acc.length === 0) { + acc.push(incomingLog); + } else { + const lastLog = acc[acc.length - 1]; + if ( + equal( + omit(lastLog, ["occurrenceCount"]), + omit(incomingLog, ["occurrenceCount"]), + ) + ) { + lastLog.hasOwnProperty("occurrenceCount") && !!lastLog.occurrenceCount + ? lastLog.occurrenceCount++ + : (lastLog.occurrenceCount = 2); + } else { + acc.push(incomingLog); + } + } + return acc; + }, currentLogs); + + return outputArray; +}; + const debuggerReducer = createImmerReducer(initialState, { [ReduxActionTypes.DEBUGGER_LOG]: ( state: DebuggerReduxState, action: ReduxAction, ) => { - state.logs = [...state.logs, ...action.payload]; + // state.logs = [...state.logs, ...action.payload]; + state.logs = removeRepeatedLogsAndMerge(state.logs, action.payload); }, [ReduxActionTypes.CLEAR_DEBUGGER_LOGS]: (state: DebuggerReduxState) => { state.logs = []; diff --git a/app/client/src/utils/AppsmithConsole.ts b/app/client/src/utils/AppsmithConsole.ts index f22ef0dbce0..eebb19e6128 100644 --- a/app/client/src/utils/AppsmithConsole.ts +++ b/app/client/src/utils/AppsmithConsole.ts @@ -47,6 +47,7 @@ function info( severity: Severity.INFO, timestamp, category, + occurrenceCount: 1, }); } @@ -60,6 +61,7 @@ function warning( severity: Severity.WARNING, timestamp, category, + occurrenceCount: 1, }); } @@ -76,6 +78,7 @@ function error( severity: Severity.ERROR, timestamp, category, + occurrenceCount: 1, }); } @@ -91,6 +94,7 @@ function addError( severity: severity, timestamp: getTimeStamp(), category, + occurrenceCount: 1, }), ); } diff --git a/app/client/src/workers/evaluate.ts b/app/client/src/workers/evaluate.ts index 3284bf5679d..50c73d78456 100644 --- a/app/client/src/workers/evaluate.ts +++ b/app/client/src/workers/evaluate.ts @@ -285,7 +285,7 @@ export default function evaluateSync( originalBinding: userScript, }); } finally { - logs = userLogs.flushLogs(skipLogsOperations); + if (!skipLogsOperations) logs = userLogs.flushLogs(); for (const entity in GLOBAL_DATA) { // @ts-expect-error: Types are not available delete self[entity];