diff --git a/app/client/cypress/e2e/Regression/ClientSide/Binding/DatePicker_Text_spec.js b/app/client/cypress/e2e/Regression/ClientSide/Binding/DatePicker_Text_spec.js index 037e637f8cd..52c699902ba 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Binding/DatePicker_Text_spec.js +++ b/app/client/cypress/e2e/Regression/ClientSide/Binding/DatePicker_Text_spec.js @@ -14,7 +14,7 @@ describe( _.agHelper.AddDsl("uiBindDsl"); }); // Skipping tests due to issue - https://www.notion.so/appsmith/f353d8c6bd664f79ad858a42010cdfc8?v=f04cde23f6424aeb9d5a6e389cd172bd&p=0717892d43684c40bae4e2c87b8308cb&pm=s - it.skip("1. DatePicker-Text, Validate selectedDate functionality", function () { + it("1. DatePicker-Text, Validate selectedDate functionality", function () { /** * Bind DatePicker1 to Text for "selectedDate" */ @@ -25,9 +25,8 @@ describe( * Set the Calender for today's date in DatePicker1 */ cy.openPropertyPane("datepickerwidget"); - cy.get(formWidgetsPage.defaultDate).click(); - cy.ClearDateFooter(); - cy.SetDateToToday(); + cy.get(formWidgetsPage.datepickerWidget).first().click(); + cy.get(formWidgetsPage.datepickerFooter).contains("Today").click(); cy.getDate(1, "YYYY-MM-DD").then((date) => { cy.log("retured date" + date); @@ -43,15 +42,15 @@ describe( cy.get(publishPage.datepickerWidget + commonlocators.inputField) .eq(0) .click(); - cy.ClearDateFooter(); - cy.setDate(1, "ddd MMM DD YYYY"); + cy.SetDateToToday(); + cy.setDate(1, "ddd MMM DD YYYY", "v1"); cy.get(commonlocators.labelTextStyle).should("contain", nextDay); }); cy.get(commonlocators.backToEditor).click(); }); - it.skip("2. DatePicker1-text: Change the date in DatePicker1 and Validate the same in text widget", function () { + it("2. DatePicker1-text: Change the date in DatePicker1 and Validate the same in text widget", function () { cy.openPropertyPane("textwidget"); /** @@ -75,9 +74,9 @@ describe( */ cy.openPropertyPane("datepickerwidget"); cy.get(formWidgetsPage.defaultDate).click(); - cy.ClearDateFooter(); - cy.setDate(1, "ddd MMM DD YYYY"); - // cy.get(commonlocators.onDateSelectedField).click(); + cy.get(formWidgetsPage.dayPickerToday).click(); + cy.get(formWidgetsPage.defaultDate).click(); + cy.setDate(1); /** *Validate the date in text widget @@ -89,7 +88,7 @@ describe( }); }); - it.skip("3. Validate the Date is not changed in DatePicker2", function () { + it("3. Validate the Date is not changed in DatePicker2", function () { cy.log("dateDp2:" + dateDp2); cy.get(formWidgetsPage.datepickerWidget + commonlocators.inputField) .eq(1) @@ -124,7 +123,7 @@ describe( _.deployMode.NavigateBacktoEditor(); }); - it.skip("5. Checks if on deselection of date triggers the onDateSelected action or not.", function () { + it("5. Checks if on deselection of date triggers the onDateSelected action or not.", function () { /** * bind datepicker to show a message "Hello" on date selected */ @@ -135,7 +134,6 @@ describe( * checking if on selecting the date triggers the message */ cy.get(formWidgetsPage.datepickerWidget).first().click(); - cy.ClearDateFooter(); cy.SetDateToToday(); cy.get(commonlocators.toastmsg).contains("hello"); diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Datepicker/DatePicker2_spec.js b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Datepicker/DatePicker2_spec.js index a84f5c06692..065f0c7a9e4 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Datepicker/DatePicker2_spec.js +++ b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Datepicker/DatePicker2_spec.js @@ -22,7 +22,7 @@ describe( it("DatePicker-Date Name validation", function () { // changing the date to today cy.get(formWidgetsPage.defaultDate).click(); - cy.SetDateToToday(); + cy.get(formWidgetsPage.dayPickerToday).click(); //changing the Button Name cy.widgetText( diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Datepicker/DatePicker_With_Switch_spec.js b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Datepicker/DatePicker_With_Switch_spec.js index d48e5ce34c9..f5257949830 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Datepicker/DatePicker_With_Switch_spec.js +++ b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Datepicker/DatePicker_With_Switch_spec.js @@ -28,11 +28,14 @@ describe( cy.closePropertyPane(); }); // Skipping tests due to issue - https://www.notion.so/appsmith/f353d8c6bd664f79ad858a42010cdfc8?v=f04cde23f6424aeb9d5a6e389cd172bd&p=0717892d43684c40bae4e2c87b8308cb&pm=s - it.skip("Date Widget with Reset widget being switch widget", function () { + it("Date Widget with Reset widget being switch widget", function () { EditorNavigation.SelectEntityByName("DatePicker1", EntityType.Widget); + cy.get(formWidgetsPage.datePickerInput).click(); + _.agHelper.GetNClick(widgetsPage.todayText); + cy.get(formWidgetsPage.defaultDate).click(); + _.agHelper.GetNClick(".ads-v2-datepicker__calender-today"); cy.get(formWidgetsPage.defaultDate).click(); - cy.SetDateToToday(); cy.setDate(1, "ddd MMM DD YYYY"); const nextDay = dayjs().format("DD/MM/YYYY"); cy.log(nextDay); @@ -51,18 +54,10 @@ describe( cy.get(widgetsPage.switchWidgetInactive).should("be.visible"); }); - it.skip("DatePicker-Date change and validate switch widget status", function () { + it("DatePicker-Date change and validate switch widget status", function () { cy.get(widgetsPage.datepickerInput).click({ force: true }); - cy.SetDateToToday(); - cy.get(widgetsPage.switchWidgetActive).should("be.visible"); - cy.get(".t--toast-action span") - .last() - .invoke("text") - .then((text) => { - const toasttext = text; - cy.log(toasttext); - expect(text.trim()).to.equal(toasttext.trim()); - }); + _.agHelper.GetNClick(widgetsPage.todayText); + _.agHelper.AssertClassExists(".bp3-switch", "t--switch-widget-active"); }); }, ); diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/TableV2_Url_Column_spec.ts b/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/TableV2_Url_Column_spec.ts index f866def606d..e906d531598 100644 --- a/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/TableV2_Url_Column_spec.ts +++ b/app/client/cypress/e2e/Regression/ClientSide/Widgets/TableV2/TableV2_Url_Column_spec.ts @@ -20,18 +20,23 @@ describe( table.ReadTableRowColumnData(3, 0, "v2").then(($cellData) => { expect($cellData).to.eq("Profile pic"); }); - table.AssertURLColumnNavigation( - 0, - 0, - "https://randomuser.me/api/portraits/med/women/39.jpg", - "v2", - ); - table.AssertURLColumnNavigation( - 3, - 0, - "https://randomuser.me/api/portraits/med/men/52.jpg", - "v2", - ); + + agHelper + .GetElement(`${table._tableRowColumnData(0, 0, "v2")} a`) + .should( + "have.attr", + "href", + "https://randomuser.me/api/portraits/med/women/39.jpg", + ) + .should("have.attr", "target", "_blank"); + agHelper + .GetElement(`${table._tableRowColumnData(3, 0, "v2")} a`) + .should( + "have.attr", + "href", + "https://randomuser.me/api/portraits/med/men/52.jpg", + ) + .should("have.attr", "target", "_blank"); }); }, ); diff --git a/app/client/cypress/fixtures/uiBindnewDsl.json b/app/client/cypress/fixtures/uiBindnewDsl.json new file mode 100644 index 00000000000..6d45729acce --- /dev/null +++ b/app/client/cypress/fixtures/uiBindnewDsl.json @@ -0,0 +1,540 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896, + "snapColumns": 64, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 600, + "containerStyle": "none", + "snapRows": 124, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 90, + "minHeight": 1292, + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "boxShadow": "none", + "widgetName": "Container1", + "topRow": 0, + "bottomRow": 60, + "parentRowSpace": 38, + "type": "CONTAINER_WIDGET", + "parentColumnSpace": 75.25, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "labelTextSize": "0.875rem", + "boxShadow": "none", + "backgroundColor": "#FFFFFF", + "widgetName": "utncsu66ty", + "rightColumn": 2408, + "orientation": "VERTICAL", + "snapColumns": 16, + "detachFromLayout": true, + "widgetId": "acizsl94my", + "containerStyle": "none", + "topRow": 0, + "bottomRow": 380, + "parentRowSpace": 1, + "isVisible": true, + "type": "CANVAS_WIDGET", + "canExtend": false, + "version": 1, + "isLoading": false, + "parentColumnSpace": 1, + "leftColumn": 0, + "dynamicBindingPathList": [], + "borderRadius": "0px", + "children": [ + { + "boxShadow": "none", + "widgetName": "Text5", + "topRow": 28, + "bottomRow": 32, + "parentRowSpace": 38, + "type": "TEXT_WIDGET", + "parentColumnSpace": 62.484375, + "overflow": "NONE", + "fontFamily": "System Default", + "dynamicTriggerPathList": [], + "leftColumn": 16, + "dynamicBindingPathList": [ + { + "key": "text" + } + ], + "text": "{{DatePicker1.selectedDate}}", + "labelTextSize": "0.875rem", + "rightColumn": 32, + "textAlign": "LEFT", + "dynamicHeight": "FIXED", + "widgetId": "t7c7i4gv0f", + "isVisible": true, + "fontStyle": "BOLD", + "version": 1, + "textColor": "#231F20", + "parentId": "acizsl94my", + "isLoading": false, + "borderRadius": "0px", + "maxDynamicHeight": 9000, + "fontSize": "0.875rem", + "minDynamicHeight": 4 + }, + { + "needsErrorInfo": false, + "boxShadow": "none", + "mobileBottomRow": 12, + "widgetName": "DatePicker1", + "minDate": "1920-12-31T18:30:00.000Z", + "dateFormat": "YYYY-MM-DD HH:mm", + "topRow": 5, + "bottomRow": 12, + "shortcuts": false, + "parentRowSpace": 10, + "labelWidth": 5, + "type": "DATE_PICKER_WIDGET2", + "mobileRightColumn": 30, + "animateLoading": true, + "parentColumnSpace": 7.26171875, + "leftColumn": 10, + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + } + ], + "labelPosition": "Top", + "isDisabled": false, + "key": "frcocd42ul", + "labelTextSize": "0.875rem", + "isRequired": false, + "defaultDate": "2024-10-24T07:39:07.659Z", + "rightColumn": 30, + "dynamicHeight": "FIXED", + "widgetId": "pwo88q4udx", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "minWidth": 450, + "isVisible": true, + "datePickerType": "DATE_PICKER", + "label": "Label", + "version": 2, + "parentId": "acizsl94my", + "labelAlignment": "left", + "renderMode": "CANVAS", + "isLoading": false, + "mobileTopRow": 5, + "timePrecision": "minute", + "responsiveBehavior": "fill", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "mobileLeftColumn": 10, + "maxDynamicHeight": 9000, + "firstDayOfWeek": 0, + "closeOnSelection": true, + "maxDate": "2121-12-31T18:29:00.000Z", + "minDynamicHeight": 4 + }, + { + "needsErrorInfo": false, + "boxShadow": "none", + "mobileBottomRow": 22, + "widgetName": "DatePicker2", + "minDate": "1920-12-31T18:30:00.000Z", + "dateFormat": "YYYY-MM-DD HH:mm", + "topRow": 14, + "bottomRow": 21, + "shortcuts": false, + "parentRowSpace": 10, + "labelWidth": 5, + "type": "DATE_PICKER_WIDGET2", + "mobileRightColumn": 29, + "animateLoading": true, + "parentColumnSpace": 7.26171875, + "leftColumn": 10, + "dynamicBindingPathList": [ + { + "key": "accentColor" + }, + { + "key": "borderRadius" + } + ], + "labelPosition": "Top", + "isDisabled": false, + "key": "frcocd42ul", + "labelTextSize": "0.875rem", + "isRequired": false, + "defaultDate": "2024-10-24T07:39:07.659Z", + "rightColumn": 30, + "dynamicHeight": "FIXED", + "widgetId": "gwcu42z6yn", + "accentColor": "{{appsmith.theme.colors.primaryColor}}", + "minWidth": 450, + "isVisible": true, + "datePickerType": "DATE_PICKER", + "label": "Label", + "version": 2, + "parentId": "acizsl94my", + "labelAlignment": "left", + "renderMode": "CANVAS", + "isLoading": false, + "mobileTopRow": 15, + "timePrecision": "minute", + "responsiveBehavior": "fill", + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "mobileLeftColumn": 9, + "maxDynamicHeight": 9000, + "firstDayOfWeek": 0, + "closeOnSelection": true, + "maxDate": "2121-12-31T18:29:00.000Z", + "minDynamicHeight": 4 + } + ] + } + ], + "labelTextSize": "0.875rem", + "backgroundColor": "#FFFFFF", + "rightColumn": 56, + "orientation": "VERTICAL", + "snapColumns": 16, + "dynamicHeight": "FIXED", + "widgetId": "mp429a7dl3", + "containerStyle": "card", + "isVisible": true, + "version": 1, + "isLoading": false, + "borderRadius": "0px", + "maxDynamicHeight": 9000, + "minDynamicHeight": 4 + }, + { + "boxShadow": "none", + "widgetName": "Modal1", + "topRow": 0, + "bottomRow": 0, + "parentRowSpace": 1, + "type": "MODAL_WIDGET", + "shouldScrollContents": true, + "parentColumnSpace": 1, + "leftColumn": 0, + "dynamicBindingPathList": [], + "children": [ + { + "boxShadow": "none", + "widgetName": "Canvas1", + "topRow": 0, + "bottomRow": 240, + "parentRowSpace": 1, + "canExtend": true, + "type": "CANVAS_WIDGET", + "shouldScrollContents": false, + "minHeight": 0, + "parentColumnSpace": 1, + "leftColumn": 0, + "dynamicBindingPathList": [], + "children": [ + { + "labelTextSize": "0.875rem", + "isRequired": false, + "boxShadow": "none", + "widgetName": "Icon1", + "rightColumn": 64, + "onClick": "{{closeModal(Modal1.name)}}", + "iconName": "cross", + "buttonColor": "#2E3D49", + "widgetId": "t3sjfihdb1", + "topRow": 0, + "bottomRow": 4, + "isVisible": true, + "type": "ICON_BUTTON_WIDGET", + "version": 1, + "parentId": "cwamdbv44c", + "isLoading": false, + "leftColumn": 60, + "dynamicBindingPathList": [], + "borderRadius": "0px", + "buttonVariant": "TERTIARY", + "iconSize": 24 + }, + { + "boxShadow": "none", + "widgetName": "Text4", + "dynamicPropertyPathList": [ + { + "key": "fontSize" + } + ], + "topRow": 0, + "bottomRow": 4, + "type": "TEXT_WIDGET", + "overflow": "NONE", + "fontFamily": "System Default", + "leftColumn": 0, + "dynamicBindingPathList": [], + "text": "Modal Title", + "labelTextSize": "0.875rem", + "rightColumn": 60, + "dynamicHeight": "FIXED", + "widgetId": "x893ud3zjh", + "isVisible": true, + "fontStyle": "BOLD", + "version": 1, + "textColor": "#231F20", + "parentId": "cwamdbv44c", + "isLoading": false, + "borderRadius": "0px", + "maxDynamicHeight": 9000, + "fontSize": "1.5rem", + "minDynamicHeight": 4 + }, + { + "labelTextSize": "0.875rem", + "boxShadow": "none", + "widgetName": "Button2", + "rightColumn": 52, + "isDefaultClickDisabled": true, + "buttonColor": "#03B365", + "widgetId": "q9snwskqan", + "topRow": 16, + "bottomRow": 20, + "isVisible": true, + "type": "BUTTON_WIDGET", + "version": 1, + "recaptchaType": "V3", + "parentId": "cwamdbv44c", + "isLoading": false, + "leftColumn": 40, + "dynamicBindingPathList": [], + "borderRadius": "0px", + "buttonVariant": "PRIMARY", + "text": "Cancel", + "isDisabled": false + }, + { + "labelTextSize": "0.875rem", + "boxShadow": "none", + "widgetName": "Button3", + "rightColumn": 64, + "isDefaultClickDisabled": true, + "buttonColor": "#03B365", + "widgetId": "tufuj2kdpz", + "topRow": 16, + "bottomRow": 20, + "isVisible": true, + "type": "BUTTON_WIDGET", + "version": 1, + "recaptchaType": "V3", + "parentId": "cwamdbv44c", + "isLoading": false, + "leftColumn": 52, + "dynamicBindingPathList": [], + "borderRadius": "0px", + "buttonVariant": "PRIMARY", + "text": "Confirm", + "isDisabled": false + } + ], + "isDisabled": false, + "labelTextSize": "0.875rem", + "rightColumn": 0, + "detachFromLayout": true, + "widgetId": "cwamdbv44c", + "isVisible": true, + "version": 1, + "parentId": "bx9a2jg08o", + "blueprint": { + "view": [ + { + "type": "ICON_WIDGET", + "position": { + "left": 15, + "top": 0 + }, + "size": { + "rows": 1, + "cols": 1 + }, + "props": { + "iconName": "cross", + "iconSize": 24, + "color": "#040627" + } + }, + { + "type": "TEXT_WIDGET", + "position": { + "left": 0, + "top": 0 + }, + "size": { + "rows": 1, + "cols": 15 + }, + "props": { + "text": "Modal Title", + "textStyle": "HEADING" + } + }, + { + "type": "BUTTON_WIDGET", + "position": { + "left": 10, + "top": 4 + }, + "size": { + "rows": 1, + "cols": 3 + }, + "props": { + "text": "Cancel", + "buttonStyle": "SECONDARY_BUTTON" + } + }, + { + "type": "BUTTON_WIDGET", + "position": { + "left": 13, + "top": 4 + }, + "size": { + "rows": 1, + "cols": 3 + }, + "props": { + "text": "Confirm", + "buttonStyle": "PRIMARY_BUTTON" + } + } + ], + "operations": [ + { + "type": "MODIFY_PROPS" + } + ] + }, + "isLoading": false, + "borderRadius": "0px" + } + ], + "height": 240, + "labelTextSize": "0.875rem", + "rightColumn": 0, + "detachFromLayout": true, + "dynamicHeight": "FIXED", + "widgetId": "bx9a2jg08o", + "isVisible": false, + "canOutsideClickClose": true, + "canEscapeKeyClose": true, + "version": 2, + "parentId": "0", + "blueprint": { + "view": [ + { + "type": "CANVAS_WIDGET", + "position": { + "left": 0, + "top": 0 + }, + "props": { + "detachFromLayout": true, + "canExtend": true, + "isVisible": true, + "isDisabled": false, + "shouldScrollContents": false, + "children": [], + "blueprint": { + "view": [ + { + "type": "ICON_WIDGET", + "position": { + "left": 15, + "top": 0 + }, + "size": { + "rows": 1, + "cols": 1 + }, + "props": { + "iconName": "cross", + "iconSize": 24, + "color": "#040627" + } + }, + { + "type": "TEXT_WIDGET", + "position": { + "left": 0, + "top": 0 + }, + "size": { + "rows": 1, + "cols": 15 + }, + "props": { + "text": "Modal Title", + "textStyle": "HEADING" + } + }, + { + "type": "BUTTON_WIDGET", + "position": { + "left": 10, + "top": 4 + }, + "size": { + "rows": 1, + "cols": 3 + }, + "props": { + "text": "Cancel", + "buttonStyle": "SECONDARY_BUTTON" + } + }, + { + "type": "BUTTON_WIDGET", + "position": { + "left": 13, + "top": 4 + }, + "size": { + "rows": 1, + "cols": 3 + }, + "props": { + "text": "Confirm", + "buttonStyle": "PRIMARY_BUTTON" + } + } + ], + "operations": [ + { + "type": "MODIFY_PROPS" + } + ] + } + } + } + ] + }, + "isLoading": false, + "borderRadius": "0px", + "maxDynamicHeight": 9000, + "width": 456, + "minDynamicHeight": 4 + } + ] + }, + "layoutOnLoadActions": [], + "layoutOnLoadActionErrors": [], + "actionUpdates": [], + "messages": [] +} diff --git a/app/client/cypress/locators/FormWidgets.json b/app/client/cypress/locators/FormWidgets.json index db603de7b8c..43462381c2d 100644 --- a/app/client/cypress/locators/FormWidgets.json +++ b/app/client/cypress/locators/FormWidgets.json @@ -78,5 +78,6 @@ "minDateTextArea" : ".t--property-control-mindate .CodeMirror textarea", "minDateInput" : ".t--property-control-mindate .ads-v2-input__input-section-input", "datePickerInput": ".t--widget-datepickerwidget2 .bp3-input", - "dayPickerNextButton": ".DayPicker-NavButton--next" + "dayPickerNextButton": ".DayPicker-NavButton--next", + "dayPickerToday": ".ads-v2-datepicker__calender-today" } diff --git a/app/client/cypress/locators/Widgets.json b/app/client/cypress/locators/Widgets.json index 92b161d141b..4dcb4ce330b 100644 --- a/app/client/cypress/locators/Widgets.json +++ b/app/client/cypress/locators/Widgets.json @@ -234,5 +234,7 @@ "propertyPaneSaveButton": ".t--property-pane-section-collapse-savebutton", "firstEditInput":"[data-colindex=0][data-rowindex=0] .t--inlined-cell-editor input.bp3-input", "cellControlSwitch" : ".t--property-control-cellwrapping .ads-v2-switch", - "propertyControlLabel" : ".t--property-control-label" + "propertyControlLabel" : ".t--property-control-label", + "todayText": "span:contains('Today')", + "dayPickerToday": ".DayPicker-Day--today" } diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index d922a6f902f..c088b90d868 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -526,9 +526,17 @@ Cypress.Commands.add("getDate", (date, dateFormate) => { return eDate; }); -Cypress.Commands.add("setDate", (date) => { - const expDate = dayjs().add(date, "days").format("dddd, MMMM Do, YYYY"); - cy.get(`.react-datepicker__day[aria-label^="Choose ${expDate}"]`).click(); +Cypress.Commands.add("setDate", (date, dateFormate, ver = "v2") => { + if (ver == "v2") { + const expDate = dayjs().add(date, "days").format("dddd, MMMM D"); + cy.get(`.react-datepicker__day[aria-label^="Choose ${expDate}"]`) + .first() + .click(); + } else if (ver == "v1") { + const expDate = dayjs().add(date, "days").format(dateFormate); + const sel = `.DayPicker-Day[aria-label=\"${expDate}\"]`; + cy.get(sel).click(); + } }); Cypress.Commands.add("validateDisableWidget", (widgetCss, disableCss) => { diff --git a/app/client/cypress/support/widgetCommands.js b/app/client/cypress/support/widgetCommands.js index fd0772748f6..23d1d828f6a 100644 --- a/app/client/cypress/support/widgetCommands.js +++ b/app/client/cypress/support/widgetCommands.js @@ -837,7 +837,7 @@ Cypress.Commands.add("selectWidgetForReset", (value) => { }); Cypress.Commands.add("SetDateToToday", () => { - cy.get(".react-datepicker .react-datepicker__day--today").click({ + cy.get("button:contains('Today')").click({ force: true, }); agHelper.AssertAutoSave(); diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/ActionForm/Section/index.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/components/ActionForm/Section/index.tsx index 7b664e11960..d47fd7f0616 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionForm/components/ActionForm/Section/index.tsx +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/ActionForm/Section/index.tsx @@ -4,7 +4,7 @@ import styles from "./styles.module.css"; interface SectionProps extends React.HTMLAttributes { children: React.ReactNode; - isStandalone?: boolean; + withoutPadding?: boolean; isFullWidth?: boolean; } @@ -12,7 +12,7 @@ const Section: React.FC = ({ children, className, isFullWidth = false, - isStandalone = false, + withoutPadding = false, ...props }) => { const classNames = clsx(styles.section, className); @@ -21,7 +21,7 @@ const Section: React.FC = ({
{children} diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/ActionForm/Section/styles.module.css b/app/client/src/PluginActionEditor/components/PluginActionForm/components/ActionForm/Section/styles.module.css index fb03da7ffbd..ac73ba3c3bc 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionForm/components/ActionForm/Section/styles.module.css +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/ActionForm/Section/styles.module.css @@ -6,11 +6,22 @@ max-width: 800px; justify-content: center; - &[data-standalone="false"] { + &[data-withoutPadding="true"] { + padding: 0; + } + + /* We do not want padding above the first section */ + &[data-withoutPadding="false"]:first-child { + padding-bottom: var(--ads-v2-spaces-6); + } + + /* All other sections expect first will have padding top and bottom */ + &[data-withoutPadding="false"]:not(:first-child) { padding-block: var(--ads-v2-spaces-6); } - &[data-standalone="false"]:not(:last-child) { + /* We will also render a border below sections expect for the last section */ + &[data-withoutPadding="false"]:not(:last-child) { border-bottom: 1px solid var(--ads-v2-color-border); } diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/ApiEditor/PostBodyData.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/components/ApiEditor/PostBodyData.tsx index de373520ece..cee26fa6075 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionForm/components/ApiEditor/PostBodyData.tsx +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/ApiEditor/PostBodyData.tsx @@ -28,7 +28,6 @@ import { Select, Option } from "@appsmith/ads"; const PostBodyContainer = styled.div` display: flex; flex-direction: column; - padding: 12px 0px 0px; background-color: var(--ads-v2-color-bg); height: 100%; gap: var(--ads-v2-spaces-4); diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/CommonEditorForm.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/CommonEditorForm.tsx index a9ae550c0ec..802a5294572 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/CommonEditorForm.tsx +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/CommonEditorForm.tsx @@ -29,7 +29,13 @@ const CommonEditorForm = (props: Props) => { } = useGetFormActionValues(); return ( - + -
+ +
-
- +
+ - +
); } diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/RequestTabs.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/RequestTabs.tsx index f3e0adb9065..62b22a18b6c 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/RequestTabs.tsx +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/RequestTabs.tsx @@ -22,13 +22,9 @@ const SettingsWrapper = styled.div` padding: 0; } `; -const TabsListWrapper = styled.div` - padding: 0 var(--ads-v2-spaces-7); -`; const StyledTabPanel = styled(TabPanel)` height: calc(100% - 50px); overflow: auto; - padding: 0 var(--ads-v2-spaces-7); `; export function RequestTabs(props: { @@ -61,31 +57,37 @@ export function RequestTabs(props: { ); return ( - - - - {Object.values(API_EDITOR_TABS) - .filter((tab) => { - return !(!props.showSettings && tab === API_EDITOR_TABS.SETTINGS); - }) - .map((tab) => ( - - {createMessage(API_EDITOR_TAB_TITLES[tab])} - - ))} - - + + + {Object.values(API_EDITOR_TABS) + .filter((tab) => { + return !(!props.showSettings && tab === API_EDITOR_TABS.SETTINGS); + }) + .map((tab) => ( + + {createMessage(API_EDITOR_TAB_TITLES[tab])} + + ))} + props.theme.spaces[4]}px - ${(props) => props.theme.spaces[14]}px 0 0; -`; +const KeyValueFlexContainer = styled.div``; const FormRowWithLabel = styled(FormRow)` flex-wrap: wrap; diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/components/EmbeddedDatasourcePathField.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/components/EmbeddedDatasourcePathField.tsx index 136c51b0cce..87615cbf7f2 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/components/EmbeddedDatasourcePathField.tsx +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/components/EmbeddedDatasourcePathField.tsx @@ -92,6 +92,7 @@ const DatasourceContainer = styled.div` position: relative; align-items: center; height: 36px; + gap: var(--ads-v2-spaces-4); .t--datasource-editor { background-color: var(--ads-v2-color-bg); .cm-s-duotone-light.CodeMirror { @@ -101,10 +102,6 @@ const DatasourceContainer = styled.div` z-index: ${Indices.Layer5}; } } - - .t--store-as-datasource { - margin-left: 10px; - } `; const hintContainerStyles: React.CSSProperties = { diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/styles.ts b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/styles.ts new file mode 100644 index 00000000000..a60145281e9 --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/styles.ts @@ -0,0 +1,12 @@ +import styled from "styled-components"; + +export const RequestMethodSelectContainer = styled.div` + width: 100px; + .ads-v2-select > .rc-select-selector { + min-width: 100px; + } +`; + +export const DatasourcePathFieldContainer = styled.div` + width: 100%; +`; diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/GraphQLEditor/PostBodyData.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/components/GraphQLEditor/PostBodyData.tsx index c66aa404858..aaabc25d70d 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionForm/components/GraphQLEditor/PostBodyData.tsx +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/GraphQLEditor/PostBodyData.tsx @@ -15,7 +15,7 @@ import FormLabel from "components/editorComponents/FormLabel"; const PostBodyContainer = styled.div` &&&& .CodeMirror { height: auto; - min-height: 250px; + min-height: 150px; } `; @@ -43,7 +43,7 @@ function PostBodyData(props: Props) { return ( -
+
Query diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/UQIEditor/UQIEditorForm.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/components/UQIEditor/UQIEditorForm.tsx index fdee4e73717..44750c91be8 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionForm/components/UQIEditor/UQIEditorForm.tsx +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/UQIEditor/UQIEditorForm.tsx @@ -2,36 +2,33 @@ import React from "react"; import FormRender from "./FormRender"; import { usePluginActionContext } from "../../../../PluginActionContext"; import { QUERY_EDITOR_FORM_NAME } from "ee/constants/forms"; -import { getFormValues, reduxForm } from "redux-form"; -import type { QueryAction, SaaSAction } from "entities/Action"; -import { useSelector } from "react-redux"; -import { getFormEvaluationState } from "selectors/formSelectors"; +import { reduxForm } from "redux-form"; import { Flex } from "@appsmith/ads"; +import { useGoogleSheetsSetDefaultProperty } from "./hooks/useGoogleSheetsSetDefaultProperty"; +import { useFormData } from "./hooks/useFormData"; +import { useInitFormEvaluation } from "./hooks/useInitFormEvaluation"; const UQIEditorForm = () => { - const { editorConfig, plugin } = usePluginActionContext(); + const { + editorConfig, + plugin: { uiComponent }, + } = usePluginActionContext(); - const formData = useSelector(getFormValues(QUERY_EDITOR_FORM_NAME)) as - | QueryAction - | SaaSAction; + useInitFormEvaluation(); - const formEvaluation = useSelector(getFormEvaluationState); + // Set default values for Google Sheets + useGoogleSheetsSetDefaultProperty(); - let formEvaluationState = {}; - - // Fetching evaluations state only once the formData is populated - if (!!formData) { - formEvaluationState = formEvaluation[formData.id]; - } + const { data, evaluationState } = useFormData(); return ( ); diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/UQIEditor/hooks/useFormData.ts b/app/client/src/PluginActionEditor/components/PluginActionForm/components/UQIEditor/hooks/useFormData.ts new file mode 100644 index 00000000000..61f798cc093 --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/UQIEditor/hooks/useFormData.ts @@ -0,0 +1,22 @@ +import { useSelector } from "react-redux"; +import { getFormValues } from "redux-form"; +import { QUERY_EDITOR_FORM_NAME } from "ee/constants/forms"; +import type { QueryAction, SaaSAction } from "entities/Action"; +import { getFormEvaluationState } from "selectors/formSelectors"; + +export const useFormData = () => { + const data = useSelector(getFormValues(QUERY_EDITOR_FORM_NAME)) as + | QueryAction + | SaaSAction; + + const formEvaluation = useSelector(getFormEvaluationState); + + let evaluationState = {}; + + // Fetching evaluations state only once the formData is populated + if (!!data) { + evaluationState = formEvaluation[data.id]; + } + + return { data, evaluationState }; +}; diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/UQIEditor/hooks/useGoogleSheetsSetDefaultProperty.ts b/app/client/src/PluginActionEditor/components/PluginActionForm/components/UQIEditor/hooks/useGoogleSheetsSetDefaultProperty.ts new file mode 100644 index 00000000000..b888239e3aa --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/UQIEditor/hooks/useGoogleSheetsSetDefaultProperty.ts @@ -0,0 +1,60 @@ +import { useEffect } from "react"; +import { PluginPackageName } from "entities/Action"; +import { merge } from "lodash"; +import { getConfigInitialValues } from "components/formControls/utils"; +import { diff, type Diff } from "deep-diff"; +import { getPathAndValueFromActionDiffObject } from "utils/getPathAndValueFromActionDiffObject"; +import { setActionProperty } from "actions/pluginActionActions"; +import { usePluginActionContext } from "../../../../../PluginActionContext"; +import { useDispatch } from "react-redux"; + +export const useGoogleSheetsSetDefaultProperty = () => { + const { + action, + editorConfig, + plugin: { packageName }, + settingsConfig, + } = usePluginActionContext(); + + const dispatch = useDispatch(); + + useEffect( + function setDefaultValuesForGoogleSheets() { + if (packageName === PluginPackageName.GOOGLE_SHEETS) { + const initialValues = {}; + + merge( + initialValues, + getConfigInitialValues(editorConfig as Record[]), + ); + + merge( + initialValues, + getConfigInitialValues(settingsConfig as Record[]), + ); + + // initialValues contains merge of action, editorConfig, settingsConfig and will be passed to redux form + merge(initialValues, action); + + // @ts-expect-error: Types are not available + const actionObjectDiff: undefined | Diff[] = + diff(action, initialValues); + + const { path = "", value = "" } = { + ...getPathAndValueFromActionDiffObject(actionObjectDiff), + }; + + if (value && path) { + dispatch( + setActionProperty({ + actionId: action.id, + propertyName: path, + value, + }), + ); + } + } + }, + [action, dispatch, editorConfig, packageName, settingsConfig], + ); +}; diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/UQIEditor/hooks/useInitFormEvaluation.ts b/app/client/src/PluginActionEditor/components/PluginActionForm/components/UQIEditor/hooks/useInitFormEvaluation.ts new file mode 100644 index 00000000000..5c52cdfa89d --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/UQIEditor/hooks/useInitFormEvaluation.ts @@ -0,0 +1,21 @@ +import { useEffect } from "react"; +import { initFormEvaluations } from "actions/evaluationActions"; +import { useDispatch } from "react-redux"; +import { usePluginActionContext } from "../../../../../PluginActionContext"; + +export const useInitFormEvaluation = () => { + const dispatch = useDispatch(); + + const { + action: { baseId }, + editorConfig, + settingsConfig, + } = usePluginActionContext(); + + useEffect( + function formEvaluationInit() { + dispatch(initFormEvaluations(editorConfig, settingsConfig, baseId)); + }, + [baseId, dispatch, editorConfig, settingsConfig], + ); +}; diff --git a/app/client/src/components/editorComponents/form/fields/KeyValueFieldArray.tsx b/app/client/src/components/editorComponents/form/fields/KeyValueFieldArray.tsx index 8e5e514dc38..c30513169b5 100644 --- a/app/client/src/components/editorComponents/form/fields/KeyValueFieldArray.tsx +++ b/app/client/src/components/editorComponents/form/fields/KeyValueFieldArray.tsx @@ -33,7 +33,7 @@ const KeyValueStackContainer = styled.div` // `; const FormRowWithLabel = styled(FormRow)` flex-wrap: wrap; - margin-bottom: ${(props) => props.theme.spaces[2] - 1}px; + margin-bottom: var(--ads-v2-spaces-3); ${FormLabel} { width: 100%; } @@ -52,7 +52,7 @@ const Flex = styled.div<{ size: number }>` ${(props) => props.size === 3 ? ` - margin-left: 5px; + margin-left: var(--ads-v2-spaces-3); ` : null}; `; @@ -81,7 +81,7 @@ const DynamicTextFieldWithDropdownWrapper = styled.div` const DynamicDropdownFieldWrapper = styled.div` position: relative; - margin-left: 5px; + margin-left: var(--ads-v2-spaces-3); border-color: var(--ads-v2-color-border); color: var(--ads-v2-color-fg); diff --git a/app/client/src/pages/Editor/APIEditor/CommonEditorForm.tsx b/app/client/src/pages/Editor/APIEditor/CommonEditorForm.tsx index 10e04d269f3..a024ab6b386 100644 --- a/app/client/src/pages/Editor/APIEditor/CommonEditorForm.tsx +++ b/app/client/src/pages/Editor/APIEditor/CommonEditorForm.tsx @@ -101,6 +101,7 @@ const TabbedViewContainer = styled.div` overflow: auto; position: relative; height: 100%; + padding: 0 var(--ads-v2-spaces-7); `; const Wrapper = styled.div` diff --git a/app/client/src/pages/Editor/IDE/EditorTabs/List.tsx b/app/client/src/pages/Editor/IDE/EditorTabs/List.tsx index 0884b47f778..e73b42762e1 100644 --- a/app/client/src/pages/Editor/IDE/EditorTabs/List.tsx +++ b/app/client/src/pages/Editor/IDE/EditorTabs/List.tsx @@ -8,6 +8,9 @@ import ListQuery from "../EditorPane/Query/List"; import ListJSObjects from "../EditorPane/JS/List"; const ListContainer = styled(Flex)` + position: absolute; + top: 32px; + padding-top: 4px; & .t--entity-item { grid-template-columns: 0 auto 1fr auto auto auto auto auto; height: 32px; @@ -24,7 +27,6 @@ export const List = () => { return ( { } return ( - + ) => { - e.stopPropagation(); - window.open(props.url, "_blank"); - }} - textColor={props.textColor} - textSize={props.textSize} - verticalAlignment={props.verticalAlignment} - > -
{content}
- - - - - ); -} - export function AutoToolTipComponent(props: Props) { const content = useToolTip( props.children, @@ -191,12 +159,27 @@ export function AutoToolTipComponent(props: Props) { props.columnType === ColumnTypes.BUTTON, ); - if (props.columnType === ColumnTypes.URL && props.title) { - return ; - } + let contentToRender; + + switch (props.columnType) { + case ColumnTypes.BUTTON: + if (props.title) { + return content; + } - if (props.columnType === ColumnTypes.BUTTON && props.title) { - return content; + break; + case ColumnTypes.URL: + contentToRender = ( + <> +
{content}
+ + + + + ); + break; + default: + contentToRender = content; } return ( @@ -212,12 +195,13 @@ export function AutoToolTipComponent(props: Props) { isCellDisabled={props.isCellDisabled} isCellVisible={props.isCellVisible} isHidden={props.isHidden} + isHyperLink={props.columnType === ColumnTypes.URL} isTextType textColor={props.textColor} textSize={props.textSize} verticalAlignment={props.verticalAlignment} > - {content} + {contentToRender} ); diff --git a/app/client/src/widgets/TableWidgetV2/component/cellComponents/AutoTooltipComponent.test.tsx b/app/client/src/widgets/TableWidgetV2/component/cellComponents/AutoTooltipComponent.test.tsx index 8a9a5721c71..bf46ff76952 100644 --- a/app/client/src/widgets/TableWidgetV2/component/cellComponents/AutoTooltipComponent.test.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/cellComponents/AutoTooltipComponent.test.tsx @@ -54,7 +54,7 @@ test("does not show tooltip for non-button types", () => { test("handles empty tooltip", () => { const { getByText } = render( - + , ); @@ -86,25 +86,6 @@ test("does not show tooltip for non-truncated text", () => { expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); }); -test("opens a new tab for URL column type when clicked", () => { - const openSpy = jest.spyOn(window, "open").mockImplementation(() => null); - - render( - - Go to Google - , - ); - - fireEvent.click(screen.getByText("Go to Google")); - expect(openSpy).toHaveBeenCalledWith("https://www.google.com", "_blank"); - - openSpy.mockRestore(); -}); - describe("isButtonTextTruncated", () => { function mockElementWidths( offsetWidth: number, diff --git a/app/client/src/widgets/TableWidgetV2/component/cellComponents/BasicCell.test.tsx b/app/client/src/widgets/TableWidgetV2/component/cellComponents/BasicCell.test.tsx new file mode 100644 index 00000000000..ee37e656b95 --- /dev/null +++ b/app/client/src/widgets/TableWidgetV2/component/cellComponents/BasicCell.test.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import { BasicCell, type PropType } from "./BasicCell"; +import { ColumnTypes } from "widgets/TableWidgetV2/constants"; +import { CompactModeTypes } from "widgets/TableWidget/component/Constants"; + +describe("BasicCell Component", () => { + const defaultProps: PropType = { + value: "Test Value", + onEdit: jest.fn(), + isCellEditable: false, + hasUnsavedChanges: false, + columnType: ColumnTypes.TEXT, + url: "", + compactMode: CompactModeTypes.DEFAULT, + isHidden: false, + isCellVisible: true, + accentColor: "", + tableWidth: 100, + disabledEditIcon: false, + disabledEditIconMessage: "", + }; + + it("renders the value", () => { + render(); + expect(screen.getByText("Test Value")).toBeInTheDocument(); + }); + + it("renders a link when columnType is URL", () => { + render( + , + ); + const link = screen.getByText("Test Value"); + + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute("href", "http://example.com"); + }); + + it("calls onEdit when double-clicked", () => { + render(); + fireEvent.doubleClick(screen.getByText("Test Value")); + expect(defaultProps.onEdit).toHaveBeenCalled(); + }); + + it("forwards ref to the div element", () => { + const ref = React.createRef(); + + render(); + expect(ref.current).toBeInstanceOf(HTMLDivElement); + }); +}); diff --git a/app/client/src/widgets/TableWidgetV2/component/cellComponents/BasicCell.tsx b/app/client/src/widgets/TableWidgetV2/component/cellComponents/BasicCell.tsx index 08d9efe8901..9b2431cf860 100644 --- a/app/client/src/widgets/TableWidgetV2/component/cellComponents/BasicCell.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/cellComponents/BasicCell.tsx @@ -1,12 +1,13 @@ import type { Ref } from "react"; -import React, { useCallback } from "react"; +import React, { useCallback, useMemo } from "react"; import { Tooltip } from "@blueprintjs/core"; import styled from "styled-components"; -import type { BaseCellComponentProps } from "../Constants"; +import type { BaseCellComponentProps, CompactMode } from "../Constants"; import { TABLE_SIZES } from "../Constants"; import { TooltipContentWrapper } from "../TableStyledWrappers"; import AutoToolTipComponent from "./AutoToolTipComponent"; import { importSvg } from "@appsmith/ads-old"; +import { ColumnTypes } from "widgets/TableWidgetV2/constants"; const EditIcon = importSvg( async () => import("assets/icons/control/edit-variant1.svg"), @@ -55,7 +56,7 @@ const Content = styled.div` const StyledEditIcon = styled.div<{ accentColor?: string; backgroundColor?: string; - compactMode: string; + compactMode: CompactMode; disabledEditIcon: boolean; }>` position: absolute; @@ -74,12 +75,12 @@ const StyledEditIcon = styled.div<{ } `; -type PropType = BaseCellComponentProps & { +export type PropType = BaseCellComponentProps & { accentColor: string; // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any value: any; - columnType: string; + columnType: ColumnTypes; tableWidth: number; isCellEditable?: boolean; isCellEditMode?: boolean; @@ -128,6 +129,18 @@ export const BasicCell = React.forwardRef( }, [onEdit, disabledEditIcon, isCellEditable], ); + const contentToRender = useMemo(() => { + switch (columnType) { + case ColumnTypes.URL: + return ( + + {value} + + ); + default: + return value; + } + }, [columnType, url, value]); return ( - {value} + {contentToRender} {isCellEditable && ( updateActionBasedOnContextType(NewAction newAction, Ac String pageId = newAction.getUnpublishedAction().getPageId(); action.setApplicationId(null); action.setPageId(null); - return updateSingleAction(newAction.getId(), action) - .name(UPDATE_SINGLE_ACTION) - .tap(Micrometer.observation(observationRegistry)) - .flatMap(updatedAction -> { - // Update page layout is skipped for JS actions here because when JSobject is updated, we first - // update all actions, action - // collection and then we update the page layout, hence updating page layout with each action update - // is not required here - if (action.getPluginType() != PluginType.JS) { - return updateLayoutService - .updatePageLayoutsByPageId(pageId) - .name(UPDATE_PAGE_LAYOUT_BY_PAGE_ID) - .tap(Micrometer.observation(observationRegistry)) - .thenReturn(updatedAction); - } - return Mono.just(updatedAction); - }) - .zipWhen(actionDTO -> newPageService.findPageById(pageId, pagePermission.getEditPermission(), false)) - .map(tuple2 -> { - ActionDTO actionDTO = tuple2.getT1(); - PageDTO pageDTO = tuple2.getT2(); - // redundant check - if (pageDTO.getLayouts().size() > 0) { - actionDTO.setErrorReports(pageDTO.getLayouts().get(0).getLayoutOnLoadActionErrors()); - } - log.debug( - "Update action based on context type completed, returning actionDTO with action id: {}", - actionDTO != null ? actionDTO.getId() : null); - return actionDTO; - }); + + // Update page layout is skipped for JS actions here because when JSobject is updated, we first + // update all actions, action + // collection and then we update the page layout, hence updating page layout with each action update + // is not required here + if (action.getPluginType() == PluginType.JS) { + return updateSingleAction(newAction.getId(), action) + .name(UPDATE_SINGLE_ACTION) + .tap(Micrometer.observation(observationRegistry)); + } else { + return updateSingleAction(newAction.getId(), action) + .name(UPDATE_SINGLE_ACTION) + .tap(Micrometer.observation(observationRegistry)) + .flatMap(updatedAction -> updateLayoutService + .updatePageLayoutsByPageId(pageId) + .name(UPDATE_PAGE_LAYOUT_BY_PAGE_ID) + .tap(Micrometer.observation(observationRegistry)) + .thenReturn(updatedAction)) + .zipWhen( + actionDTO -> newPageService.findPageById(pageId, pagePermission.getEditPermission(), false)) + .map(tuple2 -> { + ActionDTO actionDTO = tuple2.getT1(); + PageDTO pageDTO = tuple2.getT2(); + // redundant check + if (pageDTO.getLayouts().size() > 0) { + actionDTO.setErrorReports( + pageDTO.getLayouts().get(0).getLayoutOnLoadActionErrors()); + } + log.debug( + "Update action based on context type completed, returning actionDTO with action id: {}", + actionDTO != null ? actionDTO.getId() : null); + return actionDTO; + }); + } } @Override diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java index e21a319193c..af74b26950e 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java @@ -725,7 +725,7 @@ public Mono verifyCurrentUserIsSuper() { @Override public Mono restart() { - return verifyCurrentUserIsSuper().then(restartWithoutAclCheck()); + return verifyCurrentUserIsSuper().flatMap(user -> restartWithoutAclCheck()); } /** diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionCollectionServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionCollectionServiceTest.java index f201b0a3bff..c21a0f49589 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionCollectionServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionCollectionServiceTest.java @@ -26,6 +26,7 @@ import com.appsmith.server.dtos.PluginWorkspaceDTO; import com.appsmith.server.dtos.RefactorEntityNameDTO; import com.appsmith.server.dtos.WorkspacePluginStatus; +import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.helpers.MockPluginExecutor; import com.appsmith.server.helpers.PluginExecutorHelper; import com.appsmith.server.layouts.UpdateLayoutService; @@ -73,6 +74,7 @@ import static com.appsmith.server.constants.FieldName.VIEWER; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; @SpringBootTest @Slf4j @@ -708,7 +710,8 @@ public void testDeleteActionCollection_afterApplicationPublish_clearsActionColle @Test @WithUserDetails(value = "api_user") - public void testUpdateUnpublishedActionCollection_withValidCollection_callsPageLayoutOnlyOnce() { + public void + testUpdateUnpublishedActionCollection_withValidCollection_callsPageLayoutOnlyOnceAndAssertCyclicDependencyError() { Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(pluginExecutor)); Mockito.when(pluginExecutor.getHintMessages(Mockito.any(), Mockito.any())) .thenReturn(Mono.zip(Mono.just(new HashSet<>()), Mono.just(new HashSet<>()))); @@ -727,7 +730,7 @@ public void testUpdateUnpublishedActionCollection_withValidCollection_callsPageL ActionDTO action1 = new ActionDTO(); action1.setName("testAction1"); action1.setActionConfiguration(new ActionConfiguration()); - action1.getActionConfiguration().setBody("mockBody"); + action1.getActionConfiguration().setBody("initial body"); action1.getActionConfiguration().setIsValid(false); ActionDTO action2 = new ActionDTO(); @@ -744,15 +747,35 @@ public void testUpdateUnpublishedActionCollection_withValidCollection_callsPageL actionCollectionDTO.setActions(List.of(action1, action2, action3)); + Layout layout = testPage.getLayouts().get(0); + ArrayList dslList = (ArrayList) layout.getDsl().get("children"); + JSONObject tableDsl = (JSONObject) dslList.get(0); + tableDsl.put("tableData", "{{testCollection1.testAction1.data}}"); + JSONArray temp2 = new JSONArray(); + temp2.add(new JSONObject(Map.of("key", "tableData"))); + tableDsl.put("dynamicBindingPathList", temp2); + JSONArray temp3 = new JSONArray(); + temp3.add(new JSONObject(Map.of("key", "tableData"))); + tableDsl.put("dynamicPropertyPathList", temp3); + layout.getDsl().put("widgetName", "MainContainer"); + + testPage.setLayouts(List.of(layout)); + PageDTO updatedPage = + newPageService.updatePage(testPage.getId(), testPage).block(); + + // Create Js object ActionCollectionDTO createdActionCollectionDTO = layoutCollectionService.createCollection(actionCollectionDTO).block(); assert createdActionCollectionDTO != null; assert createdActionCollectionDTO.getId() != null; String createdActionCollectionId = createdActionCollectionDTO.getId(); - applicationPageService.publish(testApp.getId(), true).block(); - - actionCollectionDTO.getActions().get(0).getActionConfiguration().setBody("updatedBody"); + // Update JS object to create cyclic dependency + actionCollectionDTO.getActions().stream() + .filter(action -> "testAction1".equals(action.getName())) + .findFirst() + .ifPresent(action -> + action.getActionConfiguration().setBody("function () {\n return Table1.tableData;\n}")); final Mono updatedActionCollectionDTO = layoutCollectionService.updateUnpublishedActionCollection( @@ -766,6 +789,18 @@ public void testUpdateUnpublishedActionCollection_withValidCollection_callsPageL // collection as expected Mockito.verify(updateLayoutService, Mockito.times(2)) .updatePageLayoutsByPageId(Mockito.anyString()); + + assertEquals(1, actionCollectionDTO1.getErrorReports().size()); + assertEquals( + AppsmithError.CYCLICAL_DEPENDENCY_ERROR.getAppErrorCode(), + actionCollectionDTO1.getErrorReports().get(0).getCode()); + + // Iterate over each action and assert that errorReports is null as action collection already has + // error reports + // it's not required in each action + actionCollectionDTO + .getActions() + .forEach(action -> assertNull(action.getErrorReports(), "Error reports should be null")); }) .verifyComplete(); }